ip-wrangler 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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