ript 0.8.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.
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