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,45 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Ript
4
+ module DSL
5
+ module Primitives
6
+ module Raw
7
+
8
+ def raw?
9
+ @raw
10
+ end
11
+
12
+ def raw(rules)
13
+ @raw = true
14
+ commands = rules.split("\n").reject {|l| l !~ /^\s*[^#]+$/}
15
+
16
+ commands.each do |command|
17
+ validate_destructiveness(command)
18
+
19
+ attributes = {:raw => command}
20
+
21
+ @table << Rule.new(attributes)
22
+ end
23
+ end
24
+
25
+ private
26
+ def validate_destructiveness(command)
27
+ if command =~ /(\-F|\-\-flush)/
28
+ puts "Error: partition #{@name} - you can't use raw rules that flush tables or chains!"
29
+ puts "Offending rule:\n\n #{command}\n\n"
30
+ puts "Exiting."
31
+ exit 140
32
+ end
33
+
34
+ if command =~ /\s+(\-X|\-\-delete-chain)/
35
+ puts "Error: partition #{@name} - you can't use raw rules that delete chains!"
36
+ puts "Offending rule:\n\n #{command}\n\n"
37
+ puts "Exiting."
38
+ exit 140
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
@@ -0,0 +1,2 @@
1
+ class LabelError < NameError
2
+ end
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << Pathname.new(__FILE__).dirname.parent.expand_path.to_s
4
+
5
+ module Ript
6
+ class Partition
7
+ attr_reader :name, :filename, :line
8
+
9
+ include DSL::Primitives::Common
10
+ include DSL::Primitives::NAT
11
+ include DSL::Primitives::Filter
12
+ include DSL::Primitives::Raw
13
+
14
+ def initialize(name, block, options={})
15
+ @filename, @line = caller[2].split(':')[0..1]
16
+ @labels = {}
17
+ @prerouting = []
18
+ @postrouting = []
19
+ @input = []
20
+ @forward = []
21
+ @table = []
22
+ @name = name
23
+ # TODO should we rename this to no_is or something since that is what it really means
24
+ if options[:rules]
25
+ @raw = true
26
+ @table = options[:rules]
27
+ end
28
+
29
+ # Even when suplying our own rules we need the placeholders below to know if anything changed
30
+ @setup = []
31
+ @setup << Rule.new("table" => "nat", "new-chain" => "#{@name}-d")
32
+ @setup << Rule.new("table" => "nat", "new-chain" => "#{@name}-s")
33
+ @setup << Rule.new("table" => "filter", "new-chain" => "#{@name}-a")
34
+
35
+ # Provide a label for the zero-address
36
+ label "all", :address => "0.0.0.0/0"
37
+
38
+ begin
39
+ instance_eval &block unless block.nil?
40
+ rescue NoMethodError => e
41
+ method = e.message[/`(.+)'/, 1]
42
+ filename, line = e.backtrace.first[/(.*):(\d)/].split(':')
43
+ if filename =~ /\/lib\/ript\//
44
+ puts "Looks like you found a bug in Ript around line #{line} in #{filename}"
45
+ puts "Specifically, this is the exception raised:"
46
+ puts
47
+ puts " #{e.message}"
48
+ puts
49
+ puts "And here is the backtrace:"
50
+ puts
51
+ puts e.backtrace.map {|l| " #{l}\n" }.join
52
+ puts
53
+ puts "Please report this bug at http://github.com/bulletproofnetworks/ript"
54
+ puts
55
+ else
56
+ puts "You tried using the '#{method}' method on line #{line} in #{filename}"
57
+ similar = self.class.instance_methods.grep(/#{method}/)
58
+ if similar.size > 0
59
+ puts "This method doesn't exist in the DSL. Did you mean:"
60
+ puts
61
+ self.class.instance_methods.grep(/#{method}/).each do |m|
62
+ puts " - #{m}"
63
+ end
64
+ puts
65
+ else
66
+ puts "This method doesn't exist in the DSL. There aren't any other methods with similar names. :-("
67
+ end
68
+ end
69
+ puts "Aborting."
70
+ exit 131
71
+ rescue LabelError => e
72
+ puts e.message
73
+ puts "Aborting."
74
+ exit 131
75
+ end
76
+ end
77
+
78
+ # FIXME: Maybe implement the concept of dirtiness?
79
+ def id
80
+ return @id if @id
81
+ joined = (@setup.map {|rule| rule.to_iptables } +
82
+ @prerouting.map {|rule| rule.to_iptables }.uniq +
83
+ @postrouting.map {|rule| rule.to_iptables }.uniq +
84
+ @input.map {|rule| rule.to_iptables }.uniq +
85
+ @forward.map {|rule| rule.to_iptables }.uniq +
86
+ @table.map {|rule| rule.to_iptables }.uniq).join(' ')
87
+ @id = "#{Digest::MD5.hexdigest(joined)[0..5]}"
88
+ end
89
+
90
+ def update_id(object, key, id)
91
+ object.map { |rule|
92
+ rule[key] += "#{id}" unless rule[key] == "LOG"
93
+ rule.to_iptables
94
+ }
95
+ end
96
+
97
+ def to_iptables
98
+ if raw?
99
+ # TODO How do we clean up raw rules?
100
+ puts update_id(@setup, "new-chain", id).uniq
101
+ puts @table.map {|rule| rule.to_iptables }
102
+ puts
103
+ else
104
+ puts update_id(@setup, "new-chain", id).uniq
105
+ puts update_id(@table, "append", id).uniq
106
+ puts update_id(@prerouting, "jump", id).uniq
107
+ puts update_id(@postrouting, "jump", id).uniq
108
+ puts update_id(@input, "jump", id).uniq
109
+ puts update_id(@forward, "jump", id).uniq
110
+ puts
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ @partitions = []
117
+ @filenames = []
118
+
119
+ def partition(name, &block)
120
+ filename, line = caller.first.split(':')[0..1]
121
+
122
+ if c = @partitions.find {|c| c.name == name } then
123
+ puts "Error: Partition name '#{name}' is already defined!"
124
+ puts " - existing definition: #{c.filename}:#{c.line}"
125
+ puts " - new definition: #{filename}:#{line}"
126
+ puts "Aborting."
127
+ exit 140
128
+ end
129
+
130
+ if name =~ /\s+/
131
+ puts "Error: #{filename}:#{line}"
132
+ puts "Error: Partition name '#{name}' can't contain whitespace."
133
+ puts "Aborting."
134
+ exit 140
135
+ end
136
+
137
+ if name.count('-') > 0
138
+ puts "Error: #{filename}:#{line}"
139
+ puts "Error: Partition name '#{name}' can't contain dashes ('-')."
140
+ puts "Aborting."
141
+ exit 140
142
+ end
143
+
144
+ if name.length > 20
145
+ puts "Error: #{filename}:#{line}"
146
+ puts "Error: Partition name '#{name}' cannot be longer than 20 characters."
147
+ puts "Aborting."
148
+ exit 140
149
+ end
150
+
151
+ if @filenames.include?(filename)
152
+ puts "Error: #{filename}:#{line}"
153
+ puts "Error: Multiple partition definitions are not permitted in the same file."
154
+ puts "Aborting."
155
+ exit 140
156
+ else
157
+ @filenames << filename
158
+ end
159
+
160
+ partition = Ript::Partition.new(name, block)
161
+ @partitions << partition
162
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ class Hash
4
+ def insert_before(key, opts={})
5
+ before = self.dup.take_while {|k, v| k != key }
6
+ after = self.dup.drop_while {|k, v| k != key }
7
+ before << opts.to_a.flatten
8
+ self.clear.merge!(Hash[before]).merge!(Hash[after])
9
+ end
10
+ end
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << Pathname.new(__FILE__).dirname.parent.expand_path.to_s
4
+
5
+ require 'digest/md5'
6
+
7
+ module Ript
8
+ class Rule
9
+ def initialize(opts={})
10
+ @comment = opts.delete(:comment)
11
+ @raw = opts.delete(:raw)
12
+ @args = []
13
+ @opts = opts
14
+ end
15
+
16
+ def [](key)
17
+ @opts[key]
18
+ end
19
+
20
+ def []=(key, value)
21
+ @opts[key] = value
22
+ end
23
+
24
+ def add_option(argument, parameter)
25
+ @args << "--#{argument} #{parameter}"
26
+ end
27
+
28
+ def to_iptables
29
+ @args.clear
30
+ @opts.each_pair do |argument, parameter|
31
+ add_option(argument, parameter)
32
+ end
33
+
34
+ if comment?
35
+ "#{self.to_command} #{self.comment}"
36
+ else
37
+ self.to_command
38
+ end
39
+ end
40
+
41
+ def raw?
42
+ @raw
43
+ end
44
+
45
+ def to_command
46
+ if raw?
47
+ @raw
48
+ else
49
+ "iptables #{@args.join(' ')}"
50
+ end
51
+ end
52
+
53
+ def comment
54
+ "--match comment --comment '#{id}'"
55
+ end
56
+
57
+ def id
58
+ Digest::MD5.hexdigest(self.to_command)
59
+ end
60
+
61
+ def comment?
62
+ @comment
63
+ end
64
+
65
+ # Display the rule in iptables form, perferably before an update_id has been run
66
+ def to_s
67
+ %("#{to_command}")
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,3 @@
1
+ module Ript
2
+ VERSION = '0.8.4'
3
+ end
@@ -0,0 +1,33 @@
1
+ #
2
+ # -*- encoding: utf-8 -*-
3
+ $:.push File.expand_path("../lib", __FILE__)
4
+ require "ript/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "ript"
8
+ s.version = Ript::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = [ "Lindsay Holmwood" ]
11
+ s.email = [ "lindsay@bulletproof.net" ]
12
+ s.homepage = "http://bulletproof.net/"
13
+ s.summary = %q{DSL for iptables, and tool for incrementally applying firewall rules}
14
+ s.description = %q{Ript provides a clean Ruby DSL for describing firewall rules, and implements database migrations-like functionality for applying the rules}
15
+
16
+ s.rubyforge_project = "ript"
17
+
18
+ s.required_ruby_version = ">= 1.9.2"
19
+ s.required_rubygems_version = ">= 1.3.6"
20
+
21
+ s.files = `git ls-files`.split("\n")
22
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
24
+ s.require_paths = ["lib"]
25
+
26
+ #s.add_runtime_dependency "colorize", ">= 0"
27
+ s.add_development_dependency "rake", ">= 0"
28
+ s.add_development_dependency "rspec", ">= 0"
29
+ s.add_development_dependency "cucumber", ">= 1.1.9"
30
+ s.add_development_dependency "aruba", ">= 0"
31
+ s.add_development_dependency "colorize", ">= 0"
32
+ s.add_development_dependency "fpm", ">= 0.4.5"
33
+ end
metadata ADDED
@@ -0,0 +1,232 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ript
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.4
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Lindsay Holmwood
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: cucumber
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: 1.1.9
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.1.9
62
+ - !ruby/object:Gem::Dependency
63
+ name: aruba
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: colorize
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: fpm
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: 0.4.5
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: 0.4.5
110
+ description: Ript provides a clean Ruby DSL for describing firewall rules, and implements
111
+ database migrations-like functionality for applying the rules
112
+ email:
113
+ - lindsay@bulletproof.net
114
+ executables:
115
+ - rbenv-sudo
116
+ - ript
117
+ extensions: []
118
+ extra_rdoc_files: []
119
+ files:
120
+ - .gitignore
121
+ - .rbenv-version
122
+ - AUTHORS.md
123
+ - CHANGELOG.md
124
+ - Gemfile
125
+ - Gemfile.lock
126
+ - LICENCE
127
+ - README.md
128
+ - Rakefile
129
+ - bin/rbenv-sudo
130
+ - bin/ript
131
+ - dist/init.d
132
+ - examples/accept-multiple-from-and-to.rb
133
+ - examples/accept-with-a-list-of-ports.rb
134
+ - examples/accept-with-specific-port-and-interface.rb
135
+ - examples/accept-without-specific-from.rb
136
+ - examples/accept.rb
137
+ - examples/basic.rb
138
+ - examples/dash-in-partition-name.rb
139
+ - examples/drop.rb
140
+ - examples/duplicate-partition-names/foobar1.rb
141
+ - examples/duplicate-partition-names/foobar2.rb
142
+ - examples/errors-undefined-method-with-no-match.rb
143
+ - examples/errors-undefined-method.rb
144
+ - examples/forward-dnat-with-different-destination-port.rb
145
+ - examples/forward-dnat-with-explicit-from-and-port-mappings.rb
146
+ - examples/forward-dnat-with-explicit-from-and-ports.rb
147
+ - examples/forward-dnat-with-explicit-from.rb
148
+ - examples/forward-dnat-with-explicit-protocols.rb
149
+ - examples/forward-dnat-with-multiple-froms.rb
150
+ - examples/forward-dnat-with-multiple-ports.rb
151
+ - examples/forward-dnat-with-multiple-sources.rb
152
+ - examples/forward-dnat.rb
153
+ - examples/forward-snat-with-explicit-from.rb
154
+ - examples/forward-snat-with-multiple-sources.rb
155
+ - examples/forward-snat.rb
156
+ - examples/log-and-accept.rb
157
+ - examples/log-and-drop.rb
158
+ - examples/log-dnat.rb
159
+ - examples/log-snat.rb
160
+ - examples/log.rb
161
+ - examples/missing-address-definition-in-destination.rb
162
+ - examples/missing-address-definition-in-from.rb
163
+ - examples/multiple-partitions-in-this-file.rb
164
+ - examples/multiple-partitions/bar.rb
165
+ - examples/multiple-partitions/foo.rb
166
+ - examples/partition-name-exactly-20-characters.rb
167
+ - examples/partition-name-longer-than-20-characters.rb
168
+ - examples/postclean.rb
169
+ - examples/preclean.rb
170
+ - examples/raw-with-chain-deletion.rb
171
+ - examples/raw-with-flush.rb
172
+ - examples/raw.rb
173
+ - examples/reject.rb
174
+ - examples/space-in-partition-name.rb
175
+ - features/cli.feature
176
+ - features/dsl/errors.feature
177
+ - features/dsl/filter.feature
178
+ - features/dsl/logging.feature
179
+ - features/dsl/nat.feature
180
+ - features/dsl/raw.feature
181
+ - features/setup.feature
182
+ - features/step_definitions/cli_steps.rb
183
+ - features/step_definitions/example_steps.rb
184
+ - features/support/env.rb
185
+ - lib/ript/bootstrap.rb
186
+ - lib/ript/dsl.rb
187
+ - lib/ript/dsl/primitives.rb
188
+ - lib/ript/dsl/primitives/common.rb
189
+ - lib/ript/dsl/primitives/filter.rb
190
+ - lib/ript/dsl/primitives/nat.rb
191
+ - lib/ript/dsl/primitives/raw.rb
192
+ - lib/ript/exceptions.rb
193
+ - lib/ript/partition.rb
194
+ - lib/ript/patches.rb
195
+ - lib/ript/rule.rb
196
+ - lib/ript/version.rb
197
+ - ript.gemspec
198
+ homepage: http://bulletproof.net/
199
+ licenses: []
200
+ post_install_message:
201
+ rdoc_options: []
202
+ require_paths:
203
+ - lib
204
+ required_ruby_version: !ruby/object:Gem::Requirement
205
+ none: false
206
+ requirements:
207
+ - - ! '>='
208
+ - !ruby/object:Gem::Version
209
+ version: 1.9.2
210
+ required_rubygems_version: !ruby/object:Gem::Requirement
211
+ none: false
212
+ requirements:
213
+ - - ! '>='
214
+ - !ruby/object:Gem::Version
215
+ version: 1.3.6
216
+ requirements: []
217
+ rubyforge_project: ript
218
+ rubygems_version: 1.8.23
219
+ signing_key:
220
+ specification_version: 3
221
+ summary: DSL for iptables, and tool for incrementally applying firewall rules
222
+ test_files:
223
+ - features/cli.feature
224
+ - features/dsl/errors.feature
225
+ - features/dsl/filter.feature
226
+ - features/dsl/logging.feature
227
+ - features/dsl/nat.feature
228
+ - features/dsl/raw.feature
229
+ - features/setup.feature
230
+ - features/step_definitions/cli_steps.rb
231
+ - features/step_definitions/example_steps.rb
232
+ - features/support/env.rb