ript 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/.gitignore +6 -0
  2. data/.rbenv-version +1 -0
  3. data/AUTHORS.md +16 -0
  4. data/CHANGELOG.md +93 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +62 -0
  7. data/LICENCE +19 -0
  8. data/README.md +564 -0
  9. data/Rakefile +136 -0
  10. data/bin/rbenv-sudo +18 -0
  11. data/bin/ript +207 -0
  12. data/dist/init.d +48 -0
  13. data/examples/accept-multiple-from-and-to.rb +16 -0
  14. data/examples/accept-with-a-list-of-ports.rb +13 -0
  15. data/examples/accept-with-specific-port-and-interface.rb +14 -0
  16. data/examples/accept-without-specific-from.rb +11 -0
  17. data/examples/accept.rb +12 -0
  18. data/examples/basic.rb +4 -0
  19. data/examples/dash-in-partition-name.rb +2 -0
  20. data/examples/drop.rb +11 -0
  21. data/examples/duplicate-partition-names/foobar1.rb +2 -0
  22. data/examples/duplicate-partition-names/foobar2.rb +2 -0
  23. data/examples/errors-undefined-method-with-no-match.rb +12 -0
  24. data/examples/errors-undefined-method.rb +12 -0
  25. data/examples/forward-dnat-with-different-destination-port.rb +16 -0
  26. data/examples/forward-dnat-with-explicit-from-and-port-mappings.rb +11 -0
  27. data/examples/forward-dnat-with-explicit-from-and-ports.rb +11 -0
  28. data/examples/forward-dnat-with-explicit-from.rb +11 -0
  29. data/examples/forward-dnat-with-explicit-protocols.rb +15 -0
  30. data/examples/forward-dnat-with-multiple-froms.rb +13 -0
  31. data/examples/forward-dnat-with-multiple-ports.rb +10 -0
  32. data/examples/forward-dnat-with-multiple-sources.rb +15 -0
  33. data/examples/forward-dnat.rb +16 -0
  34. data/examples/forward-snat-with-explicit-from.rb +16 -0
  35. data/examples/forward-snat-with-multiple-sources.rb +13 -0
  36. data/examples/forward-snat.rb +9 -0
  37. data/examples/log-and-accept.rb +12 -0
  38. data/examples/log-and-drop.rb +11 -0
  39. data/examples/log-dnat.rb +10 -0
  40. data/examples/log-snat.rb +13 -0
  41. data/examples/log.rb +11 -0
  42. data/examples/missing-address-definition-in-destination.rb +15 -0
  43. data/examples/missing-address-definition-in-from.rb +15 -0
  44. data/examples/multiple-partitions-in-this-file.rb +14 -0
  45. data/examples/multiple-partitions/bar.rb +11 -0
  46. data/examples/multiple-partitions/foo.rb +17 -0
  47. data/examples/partition-name-exactly-20-characters.rb +2 -0
  48. data/examples/partition-name-longer-than-20-characters.rb +2 -0
  49. data/examples/postclean.rb +10 -0
  50. data/examples/preclean.rb +10 -0
  51. data/examples/raw-with-chain-deletion.rb +9 -0
  52. data/examples/raw-with-flush.rb +9 -0
  53. data/examples/raw.rb +50 -0
  54. data/examples/reject.rb +11 -0
  55. data/examples/space-in-partition-name.rb +2 -0
  56. data/features/cli.feature +115 -0
  57. data/features/dsl/errors.feature +107 -0
  58. data/features/dsl/filter.feature +187 -0
  59. data/features/dsl/logging.feature +114 -0
  60. data/features/dsl/nat.feature +271 -0
  61. data/features/dsl/raw.feature +28 -0
  62. data/features/setup.feature +58 -0
  63. data/features/step_definitions/cli_steps.rb +15 -0
  64. data/features/step_definitions/example_steps.rb +44 -0
  65. data/features/support/env.rb +25 -0
  66. data/lib/ript/bootstrap.rb +20 -0
  67. data/lib/ript/dsl.rb +14 -0
  68. data/lib/ript/dsl/primitives.rb +7 -0
  69. data/lib/ript/dsl/primitives/common.rb +78 -0
  70. data/lib/ript/dsl/primitives/filter.rb +145 -0
  71. data/lib/ript/dsl/primitives/nat.rb +206 -0
  72. data/lib/ript/dsl/primitives/raw.rb +45 -0
  73. data/lib/ript/exceptions.rb +2 -0
  74. data/lib/ript/partition.rb +162 -0
  75. data/lib/ript/patches.rb +10 -0
  76. data/lib/ript/rule.rb +70 -0
  77. data/lib/ript/version.rb +3 -0
  78. data/ript.gemspec +33 -0
  79. metadata +232 -0
