iptables-ruby 0.2.4

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.
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ require "rubygems"
2
+ require "rubygems/package_task"
3
+ require "rdoc/task"
4
+
5
+ # This builds the actual gem. For details of what all these options
6
+ # mean, and other ones you can add, check the documentation here:
7
+ #
8
+ # http://rubygems.org/read/chapter/20
9
+ #
10
+
11
+ # This task actually builds the gem. We also regenerate a static
12
+ # .gemspec file, which is useful if something (i.e. GitHub) will
13
+ # be automatically building a gem for this project. If you're not
14
+ # using GitHub, edit as appropriate.
15
+ #
16
+ # To publish your gem online, install the 'gemcutter' gem; Read more
17
+ # about that here: http://gemcutter.org/pages/gem_docs
18
+ Gem::PackageTask.new(spec) do |pkg|
19
+ pkg.gem_spec = spec
20
+ end
21
+
22
+ desc "Build the gemspec file #{spec.name}.gemspec"
23
+ task :gemspec do
24
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
25
+ File.open(file, "w") {|f| f << spec.to_ruby }
26
+ end
27
+
28
+ # If you don't want to generate the .gemspec file, just remove this line. Reasons
29
+ # why you might want to generate a gemspec:
30
+ # - using bundler with a git source
31
+ # - building the gem without rake (i.e. gem build blah.gemspec)
32
+ # - maybe others?
33
+ task :package => :gemspec
34
+
35
+ # Generate documentation
36
+ RDoc::Task.new do |rd|
37
+ rd.rdoc_files.include("lib/**/*.rb")
38
+ rd.rdoc_dir = "rdoc"
39
+ end
40
+
41
+ desc 'Clear out RDoc and generated packages'
42
+ task :clean => [:clobber_rdoc, :clobber_package] do
43
+ rm "#{spec.name}.gemspec"
44
+ end
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env ruby
2
+ # Check whether the active firewall matches the merged/policy firewall
3
+
4
+ config_path = '/var/lib/iptables'
5
+ json_configs = %w/policy6 policy services macros rules primitives/
6
+ @verbosity = 0
7
+ compare_comments = true
8
+ iptables_save_path = nil
9
+
10
+ def exit_status(status = 0)
11
+ if @verbosity > 0
12
+ case status
13
+ when 0
14
+ colored = "0 (OK)".green
15
+ when 1
16
+ colored = "1 (Warning)".yellow
17
+ when 2
18
+ colored = '2 (Critical)'.red
19
+ when 3
20
+ colored = '3 (Unknown)'.red
21
+ else
22
+ colored = "#{status} (Not a Nagios status)".yellow
23
+ end
24
+ puts "exiting with status ".green + colored
25
+ end
26
+ exit status
27
+ end
28
+
29
+ # handle command-line args
30
+ ARGV.each{ |arg|
31
+ case arg
32
+ when /^-c/
33
+ raise "specify configuration path using format '-c=/the/path'" unless arg.match(/^-c=(.+)/)
34
+ config_path = $1
35
+
36
+ when /^\-*h/, /^help/
37
+ # inspired by https://github.com/cespare/ruby-dedent
38
+ class String
39
+ def dedent
40
+ lines = split "\n"
41
+ return self if lines.empty?
42
+ # first indented line determines indent level
43
+ indentation = nil
44
+ lines.each{ |line|
45
+ next unless line =~ /^(\s+)/
46
+ indentation = $1
47
+ break
48
+ }
49
+ return self if indentation.nil?
50
+ lines.map { |line| line.sub(/^#{indentation}/, "") }.join "\n"
51
+ end
52
+ end
53
+
54
+ puts "
55
+ ### #{$0} ###
56
+ check whether firewall is applied
57
+ path to configurations for generated firewall:
58
+ #{config_path}
59
+ configuration json files:
60
+ #{json_configs.join(' ')}
61
+ options:
62
+ -c=/path/to/configuration:
63
+ path for configurations for generated firewall
64
+ leave blank for default
65
+ -h: this help
66
+ -ignore-comments:
67
+ when comparing firewalls, ignore comment differences
68
+ -l=/path/to/library:
69
+ path to iptables library, for testing
70
+ leave blank for default
71
+ -s=/path/to/iptables-save/output:
72
+ path to saved text output from `iptables-save`
73
+ if you specify this, it will be used instead of running `iptables-save`
74
+ -v: run verbosely
75
+ add more Vs for increased verbosity
76
+ ".dedent
77
+ exit
78
+
79
+ when /^-ignore-comments/
80
+ compare_comments = false
81
+
82
+ when /^-l/
83
+ # to test with a custom iptables lib:
84
+ raise "specify library path using format '-l=/the/path'" unless arg.match(/^-l=(.+)/)
85
+ raise "library path not found" unless File.directory? $1
86
+ require 'rubygems'
87
+ $:.unshift $1
88
+
89
+ when /^-s/
90
+ raise "specify `iptables-save` output path using format '-s=/the/path'" unless arg.match(/^-s=(.+)/)
91
+ iptables_save_path = $1
92
+
93
+ when /^-(v+)/
94
+ @verbosity = $1.length
95
+
96
+ # since we're verbose, make some colors
97
+ class String
98
+ def colorize(color_code)
99
+ "\e[#{color_code}m#{self}\e[0m"
100
+ end
101
+
102
+ def green
103
+ colorize(32)
104
+ end
105
+
106
+ def red
107
+ colorize(31)
108
+ end
109
+
110
+ def yellow
111
+ colorize(33)
112
+ end
113
+ end
114
+
115
+ else
116
+ raise "unknown argument: #{arg}"
117
+ end
118
+ }
119
+
120
+ %w/rubygems iptables/.each{ |module_name|
121
+ begin
122
+ require "#{module_name}"
123
+ rescue LoadError
124
+ puts "UNKNOWN: unable to load module '#{module_name}'"
125
+ exit_status 3
126
+ end
127
+ }
128
+
129
+ if @verbosity > 2
130
+ require 'logger'
131
+ $log = Logger.new(STDOUT)
132
+ $log.level = Logger::DEBUG
133
+ end
134
+
135
+ config = IPTables::Configuration.new
136
+ puts "reading configs".green if @verbosity > 0
137
+ json_configs.each{ |config_type|
138
+ config_file_path = "#{config_path}/#{config_type}.json"
139
+ puts " - #{config_file_path}".green if @verbosity > 1
140
+ unless File.readable? config_file_path
141
+ puts "UNKNOWN: could not read config_file_path #{config_file_path}"
142
+ exit_status 3
143
+ end
144
+ begin
145
+ config.parse_files(config_file_path)
146
+ rescue Exception => e
147
+ raise e if @verbosity > 0
148
+ puts "UNKNOWN: parsing #{config_file_path} failed: #{e.message}"
149
+ exit_status 3
150
+ end
151
+ }
152
+
153
+ puts "converging firewall".green if @verbosity > 0
154
+ begin
155
+ policy_fw = config.converge_firewall
156
+ puts '--- CONVERGED FIREWALL BEGIN ---'.yellow if @verbosity > 1
157
+ puts policy_fw.as_array if @verbosity > 1
158
+ puts '--- CONVERGED FIREWALL END ---'.yellow if @verbosity > 1
159
+ rescue Exception => e
160
+ raise e if @verbosity > 0
161
+ puts "UNKNOWN: firewall converge failed: #{e.message}"
162
+ exit_status 3
163
+ end
164
+
165
+ if iptables_save_path.nil?
166
+ puts "retrieving active firewall".green if @verbosity > 0
167
+ iptables_save = %x/iptables-save/
168
+ else
169
+ puts "retrieving saved firewall".green if @verbosity > 0
170
+ puts " - #{iptables_save_path}".green if @verbosity > 1
171
+ unless File.readable? iptables_save_path
172
+ puts "UNKNOWN: could not read iptables_save_path #{iptables_save_path}"
173
+ exit_status 3
174
+ end
175
+ iptables_save = File.readlines(iptables_save_path).join()
176
+ end
177
+ puts '--- RETRIEVED FIREWALL BEGIN ---'.yellow if @verbosity > 1
178
+ puts iptables_save if @verbosity > 1
179
+ puts '--- RETRIEVED FIREWALL END ---'.yellow if @verbosity > 1
180
+ if iptables_save.empty?
181
+ puts "UNKNOWN: iptables-save output is empty; do you have root permissions?"
182
+ exit_status 3
183
+ end
184
+
185
+ puts "parsing active firewall".green if @verbosity > 0
186
+ begin
187
+ active_firewall = IPTables::Tables.new(iptables_save)
188
+ puts '--- PARSED FIREWALL BEGIN ---'.yellow if @verbosity > 1
189
+ puts active_firewall.as_array if @verbosity > 1
190
+ puts '--- PARSED FIREWALL END ---'.yellow if @verbosity > 1
191
+ rescue Exception => e
192
+ raise e if @verbosity > 0
193
+ puts "UNKNOWN: unable to parse active firewall: #{e.message}"
194
+ exit_status 3
195
+ end
196
+
197
+ puts "comparing active firewall to converged firewall".green if @verbosity > 0
198
+ puts "differences in comments are ".green + (compare_comments ? 'compared'.green : 'ignored'.red) if @verbosity > 0
199
+ comparison = IPTables::TablesComparison.new(active_firewall, policy_fw)
200
+ comparison.ignore_comments unless compare_comments
201
+ unless comparison.equal?
202
+ if @verbosity > 2
203
+ require 'pp'
204
+ puts "--- BEGIN ACTIVE_FIREWALL AS ARRAY ---".yellow
205
+ pp active_firewall.as_array(comments = compare_comments)
206
+ puts "--- END ACTIVE_FIREWALL AS ARRAY ---".yellow
207
+ puts "--- BEGIN POLICY_FIREWALL AS ARRAY ---".yellow
208
+ pp policy_fw.as_array(comments = compare_comments)
209
+ puts "--- END POLICY_FIREWALL AS ARRAY ---".yellow
210
+ end
211
+ puts "--- BEGIN NAGIOS MESSAGE ---".yellow if @verbosity > 0
212
+ puts "WARNING: firewall needs to be applied"
213
+ puts comparison.as_array.join("\n")
214
+ puts "--- END NAGIOS MESSAGE ---".yellow if @verbosity > 0
215
+ exit_status 1
216
+ end
217
+
218
+ puts "Nagios message:" if @verbosity > 0
219
+ puts "OK: active firewall matches policy firewall"
220
+ exit_status
@@ -0,0 +1,92 @@
1
+ {
2
+ "macros": {
3
+ "accept-established": "-m state --state ESTABLISHED -j ACCEPT",
4
+ "accept-new_established": "-m state --state NEW,ESTABLISHED -j ACCEPT",
5
+ "accept-in_storage": [
6
+ {
7
+ "comment": "_ My storage ip address from storage addresses"
8
+ },
9
+ {
10
+ "interpolated": "-s <% storage.subnet %> -d <% storage.address %> -i <% storage.device %> -j ACCEPT",
11
+ "requires_primitive": "storage.address"
12
+ }
13
+ ],
14
+ "accept-related": "-m state --state RELATED -j ACCEPT",
15
+ "filter-attacks_and_probes": [
16
+ {
17
+ "comment": "_ Protect against attacks and probes"
18
+ },
19
+ "-m state --state INVALID -j DROP",
20
+ "-f -j strict_fragments",
21
+ "-p tcp -m state --state NEW -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -j strict_nosyn",
22
+ "-p icmp -m icmp --icmp-type 8 -j strict_icmpflood",
23
+ "-p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j strict_synflood",
24
+ "-p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,PSH,ACK,URG -j strict_malxmas",
25
+ "-p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j strict_malnull",
26
+ "-p tcp -m tcp --tcp-flags FIN,SYN FIN,SYN -j strict_malbad",
27
+ "-p tcp -m tcp --tcp-flags SYN,RST SYN,RST -j strict_malbad",
28
+ "-p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,ACK,URG -j strict_malbad",
29
+ "-p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,PSH,URG -j strict_malbad"
30
+ ],
31
+ "jump-in_peers": [
32
+ {
33
+ "comment": "_ My internet ip address on internet-facing network"
34
+ },
35
+ {
36
+ "interpolated": "-s <% internet.subnet.vpn %> -d <% internet.address %> -i <% internet.device %> -j in_peers"
37
+ },
38
+ {
39
+ "interpolated": "-s <% internet.subnet.internet %> -d <% internet.address %> -i <% internet.device %> -j in_peers"
40
+ }
41
+ ],
42
+ "jump-in_public": [
43
+ {
44
+ "comment": "_ Everything else"
45
+ },
46
+ {
47
+ "interpolated": "-d <% internet.address %> -i <% internet.device %> -j in_public"
48
+ }
49
+ ],
50
+ "log-drop": [
51
+ {
52
+ "comment": "_ Log and drop"
53
+ },
54
+ {
55
+ "ulog": ""
56
+ },
57
+ "-j DROP"
58
+ ],
59
+ "log-reject": [
60
+ {
61
+ "comment": "_ Log and reject"
62
+ },
63
+ {
64
+ "ulog": "-p tcp"
65
+ },
66
+ "-p tcp -j REJECT --reject-with tcp-reset"
67
+ ],
68
+ "return-iana": [
69
+ {
70
+ "comment": "_ IANA-reserved address spaces"
71
+ },
72
+ {
73
+ "interpolated": "-s <% iana_reserved %> -j RETURN"
74
+ }
75
+ ],
76
+ "return-martians": [
77
+ {
78
+ "comment": "Requests from networks that should never appear here"
79
+ },
80
+ {
81
+ "interpolated": "-s <% internet.subnet.vpn %> -j RETURN"
82
+ },
83
+ {
84
+ "interpolated": "-s <% internet.subnet.internet %> -j RETURN"
85
+ },
86
+ {
87
+ "interpolated": "-s <% storage.subnet %> -j RETURN",
88
+ "requires_primitive": "storage.address"
89
+ }
90
+ ]
91
+ }
92
+ }
@@ -0,0 +1,208 @@
1
+ {
2
+ "policy": {
3
+ "filter": {
4
+ "INPUT": {
5
+ "policy": "DROP",
6
+ "rules": [
7
+ {
8
+ "comment": "BEGIN: in-bound traffic"
9
+ },
10
+ "-i lo -j ACCEPT",
11
+ {
12
+ "node_addition_points": [
13
+ "INPUT"
14
+ ]
15
+ },
16
+ {
17
+ "macro": "accept-in_storage",
18
+ "requires_primitive": "storage.address"
19
+ },
20
+ {
21
+ "macro": "jump-in_peers"
22
+ },
23
+ {
24
+ "macro": "jump-in_public"
25
+ },
26
+ {
27
+ "macro": "accept-related"
28
+ },
29
+ {
30
+ "macro": "log-drop"
31
+ }
32
+ ]
33
+ },
34
+ "FORWARD": {
35
+ "policy": "DROP",
36
+ "rules": [
37
+ {
38
+ "comment": "BEGIN: forwarded traffic"
39
+ },
40
+ {
41
+ "node_addition_points": [
42
+ "FORWARD"
43
+ ]
44
+ },
45
+ {
46
+ "macro": "accept-related"
47
+ },
48
+ {
49
+ "macro": "log-drop"
50
+ }
51
+ ]
52
+ },
53
+ "OUTPUT": {
54
+ "policy": "ACCEPT"
55
+ },
56
+ "in_peers": {
57
+ "policy": "-",
58
+ "rules": [
59
+ {
60
+ "comment": "BEGIN: chain for my internet ip address on internet-facing network card from peer hosts"
61
+ },
62
+ {
63
+ "macro": "accept-established"
64
+ },
65
+ {
66
+ "service": "icmp"
67
+ },
68
+ {
69
+ "service": "ssh"
70
+ },
71
+ {
72
+ "node_addition_points": [
73
+ "in_peers",
74
+ "in_*"
75
+ ]
76
+ },
77
+ {
78
+ "macro": "accept-related"
79
+ },
80
+ {
81
+ "macro": "log-reject"
82
+ }
83
+ ]
84
+ },
85
+ "in_public": {
86
+ "policy": "-",
87
+ "rules": [
88
+ {
89
+ "comment": "BEGIN: chain for my internet ip address on internet-facing network card from internet hosts"
90
+ },
91
+ {
92
+ "macro": "return-iana"
93
+ },
94
+ {
95
+ "macro": "return-martians"
96
+ },
97
+ {
98
+ "macro": "accept-established"
99
+ },
100
+ {
101
+ "macro": "filter-attacks_and_probes"
102
+ },
103
+ {
104
+ "node_addition_points": [
105
+ "in_public",
106
+ "in_*"
107
+ ]
108
+ },
109
+ {
110
+ "macro": "accept-related"
111
+ },
112
+ {
113
+ "macro": "log-drop"
114
+ }
115
+ ]
116
+ },
117
+ "strict_fragments": {
118
+ "policy": "-",
119
+ "rules": [
120
+ {
121
+ "comment": "BEGIN: chain for handling packet fragments"
122
+ },
123
+ {
124
+ "ulog": ""
125
+ },
126
+ "-j DROP"
127
+ ]
128
+ },
129
+ "strict_icmpflood": {
130
+ "policy": "-",
131
+ "rules": [
132
+ {
133
+ "comment": "BEGIN: chain for handling icmp floods"
134
+ },
135
+ "-m limit --limit 100/sec --limit-burst 50 -j RETURN",
136
+ {
137
+ "ulog": ""
138
+ },
139
+ "-j DROP"
140
+ ]
141
+ },
142
+ "strict_malbad": {
143
+ "policy": "-",
144
+ "rules": [
145
+ {
146
+ "comment": "BEGIN: chain for handling malformed/bad packets"
147
+ },
148
+ {
149
+ "ulog": ""
150
+ },
151
+ "-j DROP"
152
+ ]
153
+ },
154
+ "strict_malnull": {
155
+ "policy": "-",
156
+ "rules": [
157
+ {
158
+ "comment": "BEGIN: chain for handling malformed/null packets"
159
+ },
160
+ {
161
+ "ulog": ""
162
+ },
163
+ "-j DROP"
164
+ ]
165
+ },
166
+ "strict_malxmas": {
167
+ "policy": "-",
168
+ "rules": [
169
+ {
170
+ "comment": "BEGIN: chain for handling malformed/xmas packets"
171
+ },
172
+ {
173
+ "ulog": ""
174
+ },
175
+ "-j DROP"
176
+ ]
177
+ },
178
+ "strict_nosyn": {
179
+ "policy": "-",
180
+ "rules": [
181
+ {
182
+ "comment": "BEGIN: chain for handling new TCP packets without SYN"
183
+ },
184
+ {
185
+ "ulog": ""
186
+ },
187
+ "-j DROP"
188
+ ]
189
+ },
190
+ "strict_synflood": {
191
+ "policy": "-",
192
+ "rules": [
193
+ {
194
+ "comment": "BEGIN: chain for handling Syn floods"
195
+ },
196
+ "-m limit --limit 100/sec --limit-burst 50 -j RETURN",
197
+ {
198
+ "ulog": ""
199
+ },
200
+ "-j DROP"
201
+ ]
202
+ }
203
+ },
204
+ "mangle": null,
205
+ "nat": null,
206
+ "raw": null
207
+ }
208
+ }