ipscriptables 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.rubocop.yml +15 -0
  4. data/.travis.yml +10 -0
  5. data/CHANGELOG.md +6 -0
  6. data/CONTRIBUTING.md +43 -0
  7. data/Gemfile +13 -0
  8. data/LICENSE +20 -0
  9. data/README.md +54 -0
  10. data/Rakefile +22 -0
  11. data/bin/ipscriptables +6 -0
  12. data/cookbook/.gitignore +2 -0
  13. data/cookbook/.kitchen.yml +28 -0
  14. data/cookbook/Berksfile +6 -0
  15. data/cookbook/README.md +53 -0
  16. data/cookbook/attributes/default.rb +3 -0
  17. data/cookbook/chefignore +96 -0
  18. data/cookbook/libraries/default.rb +35 -0
  19. data/cookbook/metadata.rb +9 -0
  20. data/cookbook/providers/rules.rb +21 -0
  21. data/cookbook/recipes/default.rb +10 -0
  22. data/cookbook/recipes/load.rb +8 -0
  23. data/cookbook/resources/rules.rb +17 -0
  24. data/cookbook/test/cookbooks/ipscriptables-test/#metadata.rb# +8 -0
  25. data/cookbook/test/cookbooks/ipscriptables-test/metadata.rb +11 -0
  26. data/cookbook/test/cookbooks/ipscriptables-test/recipes/default.rb +23 -0
  27. data/cookbook/test/cookbooks/ipscriptables-test/recipes/prepare.rb +5 -0
  28. data/cookbook/test/data/.gitignore +1 -0
  29. data/cookbook/test/integration/default/bats/default.bats +9 -0
  30. data/doc/iptables-switches.txt +342 -0
  31. data/ipscriptables.gemspec +38 -0
  32. data/lib/ipscriptables.rb +14 -0
  33. data/lib/ipscriptables/chain.rb +83 -0
  34. data/lib/ipscriptables/cli.rb +19 -0
  35. data/lib/ipscriptables/helpers.rb +39 -0
  36. data/lib/ipscriptables/pretty_print.rb +58 -0
  37. data/lib/ipscriptables/rule.rb +95 -0
  38. data/lib/ipscriptables/ruleset.rb +103 -0
  39. data/lib/ipscriptables/ruleset/class_methods.rb +67 -0
  40. data/lib/ipscriptables/runtime.rb +97 -0
  41. data/lib/ipscriptables/table.rb +77 -0
  42. data/lib/ipscriptables/version.rb +5 -0
  43. data/spec/fixtures/clyhq.txt +40 -0
  44. data/spec/fixtures/docker-plus.txt +31 -0
  45. data/spec/fixtures/drumknott.txt +67 -0
  46. data/spec/fixtures/falcor.txt +39 -0
  47. data/spec/fixtures/ghq.txt +102 -0
  48. data/spec/fixtures/ip6tables-empty.txt +7 -0
  49. data/spec/fixtures/only-docker-c.txt +23 -0
  50. data/spec/fixtures/only-docker.txt +23 -0
  51. data/spec/fixtures/only_docker.rb +22 -0
  52. data/spec/fixtures/runtime.rb +7 -0
  53. data/spec/fixtures/runtime2.rb +16 -0
  54. data/spec/ipscriptables/dsl_spec.rb +74 -0
  55. data/spec/ipscriptables/helpers_spec.rb +58 -0
  56. data/spec/ipscriptables/rule_spec.rb +41 -0
  57. data/spec/ipscriptables/ruleset/class_methods_spec.rb +52 -0
  58. data/spec/ipscriptables/ruleset_spec.rb +199 -0
  59. data/spec/ipscriptables/runtime_spec.rb +227 -0
  60. data/spec/ipscriptables/table_spec.rb +32 -0
  61. data/spec/ipscriptables/version_spec.rb +12 -0
  62. data/spec/spec_helper.rb +60 -0
  63. metadata +350 -0
