ip-wrangler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ module IpWrangler
2
+ module Exec
3
+ $iptables_bin_path = '/usr/bin/sudo /sbin/iptables'
4
+
5
+ extend self
6
+
7
+ def execute_command(command)
8
+ output = `#{command}`
9
+ puts "Execute: #{command} => output: #{output}, result: #{$?.exitstatus}"
10
+ output
11
+ end
12
+
13
+ def execute_iptables_command(command)
14
+ execute_command("#{$iptables_bin_path} #{command}")
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,37 @@
1
+ module IpWrangler
2
+ module Ip
3
+ def parse_ip(ip)
4
+ ip.split(/\./).map { |octet| octet.to_i }
5
+ end
6
+
7
+ def str_ip(ip)
8
+ "#{ip[0]}.#{ip[1]}.#{ip[2]}.#{ip[3]}"
9
+ end
10
+
11
+ def validate_ip(ip)
12
+ ip.select { |octet| octet < 0 || octet > 255 }.length == 0
13
+ end
14
+
15
+ def range_ips(start, stop)
16
+ if validate_ip(start) && validate_ip(stop)
17
+ start = start.inject { |sum, octet| sum * 256 + octet }
18
+ stop = stop.inject { |sum, octet| sum * 256 + octet }
19
+
20
+ (start..stop).map do |ip|
21
+ IP.new([(ip / 16777216) % 256,
22
+ (ip / 65536) % 256,
23
+ (ip / 256) % 256,
24
+ ip % 256])
25
+ end
26
+ end
27
+ end
28
+
29
+ def validate_port(port)
30
+ port > 0 && port < 65536
31
+ end
32
+
33
+ def range_ports(start, stop, ip, protocol)
34
+ (start..stop).map { |port| Port.new(ip, port, protocol) }
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,222 @@
1
+ module IpWrangler
2
+ class Iptables
3
+ $iptables_bin_path = '/usr/bin/sudo /sbin/iptables'
4
+ $awk_bin_path = '/usr/bin/awk'
5
+ $tail_bin_path = '/usr/bin/tail'
6
+ $grep_bin_path = '/bin/grep'
7
+
8
+ def initialize(chain_name, logger)
9
+ @chain_name = chain_name
10
+ @logger = logger
11
+ end
12
+
13
+ def rule_nat_port(public_ip, public_port, private_ip, private_port, protocol)
14
+ [Parameter.destination(public_ip),
15
+ Parameter.protocol(protocol),
16
+ Parameter.destination_port(public_port),
17
+ Parameter.jump('DNAT'),
18
+ Parameter.to_destination("#{private_ip}:#{private_port}")]
19
+ end
20
+
21
+ def rule_nat_ip(public_ip, private_ip)
22
+ rule_dnat = [Parameter.destination(public_ip),
23
+ Parameter.jump('DNAT'),
24
+ Parameter.to_destination(private_ip)]
25
+ rule_snat = [Parameter.source(private_ip),
26
+ Parameter.jump('SNAT'),
27
+ Parameter.to(public_ip)]
28
+ return rule_dnat, rule_snat
29
+ end
30
+
31
+ def append_nat_port(public_ip, public_port, private_ip, private_port, protocol)
32
+ rule = rule_nat_port(public_ip, public_port, private_ip, private_port, protocol)
33
+
34
+ execute(Command.check_rule("#{@chain_name}_PRE", 'nat', rule))
35
+ if $?.exitstatus == 1
36
+ execute(Command.append_rule("#{@chain_name}_PRE", 'nat', rule))
37
+ end
38
+ end
39
+
40
+ def append_nat_ip(public_ip, private_ip)
41
+ rule_dnat, rule_snat = rule_nat_ip(public_ip, private_ip)
42
+
43
+ execute(Command.check_rule("#{@chain_name}_PRE", 'nat', rule_dnat))
44
+ if $?.exitstatus == 1
45
+ execute(Command.append_rule("#{@chain_name}_PRE", 'nat', rule_dnat))
46
+ end
47
+ execute(Command.check_rule("#{@chain_name}_POST", 'nat', rule_snat))
48
+ if $?.exitstatus == 1
49
+ execute(Command.append_rule("#{@chain_name}_POST", 'nat', rule_snat))
50
+ end
51
+ end
52
+
53
+ def delete_nat_port(public_ip, public_port, private_ip, private_port, protocol)
54
+ rule = rule_nat_port(public_ip, public_port, private_ip, private_port, protocol)
55
+
56
+ execute(Command.delete_rule_spec("#{@chain_name}_PRE", rule, 'nat'))
57
+ end
58
+
59
+ def delete_nat_ip(public_ip, private_ip)
60
+ rule_dnat, rule_snat = rule_nat_ip(public_ip, private_ip)
61
+
62
+ command_dnat = Command.delete_rule_spec("#{@chain_name}_PRE", rule_dnat, 'nat')
63
+ command_snat = Command.delete_rule_spec("#{@chain_name}_POST", rule_snat, 'nat')
64
+ execute(command_dnat, command_snat)
65
+ end
66
+
67
+ def not_exists_nat_port?(public_ip, public_port, protocol, _, _)
68
+ command = "#{$iptables_bin_path} -t nat -n -v -L #{@chain_name}_PRE | "\
69
+ "#{$awk_bin_path} '{print $9, $10, $11, $12}' | "\
70
+ "#{$grep_bin_path} -i '^#{public_ip} #{protocol} dpt:#{public_port}'"
71
+ output = IpWrangler::Exec.execute_command(command)
72
+ output.empty?
73
+ end
74
+
75
+ def not_exists_nat_ip?(public_ip, _)
76
+ command = "#{$iptables_bin_path} -t nat -n -v -L #{@chain_name}_PRE | "\
77
+ "#{$awk_bin_path} '{print $9, $10}' | "\
78
+ "#{$grep_bin_path} -i '^#{public_ip}'"
79
+ output = IpWrangler::Exec.execute_command(command)
80
+ output.empty?
81
+ end
82
+
83
+ def execute(*commands)
84
+ commands.each do |command|
85
+ IpWrangler::Exec.execute_iptables_command("#{command}")
86
+ end
87
+ end
88
+ end
89
+
90
+ class Command
91
+ @@commands = {
92
+ append_rule: '--append',
93
+ insert_rule: '--insert',
94
+ replace_rule: '--replace',
95
+ check_rule: '--check',
96
+ delete_rule: '--delete',
97
+ new_chain: '--new-chain',
98
+ rename_chain: '--rename-chain',
99
+ policy_chain: '--policy',
100
+ zero_chain: '--zero',
101
+ flush_chain: '--flush',
102
+ delete_chain: '--delete-chain'
103
+ }
104
+
105
+ def self.parameters_to_s(parameters)
106
+ __parameters = ''
107
+ parameters.each { |parameter| __parameters = "#{__parameters} #{parameter} " }
108
+ "#{__parameters}".gsub(/\s+/, ' ')
109
+ end
110
+
111
+ def self.append_rule(chain, table, parameters)
112
+ "-t #{table} #{@@commands[:append_rule]} #{chain} #{parameters_to_s(parameters)}"
113
+ end
114
+
115
+ def self.insert_rule(chain, num, table, parameters)
116
+ "-t #{table} #{@@commands[:insert_rule]} #{chain} #{num} #{parameters_to_s(parameters)}"
117
+ end
118
+
119
+ def self.replace_rule(chain, num, table, parameters)
120
+ "-t #{table} #{@@commands[:replace_rule]} #{chain} #{num} #{parameters_to_s(parameters)}"
121
+ end
122
+
123
+ def self.check_rule(chain, table, parameters)
124
+ "-t #{table} #{@@commands[:check_rule]} #{chain} #{parameters_to_s(parameters)}"
125
+ end
126
+
127
+ def self.delete_rule(chain, num, table)
128
+ "-t #{table} #{@@commands[:delete_rule]} #{chain} #{num}"
129
+ end
130
+
131
+ def self.delete_rule_spec(chain, parameters, table)
132
+ "-t #{table} #{@@commands[:delete_rule]} #{chain} #{parameters_to_s(parameters)}"
133
+ end
134
+
135
+ def self.new_chain(chain, table)
136
+ "-t #{table} #{@@commands[:new_chain]} #{chain}"
137
+ end
138
+
139
+ def self.rename_chain(old_chain, new_chain, table)
140
+ "-t #{table} #{@@commands[:rename_chain]} #{old_chain} #{new_chain}"
141
+ end
142
+
143
+ def self.policy_chain(chain, target, table)
144
+ "-t #{table} #{@@commands[:policy_chain]} #{chain} #{target}"
145
+ end
146
+
147
+ def self.zero_chain(chain, num, table)
148
+ "-t #{table} #{@@commands[:zero_chain]} #{chain} #{num}"
149
+ end
150
+
151
+ def self.flush_chain(chain, table)
152
+ "-t #{table} #{@@commands[:flush_chain]} #{chain}"
153
+ end
154
+
155
+ def self.delete_chain(chain, table)
156
+ "-t #{table} #{@@commands[:delete_chain]} #{chain}"
157
+ end
158
+ end
159
+
160
+ class Parameter
161
+ @@parameters = {
162
+ protocol: '--protocol',
163
+ source: '--source',
164
+ destination: '--destination',
165
+ source_port: '--source-port',
166
+ destination_port: '--destination-port',
167
+ in_interface: '--in-interface',
168
+ out_interface: '--out-interface',
169
+ to_destination: '--to-destination',
170
+ to: '--to',
171
+ jump: '--jump'
172
+ }
173
+
174
+ def initialize(name, value = nil)
175
+ @name, @value = name, value
176
+ end
177
+
178
+ def to_s
179
+ "#{@name} #{@value}"
180
+ end
181
+
182
+ def self.protocol(protocol)
183
+ new(@@parameters[:protocol], protocol)
184
+ end
185
+
186
+ def self.source(source)
187
+ new(@@parameters[:source], source)
188
+ end
189
+
190
+ def self.destination(destination)
191
+ new(@@parameters[:destination], destination)
192
+ end
193
+
194
+ def self.source_port(source_port)
195
+ new(@@parameters[:source_port], source_port)
196
+ end
197
+
198
+ def self.destination_port(destination_port)
199
+ new(@@parameters[:destination_port], destination_port)
200
+ end
201
+
202
+ def self.in_interface(in_interface)
203
+ new(@@parameters[:in_interface], in_interface)
204
+ end
205
+
206
+ def self.out_interface(out_interface)
207
+ new(@@parameters[:out_interface], out_interface)
208
+ end
209
+
210
+ def self.to_destination(destination)
211
+ new(@@parameters[:to_destination], destination)
212
+ end
213
+
214
+ def self.to(destination)
215
+ new(@@parameters[:to], destination)
216
+ end
217
+
218
+ def self.jump(target)
219
+ new(@@parameters[:jump], target)
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,324 @@
1
+ config_file = ENV['__config_file']
2
+ $config = YAML.load_file(config_file)
3
+
4
+ use Rack::Auth::Basic, 'Restricted Area' do |username, password|
5
+ [username, password] == [$config['username'], $config['password']]
6
+ end
7
+
8
+ $logger = Logger.new("#{$config['log_dir']}/app_output.log")
9
+
10
+ $nat = IpWrangler::NAT.new($config, $logger)
11
+
12
+ def sandbox(&block)
13
+ begin
14
+ content_type('application/json')
15
+ yield
16
+ rescue RuntimeError => e
17
+ $logger.error("Runtime error: #{e}:#{e.backtrace}")
18
+ 400
19
+ rescue Exception => e
20
+ $logger.error("Unresolved exception: #{e}:#{e.backtrace}")
21
+ 500
22
+ end
23
+ end
24
+
25
+ def valid_ip?(ip)
26
+ (ip =~ /^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}$/) ? true : false
27
+ end
28
+
29
+ def valid_port?(port)
30
+ (1..65535).include?(port)
31
+ end
32
+
33
+ def valid_protocol?(protocol)
34
+ protocol =~ /^(tcp|udp)$/i ? true : false
35
+ end
36
+
37
+ def release_ip_and_check(private_ip, public_ip = nil)
38
+ released_ip = $nat.release_ip(private_ip, public_ip)
39
+ check_released_resource(released_ip)
40
+ end
41
+
42
+ def release_port_and_check(private_ip, private_port = nil, protocol = nil)
43
+ released_port = $nat.release_port(private_ip, private_port, protocol)
44
+ check_released_resource(released_port)
45
+ end
46
+
47
+ def check_released_resource(released_resource)
48
+ if released_resource.length > 0
49
+ [200, released_resource.to_json]
50
+ else
51
+ 204
52
+ end
53
+ end
54
+
55
+ # List any NAT port(s)
56
+ get '/nat/port' do
57
+ sandbox do
58
+ $nat.get_nat_ports.map { |nat_port|
59
+ puts nat_port[:public_ip]
60
+ nat_port[:public_ip] = $config['ext_ip']
61
+ nat_port
62
+ }.to_json
63
+ end
64
+ end
65
+
66
+ # List NAT port(s) for specified private IP
67
+ get '/nat/port/*' do |private_ip|
68
+ sandbox do
69
+ if valid_ip?(private_ip)
70
+ $nat.get_nat_ports(private_ip).map { |nat_port|
71
+ puts nat_port[:public_ip]
72
+ nat_port[:public_ip] = $config['ext_ip']
73
+ nat_port
74
+ }.to_json
75
+ else
76
+ 500
77
+ end
78
+ end
79
+ end
80
+
81
+ # List any NAT IP(s)
82
+ get '/nat/ip' do
83
+ sandbox do
84
+ $nat.get_nat_ips.map { |nat_ip| nat_ip }.to_json
85
+ end
86
+ end
87
+
88
+ # List NAT IP(s) for specified private IP
89
+ get '/nat/ip/*' do |private_ip|
90
+ sandbox do
91
+ if valid_ip?(private_ip)
92
+ $nat.get_nat_ips(private_ip).map { |nat_ip| nat_ip }.to_json
93
+ else
94
+ 500
95
+ end
96
+ end
97
+ end
98
+
99
+ # Create NAT port(s) for specified IP
100
+ post '/nat/port/*/*/*' do |private_ip, private_port, protocol|
101
+ sandbox do
102
+ private_port = private_port.to_i
103
+ if valid_ip?(private_ip) && valid_port?(private_port) && valid_protocol?(protocol)
104
+ public_ip_port = $nat.lock_port(private_ip, private_port, protocol)
105
+
106
+ if public_ip_port
107
+ public_ip_port[:public_ip] = $config['ext_ip']
108
+ public_ip_port.to_json
109
+ else
110
+ 404
111
+ end
112
+ else
113
+ 500
114
+ end
115
+ end
116
+ end
117
+
118
+ # Create NAT port(s) for specified IP
119
+ post '/nat/port/*/*' do |private_ip, private_port|
120
+ sandbox do
121
+ private_port = private_port.to_i
122
+ if valid_ip?(private_ip) && valid_port?(private_port)
123
+ public_ip_port_tcp = $nat.lock_port(private_ip, private_port, 'tcp')
124
+ public_ip_port_udp = $nat.lock_port(private_ip, private_port, 'udp')
125
+
126
+ out = nil
127
+
128
+ if public_ip_port_tcp
129
+ public_ip_port_tcp[:public_ip] = $config['ext_ip']
130
+ out = "#{public_ip_port_tcp.to_json}"
131
+ end
132
+
133
+ if public_ip_port_udp
134
+ public_ip_port_udp[:public_ip] = $config['ext_ip']
135
+ if out
136
+ out += ",#{public_ip_port_udp.to_json}"
137
+ else
138
+ out = "#{public_ip_port_udp.to_json}"
139
+ end
140
+ end
141
+
142
+ if out
143
+ out = "[#{out}]"
144
+ out
145
+ else
146
+ 404
147
+ end
148
+ else
149
+ 500
150
+ end
151
+ end
152
+ end
153
+
154
+ # Create NAT IP for specified private IP
155
+ post '/nat/ip/*' do |private_ip|
156
+ sandbox do
157
+ if valid_ip?(private_ip)
158
+ public_ip = $nat.lock_ip(private_ip)
159
+
160
+ if public_ip
161
+ public_ip.to_json
162
+ else
163
+ 404
164
+ end
165
+ else
166
+ 500
167
+ end
168
+ end
169
+ end
170
+
171
+ # Delete NAT port with specified protocol for specified IP
172
+ delete '/nat/port/*/*/*' do |private_ip, private_port, protocol|
173
+ sandbox do
174
+ private_port = private_port.to_i
175
+ if valid_ip?(private_ip) && valid_port?(private_port) && valid_protocol?(protocol)
176
+ release_port_and_check(private_ip, private_port, protocol)
177
+ else
178
+ 500
179
+ end
180
+ end
181
+ end
182
+
183
+ # Delete NAT port with any protocol (TCP and UDP) for specified IP
184
+ delete '/nat/port/*/*' do |private_ip, private_port|
185
+ sandbox do
186
+ private_port = private_port.to_i
187
+ if valid_ip?(private_ip) && valid_port?(private_port)
188
+ release_port_and_check(private_ip, private_port)
189
+ else
190
+ 500
191
+ end
192
+ end
193
+ end
194
+
195
+ # Delete any NAT ports for specified IP
196
+ delete '/nat/port/*' do |private_ip|
197
+ sandbox do
198
+ if valid_ip?(private_ip)
199
+ release_port_and_check(private_ip)
200
+ else
201
+ 500
202
+ end
203
+ end
204
+ end
205
+
206
+ # Delete NAT IP for specified IP
207
+ delete '/nat/ip/*/*' do |private_ip, public_ip|
208
+ sandbox do
209
+ if valid_ip?(private_ip) && valid_ip?(public_ip)
210
+ release_ip_and_check(private_ip, public_ip)
211
+ else
212
+ 500
213
+ end
214
+ end
215
+ end
216
+
217
+ # Delete NAT any IP(s) for specified IP
218
+ delete '/nat/ip/*' do |private_ip|
219
+ sandbox do
220
+ if valid_ip?(private_ip)
221
+ release_ip_and_check(private_ip)
222
+ else
223
+ 500
224
+ end
225
+ end
226
+ end
227
+
228
+ # OLD API (compatibility)
229
+
230
+ get '/' do
231
+ 'IptWr REST Endpoint!'
232
+ end
233
+
234
+ get '/dnat' do
235
+ sandbox do
236
+ $nat.get_nat_ports.map { |nat_port|
237
+ nat_port[:public_ip] = $config['ext_ip']
238
+ nat_port[:privPort] = nat_port[:private_port]
239
+ nat_port[:pubIp] = nat_port[:public_ip]
240
+ nat_port[:pubPort] = nat_port[:public_port]
241
+ nat_port
242
+ }.to_json
243
+ end
244
+ end
245
+
246
+ get '/dnat/*' do |ip|
247
+ sandbox do
248
+ if valid_ip?(ip)
249
+ $nat.get_nat_ports(ip).map { |nat_port|
250
+ nat_port[:public_ip] = $config['ext_ip']
251
+ nat_port[:privPort] = nat_port[:private_port]
252
+ nat_port[:pubIp] = nat_port[:public_ip]
253
+ nat_port[:pubPort] = nat_port[:public_port]
254
+ nat_port
255
+ }.to_json
256
+ else
257
+ 500
258
+ end
259
+ end
260
+ end
261
+
262
+ post '/dnat/*' do |ip|
263
+ sandbox do
264
+ redirects = []
265
+
266
+ request.body.rewind
267
+ data = JSON.parse(request.body.read)
268
+
269
+ if valid_ip?(ip)
270
+ data.each do |dpp|
271
+ if valid_port?(dpp['port']) && valid_protocol?(dpp['proto'])
272
+ redir = $nat.lock_port(ip, dpp['port'], dpp['proto'])
273
+ if redir
274
+ redir[:public_ip] = $config['ext_ip']
275
+ redir[:privPort] = redir[:private_port]
276
+ redir[:pubIp] = redir[:public_ip]
277
+ redir[:pubPort] = redir[:public_port]
278
+ redirects.push(redir)
279
+ end
280
+ end
281
+ end
282
+
283
+ if redirects.length > 0
284
+ redirects.to_json
285
+ else
286
+ 404
287
+ end
288
+ else
289
+ 500
290
+ end
291
+ end
292
+ end
293
+
294
+ delete '/dnat/*/*/*' do |ip, port, proto|
295
+ sandbox do
296
+ port = port.to_i
297
+ if valid_ip?(ip) && valid_port?(port) && valid_protocol?(proto)
298
+ release_port_and_check(ip, port, proto)
299
+ else
300
+ 500
301
+ end
302
+ end
303
+ end
304
+
305
+ delete '/dnat/*/*' do |ip, port|
306
+ sandbox do
307
+ port = port.to_i
308
+ if valid_ip?(ip) && valid_port?(port)
309
+ release_port_and_check(ip, port)
310
+ else
311
+ 500
312
+ end
313
+ end
314
+ end
315
+
316
+ delete '/dnat/*' do |ip|
317
+ sandbox do
318
+ if valid_ip?(ip)
319
+ release_port_and_check(ip)
320
+ else
321
+ 500
322
+ end
323
+ end
324
+ end