@@ -0,0 +1,15 @@
1
+ Before("@timeout-10") do
2
+ @aruba_timeout_seconds = 10
3
+ end
4
+
5
+ Then /^the output from "([^"]*)" should match:$/ do |cmd, partial_output|
6
+ output_from(cmd).should =~ /#{partial_output}/
7
+ end
8
+
9
+ Then /^the output from "([^"]*)" should contain exactly:$/ do |cmd, exact_output|
10
+ output_from(cmd).should == exact_output
11
+ end
12
+
13
+ Given /^I have no iptables rules loaded$/ do
14
+ run_simple("rake clean_slate")
15
+ end
@@ -0,0 +1,44 @@
1
+ Before do
2
+ rules_src = File.join(File.dirname(__FILE__), '..', '..', 'examples', '.')
3
+ rules_dest = File.join(current_dir, 'examples')
4
+ FileUtils.mkdir_p(rules_dest)
5
+ FileUtils.cp_r(rules_src, rules_dest)
6
+ end
7
+
8
+ Then /^the created chain name in all tables should match$/ do
9
+ lines = all_output.split("\n")
10
+
11
+ lines.each do |line|
12
+ @chain_names ||= []
13
+ if line =~ /^# /
14
+ @chain_name = line[2..-1]
15
+ @chain_names = ['s', 'd', 'a'].map { |table| client, hash = @chain_name.split(/-/); "#{client}-#{table}#{hash}" }
16
+ end
17
+
18
+ next if line.size == 0
19
+ next if line =~ /--(new-chain|jump) partition-/
20
+ next if line =~ /--(new-chain|jump) ript_bootstrap-/
21
+
22
+ line.should match(%r{(^\# #{@chain_name})|(#{@chain_names.join('|')})}) if line !~ /LOG/
23
+ end
24
+ end
25
+
26
+ When /^I generate rules for packet filtering$/ do
27
+ examples_path = Pathname.new(__FILE__).parent.parent.parent.join('examples')
28
+
29
+ examples = Dir.glob("#{examples_path}/{accept,drop,reject,log}*.rb")
30
+ examples.each do |example|
31
+ run_simple("ript rules generate #{example}")
32
+ commands = all_output.split("\n").find_all {|line| line =~ /^iptables/ }
33
+
34
+ @all_outputs ||= []
35
+ @all_outputs += commands
36
+ end
37
+ end
38
+
39
+ Then /^I should see a protocol specified when a port is specified$/ do
40
+ dports = @all_outputs.find_all {|line| line =~ /dport/}
41
+ dports.each do |command|
42
+ command.should =~ / --protocol /
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+ require 'aruba/cucumber'
6
+ require 'colorize'
7
+
8
+ if Process.uid != 0
9
+ puts "You need to be root to run these tests!"
10
+ abort
11
+ end
12
+
13
+ def clean_slate_after_2_minutes
14
+ root = Pathname.new(__FILE__).parent.parent.parent
15
+ path = ENV['PATH']
16
+
17
+ clean_command = "export PATH=#{path} && echo 'cd #{root} && rake clean_slate'"
18
+ at_command = "at 'now + 2 minutes' >/dev/null 2>&1"
19
+ command = "#{clean_command} | #{at_command}"
20
+
21
+ puts "If these tests lock you out, all iptables rules will be flushed in 2 minutes.\n".yellow
22
+ system(command)
23
+ end
24
+
25
+ clean_slate_after_2_minutes
@@ -0,0 +1,20 @@
1
+ module Ript
2
+ class Bootstrap
3
+ def self.partition
4
+ rules = []
5
+
6
+ rules << Rule.new("table" => "filter", "new-chain" => "partition-a")
7
+ rules << Rule.new("table" => "filter", "insert" => "INPUT 1", "jump" => "partition-a")
8
+ rules << Rule.new("table" => "filter", "insert" => "OUTPUT 1", "jump" => "partition-a")
9
+ rules << Rule.new("table" => "filter", "insert" => "FORWARD 1", "jump" => "partition-a")
10
+
11
+ rules << Rule.new("table" => "nat", "new-chain" => "partition-d")
12
+ rules << Rule.new("table" => "nat", "insert" => "PREROUTING 1", "jump" => "partition-d")
13
+
14
+ rules << Rule.new("table" => "nat", "new-chain" => "partition-s")
15
+ rules << Rule.new("table" => "nat", "insert" => "POSTROUTING 1", "jump" => "partition-s")
16
+
17
+ Partition.new('ript_bootstrap', nil, :rules => rules)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if RUBY_VERSION =~ /^1.8/ then
4
+ puts "Ript requires Ruby 1.9 to run. Exiting."
5
+ exit 2
6
+ end
7
+
8
+ $: << Pathname.new(__FILE__).dirname.parent.expand_path.to_s
9
+
10
+ require 'ript/dsl/primitives'
11
+ require 'ript/rule'
12
+ require 'ript/partition'
13
+ require 'ript/exceptions'
14
+ require 'ript/patches'
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ $: << Pathname.new(__FILE__).dirname.parent.parent.expand_path.to_s
3
+
4
+ require 'ript/dsl/primitives/common'
5
+ require 'ript/dsl/primitives/nat'
6
+ require 'ript/dsl/primitives/filter'
7
+ require 'ript/dsl/primitives/raw'
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Ript
4
+ module DSL
5
+ module Primitives
6
+ module Common
7
+ def label(label, opts={})
8
+ @labels[label] = opts
9
+ end
10
+
11
+ def interface(arg)
12
+ @interface = arg
13
+ end
14
+
15
+ def ports(*args)
16
+ if args.class == Array
17
+ args.each do |port|
18
+ if port.class == Range
19
+ @ports << "#{port.begin}:#{port.end}"
20
+ else
21
+ @ports << port
22
+ end
23
+ end
24
+ else
25
+ port = args
26
+ @ports << port
27
+ end
28
+ end
29
+
30
+ def from(*label)
31
+ label.flatten!(2)
32
+ if label.is_a?(Array)
33
+ label.each do |l|
34
+ @froms << l
35
+ end
36
+ else
37
+ @froms << label
38
+ end
39
+ end
40
+
41
+ def to(*label)
42
+ label.flatten!(2)
43
+ if label.is_a?(Array)
44
+ label.each do |l|
45
+ @tos << l
46
+ end
47
+ else
48
+ @tos << label
49
+ end
50
+ end
51
+
52
+ def protocols(*args)
53
+ # FIXME: refactor to just use flatten!
54
+ if args.class == Array
55
+ args.each do |protocol|
56
+ @protocols << protocol
57
+ end
58
+ else
59
+ protocol = args
60
+ @protocols << protocol
61
+ end
62
+ end
63
+
64
+ def validate(opts={})
65
+ opts.each_pair do |type, label|
66
+ if not label_exists?(label)
67
+ raise LabelError, "Address '#{label}' (a #{type}) isn't defined"
68
+ end
69
+ end
70
+ end
71
+
72
+ def label_exists?(label)
73
+ @labels.has_key?(label)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Ript
4
+ module DSL
5
+ module Primitives
6
+ module Filter
7
+ # Accept traffic to/from a destination/source.
8
+ #
9
+ # This allows traffic for a particular port/protocol to be passed into
10
+ # userland on the local machine.
11
+ def accept(name, opts={}, &block)
12
+ opts.merge!(:jump => "ACCEPT")
13
+ build_rule(name, block, opts)
14
+ end
15
+
16
+ # Reject traffic to/from a destination/source.
17
+ #
18
+ # Send an error packet back for traffic that matches.
19
+ def reject(name, opts={}, &block)
20
+ opts.merge!(:jump => "REJECT")
21
+ build_rule(name, block, opts)
22
+ end
23
+
24
+ # Drop traffic to/from a destination/source.
25
+ #
26
+ # Silently drop packets that match.
27
+ def drop(name, opts={}, &block)
28
+ opts.merge!(:jump => "DROP")
29
+ build_rule(name, block, opts)
30
+ end
31
+
32
+ # Log traffic to/from a destination/source.
33
+ #
34
+ # Log packets that match via the kernel log (read with dmesg or syslog).
35
+ def log(name, opts={}, &block)
36
+ opts.merge!(:jump => "LOG")
37
+ build_rule(name, block, opts)
38
+ end
39
+
40
+ private
41
+ # Construct a rule to be applied to the `filter` table.
42
+ #
43
+ # This method is used to construct simple rules on the filter table to
44
+ # accept/reject/drop/log traffic to and from various addresses.
45
+ #
46
+ # Accepts a block of the actual rule definition to evaluate, and
47
+ # appends the generated rule to the @table instance variable on the
48
+ # partition instance.
49
+ #
50
+ # This method returns nothing.
51
+ #
52
+ def build_rule(name, block, opts={})
53
+ @froms = []
54
+ @tos = []
55
+ @ports = []
56
+ @protocols = []
57
+ insert = opts[:insert] || "partition-a"
58
+ jump = opts[:jump] || "DROP"
59
+ log = opts[:log]
60
+
61
+ # Evaluate the block.
62
+ instance_eval &block
63
+
64
+ # Default all rules to apply to TCP packets if no protocol is specified
65
+ @protocols << 'TCP' if @protocols.size == 0
66
+
67
+ @protocols.map! {|protocol| {"protocol" => protocol} }
68
+ @ports.map! {|port| {"dport" => port} }
69
+
70
+ # Provide a default from address, so the @ports => @protocols => @froms
71
+ # nested iteration below works.
72
+ @froms << 'all' if @froms.size == 0
73
+
74
+ @froms.each do |from|
75
+ @tos.each do |to|
76
+ validate(:from => from, :to => to)
77
+
78
+ from_address = @labels[from][:address]
79
+ to_address = @labels[to][:address]
80
+
81
+ attributes = {
82
+ "table" => "filter",
83
+ "insert" => insert,
84
+ "destination" => to_address,
85
+ "jump" => "#{@name}-a",
86
+ }
87
+ @input << Rule.new(attributes)
88
+ @input << Rule.new(attributes.merge("jump" => "LOG")) if log
89
+
90
+ attributes = {
91
+ "table" => "filter",
92
+ "append" => "#{@name}-a",
93
+ "destination" => to_address,
94
+ "source" => from_address,
95
+ "jump" => jump
96
+ }
97
+ attributes.insert_before("destination", "in-interface" => @interface) if @interface
98
+
99
+ # Build up a list of arguments we need to build expanded rules.
100
+ #
101
+ # This allows us to expand shorthand definitions like:
102
+ #
103
+ # accept "multiple rules in one" do
104
+ # from "foo", "bar", "baz"
105
+ # to "spoons"
106
+ # end
107
+ #
108
+ # ... into multiple rules, one ACCEPT rule for foo, bar, baz.
109
+ #
110
+ case
111
+ when @ports.size > 0 && @protocols.size > 0
112
+ # build the rules based on the arguments supplied
113
+ arguments = @protocols.product(@ports).map {|ary| ary.inject(:merge) }
114
+ when @ports.size == 0 && @protocols.size > 0
115
+ arguments = @protocols
116
+ when @protocols.size == 0 && @ports.size > 0
117
+ arguments = @ports
118
+ else
119
+ arguments = []
120
+ end
121
+
122
+ # If we have arguments, iterate through them
123
+ if arguments.size > 0
124
+ arguments.each do |options|
125
+ options.each_pair do |key, value|
126
+ attributes = attributes.dup # avoid overwriting existing hash values from previous iterations
127
+ attributes.insert_before("destination", key => value)
128
+ end
129
+
130
+ @table << Rule.new(attributes.merge("jump" => "LOG")) if log
131
+ @table << Rule.new(attributes)
132
+ end
133
+ else
134
+ @table << Rule.new(attributes.merge("jump" => "LOG")) if log
135
+ @table << Rule.new(attributes)
136
+ end # if
137
+ end # @tos.each
138
+ end # @froms.each
139
+
140
+ end # def build_rule
141
+ end
142
+ end
143
+ end
144
+ end
145
+
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Ript
4
+ module DSL
5
+ module Primitives
6
+ module NAT
7
+ def rewrite(name, opts={}, &block)
8
+ # Reset these so parameters don't leak between calls to forward.
9
+ @sources = []
10
+ @destinations = []
11
+ @ports = []
12
+ @protocols = []
13
+ @tos = []
14
+ @froms = []
15
+ log = opts[:log]
16
+
17
+ @snat_sources = []
18
+ @snat_destinations = []
19
+
20
+ # Evaluate the block.
21
+ instance_eval &block
22
+
23
+ # Default all rules to apply to TCP packets if no protocol is specified
24
+ @protocols << 'TCP' if @protocols.size == 0
25
+
26
+ @snat_sources.zip(@snat_destinations) do |source, destination|
27
+ validate(:source => source, :destination => destination)
28
+
29
+ source_address = @labels[source][:address]
30
+ destination_address = @labels[destination][:address]
31
+
32
+ attributes = { "table" => "nat",
33
+ "insert" => "partition-s",
34
+ "source" => source_address,
35
+ "jump" => "#{@name}-s" }
36
+
37
+ fattributes = { "table" => "filter",
38
+ "insert" => "partition-a",
39
+ "source" => source_address,
40
+ "jump" => "#{@name}-a" }
41
+
42
+ @postrouting << Rule.new(attributes)
43
+ @postrouting << Rule.new(attributes.merge("jump" => "LOG")) if log
44
+ @input << Rule.new(fattributes)
45
+ @input << Rule.new(fattributes.merge("jump" => "LOG")) if log
46
+
47
+
48
+ attributes = { "table" => "nat",
49
+ "append" => "#{@name}-s",
50
+ "source" => source_address,
51
+ "jump" => "SNAT",
52
+ "to-source" => destination_address }
53
+
54
+ fattributes = { "table" => "filter",
55
+ "append" => "#{@name}-a",
56
+ "source" => source_address,
57
+ "jump" => "ACCEPT" }
58
+
59
+ @froms.map {|from| @labels[from][:address]}.each do |address|
60
+ attributes.insert_before("destination", "source" => address)
61
+ end
62
+
63
+ @table << Rule.new(attributes.merge("jump" => "LOG")) if log
64
+ @table << Rule.new(attributes)
65
+ @table << Rule.new(fattributes.merge("jump" => "LOG")) if log
66
+ @table << Rule.new(fattributes)
67
+ end
68
+
69
+
70
+ # Provide a default from address, so the @ports => @protocols => @froms
71
+ # nested iteration below works.
72
+ @froms << 'all' if @froms.size == 0
73
+
74
+ # Build up rules based on evaluation.
75
+ @sources.zip(@destinations).each do |source, destination|
76
+ validate(:source => source, :destination => destination)
77
+
78
+ source_address = @labels[source][:address]
79
+ destination_address = @labels[destination][:address]
80
+
81
+ attributes = { "table" => "nat",
82
+ "insert" => "partition-d",
83
+ "destination" => source_address,
84
+ "jump" => "#{@name}-d" }
85
+
86
+ fattributes = { "table" => "filter",
87
+ "insert" => "partition-a",
88
+ "destination" => destination_address,
89
+ "jump" => "#{@name}-a" }
90
+
91
+ @prerouting << Rule.new(attributes)
92
+ @prerouting << Rule.new(attributes.merge("jump" => "LOG")) if log
93
+ @input << Rule.new(fattributes)
94
+ @input << Rule.new(fattributes.merge("jump" => "LOG")) if log
95
+
96
+ @ports.each do |port|
97
+ @protocols.each do |protocol|
98
+ @froms.map {|from| @labels[from][:address]}.each do |from_address|
99
+ if port.class == Hash
100
+ port.each_pair do |source_port, destination_port|
101
+ attributes = { "table" => "nat",
102
+ "append" => "#{@name}-d",
103
+ "protocol" => protocol,
104
+ "destination" => source_address,
105
+ "dport" => source_port,
106
+ "jump" => "DNAT",
107
+ "to-destination" => destination_address + ":#{destination_port}" }
108
+
109
+ fattributes = { "table" => "filter",
110
+ "append" => "#{@name}-a",
111
+ "protocol" => protocol,
112
+ "destination" => destination_address,
113
+ "dport" => destination_port,
114
+ "jump" => "ACCEPT" }
115
+
116
+ attributes.insert_before("destination", "source" => from_address) unless from_address == "0.0.0.0/0"
117
+
118
+ @table << Rule.new(attributes.merge("jump" => "LOG")) if log
119
+ @table << Rule.new(attributes)
120
+ @table << Rule.new(fattributes.merge("jump" => "LOG")) if log
121
+ @table << Rule.new(fattributes)
122
+ end
123
+ else
124
+ attributes = { "table" => "nat",
125
+ "append" => "#{@name}-d",
126
+ "protocol" => protocol,
127
+ "destination" => source_address,
128
+ "dport" => port,
129
+ "jump" => "DNAT",
130
+ "to-destination" => destination_address }
131
+
132
+ fattributes = { "table" => "filter",
133
+ "append" => "#{@name}-a",
134
+ "protocol" => protocol,
135
+ "destination" => destination_address,
136
+ "dport" => port,
137
+ "jump" => "ACCEPT" }
138
+
139
+ attributes.insert_before("destination", "source" => from_address) unless from_address == "0.0.0.0/0"
140
+
141
+ @table << Rule.new(attributes.merge("jump" => "LOG")) if log
142
+ @table << Rule.new(attributes)
143
+ @table << Rule.new(fattributes.merge("jump" => "LOG")) if log
144
+ @table << Rule.new(fattributes)
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ def dnat(opts={})
153
+ opts.each_pair do |source, destination|
154
+ # If the source argument to dnat is an Array:
155
+ #
156
+ # dnat [ "www.bar.com",
157
+ # "secure.bar.com",
158
+ # "static.bar.com" ] => "barprod-proxy-01"
159
+ #
160
+ # loop through each source, and create the associated destination.
161
+ if source.is_a?(Array)
162
+ source.each do |s|
163
+ @sources << s
164
+ @destinations << destination
165
+ end
166
+ # If the source is just a plain label:
167
+ #
168
+ # dnat "www.bar.com" => "barprod-proxy-01"
169
+ #
170
+ # simply add it and the destination to the collection.
171
+ else
172
+ @sources << source
173
+ @destinations << destination
174
+ end
175
+ end
176
+ end
177
+
178
+ def snat(opts={})
179
+ opts.each_pair do |source, destination|
180
+ # If the source argument to snat is an Array:
181
+ #
182
+ # snat [ "www.bar.com",
183
+ # "secure.bar.com",
184
+ # "static.bar.com" ] => "barprod-proxy-01"
185
+ #
186
+ # loop through each source, and create the associated destination.
187
+ if source.is_a?(Array)
188
+ source.each do |s|
189
+ @snat_sources << s
190
+ @snat_destinations << destination
191
+ end
192
+ # If the source is just a plain label:
193
+ #
194
+ # snat "www.bar.com" => "barprod-proxy-01"
195
+ #
196
+ # simply add it and the destination to the collection.
197
+ else
198
+ @snat_sources << source
199
+ @snat_destinations << destination
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end