@@ -0,0 +1,38 @@
1
+ # -*- mode: ruby; coding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ipscriptables/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'ipscriptables'
8
+ spec.version = IPScriptables::VERSION
9
+ spec.authors = ['Maciej Pasternacki']
10
+ spec.email = ['maciej@3ofcoins.net']
11
+ spec.description = 'Ruby-driven IPTables'
12
+ spec.summary = 'Ruby-driven IPTables'
13
+ spec.homepage = 'https://github.com/3ofcoins/ipscriptables/'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(/^(test|spec|features)\//)
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'docile'
22
+ spec.add_dependency 'hashie'
23
+ spec.add_dependency 'systemu'
24
+ spec.add_dependency 'ohai'
25
+ spec.add_dependency 'sigar' # for OHAI's network_listeners plugin
26
+ spec.add_dependency 'ipaddr_extensions' # for OHAI's ip_scopes plugin
27
+ spec.add_dependency 'diffy'
28
+ spec.add_dependency 'clamp'
29
+
30
+ spec.add_development_dependency 'bundler', '~> 1.3'
31
+ spec.add_development_dependency 'minitest'
32
+ spec.add_development_dependency 'mocha'
33
+ spec.add_development_dependency 'simplecov'
34
+ spec.add_development_dependency 'rake', '~> 10.1'
35
+ spec.add_development_dependency 'wrong', '~> 0.7'
36
+ spec.add_development_dependency 'fauxhai'
37
+ spec.add_development_dependency 'rubocop'
38
+ end
@@ -0,0 +1,14 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'docile'
4
+ require 'hashie'
5
+
6
+ require 'ipscriptables/version'
7
+
8
+ require 'ipscriptables/ruleset'
9
+ require 'ipscriptables/table'
10
+ require 'ipscriptables/chain'
11
+ require 'ipscriptables/rule'
12
+ require 'ipscriptables/runtime'
13
+
14
+ require 'ipscriptables/pretty_print'
@@ -0,0 +1,83 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module IPScriptables
4
+ class Chain
5
+ extend Forwardable
6
+ include Enumerable
7
+ attr_reader :name, :table, :rules, :counters
8
+ attr_accessor :policy
9
+ def_delegators :rules, :each, :clear, :<<, :empty?
10
+ def_delegators :table, :ruleset, :opts
11
+
12
+ def initialize(name, table, policy = '-', counters = [0, 0], &block)
13
+ @name = name
14
+ @table = table
15
+ @policy = policy
16
+ @counters = counters
17
+ @rules = []
18
+ @rule_stack = []
19
+ Docile.dsl_eval(self, &block) if block_given?
20
+ end
21
+
22
+ def original
23
+ table.original[name] if table.original
24
+ end
25
+
26
+ def alter(policy = nil, counters = nil, &block)
27
+ @policy = policy unless policy.nil?
28
+ @counters = counters unless counters.nil?
29
+ Docile.dsl_eval(self, &block) if block_given?
30
+ end
31
+
32
+ def rule(term, *rest, &block) # rubocop:disable CyclomaticComplexity, MethodLength, LineLength
33
+ # FIXME: ^^
34
+ case term
35
+ when Rule
36
+ @rules << term # we trust here that term.chain is self
37
+ when Hash
38
+ # Explode hash into [switch, value, switch, value, ...] sequence
39
+ exploded = []
40
+ term.each do |key, val|
41
+ if key.is_a? Symbol
42
+ key = key.to_s
43
+ if key.length == 1
44
+ exploded << "-#{key}"
45
+ else
46
+ exploded << "--#{key.gsub('_', '-')}"
47
+ end
48
+ else
49
+ exploded << key.to_s
50
+ end
51
+ exploded << val
52
+ end
53
+ exploded.concat(rest)
54
+ rule(*exploded, &block)
55
+ when Enumerable
56
+ term.each do |term1|
57
+ rule(term1, *rest, &block)
58
+ end
59
+ else
60
+ begin
61
+ @rule_stack << term
62
+ if !rest.empty?
63
+ rule(*rest, &block)
64
+ elsif block_given?
65
+ yield
66
+ else
67
+ @rules << Rule.new(self, @rule_stack.map(&:to_s).join(' '))
68
+ end
69
+ ensure
70
+ @rule_stack.pop
71
+ end
72
+ end
73
+ end
74
+
75
+ def render_header
76
+ ":#{name} #{policy} [#{counters.join(':')}]"
77
+ end
78
+
79
+ def render_rules
80
+ rules.map(&:render).join("\n") unless rules.empty?
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,19 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'clamp'
3
+ require 'ipscriptables'
4
+
5
+ module IPScriptables
6
+ class CLI < Clamp::Command
7
+ option '--apply', :flag, 'Apply changes to iptables/ip6tables'
8
+ option '--quiet', :flag, 'Don\'t print diff'
9
+
10
+ parameter 'SCRIPT ...', 'Ruby DSL spec(s) to evaluate',
11
+ attribute_name: :scripts
12
+
13
+ def execute
14
+ runtime = IPScriptables::Runtime.new(apply: apply?, quiet: quiet?)
15
+ scripts.each { |script| runtime.load_file(script) }
16
+ runtime.execute!
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'ohai'
4
+ require 'systemu'
5
+
6
+ module IPScriptables
7
+ module Helpers
8
+ extend Forwardable
9
+ def_delegators IPScriptables::Helpers, :ohai, :run_command
10
+
11
+ class << self
12
+ def run_command(*argv)
13
+ status, stdout, stderr = systemu(argv)
14
+ unless status.success?
15
+ $stderr.puts stdout.gsub(/^/, "#{argv.first}: ") unless stdout.empty?
16
+ fail "#{status}: #{stderr}"
17
+ end
18
+ $stderr.puts stderr.gsub(/^/, "#{argv.first}: ") unless stderr.empty?
19
+ stdout
20
+ end
21
+
22
+ def ohai
23
+ @ohai ||= setup_ohai
24
+ end
25
+
26
+ private
27
+
28
+ def setup_ohai
29
+ require 'ohai'
30
+ ohai = Ohai::System.new
31
+ %w(os platform kernel hostname network ip_scopes network_listeners
32
+ cloud).each do |plugin|
33
+ ohai.require_plugin plugin
34
+ end
35
+ ohai
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,58 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module IPScriptables
4
+ class Ruleset
5
+ def inspect
6
+ "#<#{self.class} [#{map(&:inspect).join(', ')}]>"
7
+ end
8
+
9
+ def pretty_print(q)
10
+ q.object_address_group(self) do
11
+ q.group(2) do
12
+ q.breakable
13
+ q.seplist(self, -> { q.breakable }) { |v| q.pp v }
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ class Table
20
+ def inspect
21
+ "#<#{self.class} #{name} [#{map(&:inspect).join(', ')}]>"
22
+ end
23
+
24
+ def pretty_print(q)
25
+ q.group(2, "*#{name} {", '}') do
26
+ unless @chains.empty?
27
+ q.breakable
28
+ q.seplist(self, -> { q.breakable }) { |v| q.pp v }
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ class Chain
35
+ def inspect
36
+ "#<#{self.class} #{name} [#{map(&:inspect).join(', ')}]>"
37
+ end
38
+
39
+ def pretty_print(q)
40
+ q.group(2, "#{render_header} {", '}') do
41
+ unless rules.empty?
42
+ q.breakable
43
+ q.seplist(rules, -> { q.breakable ' ; ' }) { |v| q.pp(v) }
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ class Rule
50
+ def inspect
51
+ "#<#{self.class} #{render_counters}#{rule}>"
52
+ end
53
+
54
+ def pretty_print(q)
55
+ q.text("#{render_counters}#{rule}")
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,95 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'shellwords'
3
+
4
+ module IPScriptables
5
+ class Rule
6
+ OPTION_SYNONYMS = Hashie::Mash[
7
+ :p => :protocol,
8
+ :s => :source,
9
+ :d => :destination,
10
+ :j => :jump,
11
+ :g => :goto,
12
+ :i => :in_interface,
13
+ :o => :out_interface,
14
+ :f => :fragment,
15
+ :m => :match,
16
+ :sport => :source_port,
17
+ :dport => :destination_port,
18
+ :sports => :source_ports,
19
+ :dports => :destination_ports
20
+ ].freeze
21
+
22
+ extend Forwardable
23
+ attr_reader :chain, :rule, :counters
24
+ def_delegators :chain, :opts
25
+
26
+ def initialize(chain, rule, counters = nil) # rubocop:disable MethodLength
27
+ @chain, @rule, @counters = chain, rule, counters
28
+ @parsed = Hashie::Mash.new
29
+
30
+ @counters ||= original.counters if original
31
+ @counters ||= [0, 0] if opts[:counters]
32
+
33
+ key = nil
34
+ Shellwords.shellsplit(rule).each do |word|
35
+ case word
36
+ when /^-+(.*)$/
37
+ self[key] = true if key
38
+ key = Regexp.last_match[1].gsub('-', '_')
39
+ else
40
+ self[key] = word
41
+ key = nil
42
+ end
43
+ end
44
+ end
45
+
46
+ def original
47
+ chain.original.find { |rule| rule == self } if chain.original
48
+ end
49
+
50
+ def ==(other)
51
+ other = other.rule if other.respond_to?(:rule)
52
+ rule == other
53
+ end
54
+
55
+ def [](k)
56
+ k = k.to_s.sub(/^-+/, '').gsub('-', '_')
57
+ @parsed[OPTION_SYNONYMS.fetch(k, k)]
58
+ end
59
+
60
+ def proto
61
+ self[:protocol]
62
+ end
63
+
64
+ def match
65
+ Array(self[:m])
66
+ end
67
+
68
+ def target
69
+ self[:jump]
70
+ end
71
+
72
+ def =~(other)
73
+ rule =~ other
74
+ end
75
+
76
+ def render
77
+ "#{render_counters}-A #{chain.name} #{rule}"
78
+ end
79
+
80
+ def render_counters
81
+ "[#{counters.join(':')}] " if counters
82
+ end
83
+
84
+ private
85
+
86
+ def []=(k, v)
87
+ k = OPTION_SYNONYMS.fetch(k, k)
88
+ if @parsed.key?(k)
89
+ @parsed[k] = Array(@parsed[k]) << v
90
+ else
91
+ @parsed[k] = v
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,103 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'diffy'
4
+
5
+ require 'ipscriptables/helpers'
6
+ require 'ipscriptables/ruleset/class_methods'
7
+
8
+ module IPScriptables
9
+ class Ruleset
10
+ include Helpers
11
+
12
+ attr_reader :opts
13
+ extend Forwardable
14
+ include Enumerable
15
+ def_delegators :@tables, :[]=, :[]
16
+ def_delegators :to_ary, :each
17
+ def_delegators :opts, :original
18
+
19
+ def initialize(opts = {}, &block)
20
+ @tables = Hashie::Mash.new
21
+ @opts = Hashie::Mash[opts]
22
+ dsl_eval(&block) if block_given?
23
+ end
24
+
25
+ def dsl_eval(&block)
26
+ Docile.dsl_eval(self, &block)
27
+ end
28
+
29
+ def load_file(path)
30
+ dsl_eval { instance_eval(File.read(path), path) }
31
+ end
32
+
33
+ def respond_to?(meth)
34
+ super || @opts.respond_to?(meth)
35
+ end
36
+
37
+ def method_missing(meth, *args, &block)
38
+ if @opts.respond_to?(meth)
39
+ @opts.send(meth, *args, &block)
40
+ else
41
+ super
42
+ end
43
+ end
44
+
45
+ def bud(opts = {}, &block)
46
+ opts = opts.merge skip_builtin_chains: true, original: self
47
+ opts[:family] = self.opts.family if self.opts.family?
48
+ child = self.class.new(opts)
49
+ each do |table|
50
+ child_table = child.table(table.name)
51
+ table.each do |chain|
52
+ child_table.chain chain.name, chain.policy, chain.counters
53
+ end
54
+ end
55
+ Docile.dsl_eval(child, &block) if block_given?
56
+ child
57
+ end
58
+
59
+ def to_ary
60
+ @tables.values
61
+ end
62
+
63
+ def table(name, &block)
64
+ if @tables.key?(name)
65
+ Docile.dsl_eval(@tables[name], &block)
66
+ else
67
+ self[name] = Table.new(name, self, &block)
68
+ end
69
+ end
70
+
71
+ def inherit(table, *names, &block)
72
+ self[table].inherit(*names, &block)
73
+ end
74
+
75
+ def render
76
+ map(&:render).join("\n") << "\n"
77
+ end
78
+
79
+ def diff(from = nil)
80
+ from ||= original
81
+ fail 'Need something to diff against' unless from
82
+ Diffy::Diff.new(from.render, render)
83
+ end
84
+
85
+ def restore!
86
+ IO.popen(restore_command, 'w') do |restore|
87
+ restore.write(render)
88
+ end
89
+ unless $?.success?
90
+ fail "Failure in #{restore_command.join(' ').inspect}: #{$?}"
91
+ end
92
+ end
93
+
94
+ def restore_command
95
+ case opts[:family]
96
+ when :inet then %w(iptables-restore -c)
97
+ when :inet6 then %w(ip6tables-restore -c)
98
+ else fail NotImplementedError,
99
+ "Unsupported family #{opts[:family].inspect}"
100
+ end
101
+ end
102
+ end
103
+ end