ipscriptables 0.0.1

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 (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