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.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +15 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +6 -0
- data/CONTRIBUTING.md +43 -0
- data/Gemfile +13 -0
- data/LICENSE +20 -0
- data/README.md +54 -0
- data/Rakefile +22 -0
- data/bin/ipscriptables +6 -0
- data/cookbook/.gitignore +2 -0
- data/cookbook/.kitchen.yml +28 -0
- data/cookbook/Berksfile +6 -0
- data/cookbook/README.md +53 -0
- data/cookbook/attributes/default.rb +3 -0
- data/cookbook/chefignore +96 -0
- data/cookbook/libraries/default.rb +35 -0
- data/cookbook/metadata.rb +9 -0
- data/cookbook/providers/rules.rb +21 -0
- data/cookbook/recipes/default.rb +10 -0
- data/cookbook/recipes/load.rb +8 -0
- data/cookbook/resources/rules.rb +17 -0
- data/cookbook/test/cookbooks/ipscriptables-test/#metadata.rb# +8 -0
- data/cookbook/test/cookbooks/ipscriptables-test/metadata.rb +11 -0
- data/cookbook/test/cookbooks/ipscriptables-test/recipes/default.rb +23 -0
- data/cookbook/test/cookbooks/ipscriptables-test/recipes/prepare.rb +5 -0
- data/cookbook/test/data/.gitignore +1 -0
- data/cookbook/test/integration/default/bats/default.bats +9 -0
- data/doc/iptables-switches.txt +342 -0
- data/ipscriptables.gemspec +38 -0
- data/lib/ipscriptables.rb +14 -0
- data/lib/ipscriptables/chain.rb +83 -0
- data/lib/ipscriptables/cli.rb +19 -0
- data/lib/ipscriptables/helpers.rb +39 -0
- data/lib/ipscriptables/pretty_print.rb +58 -0
- data/lib/ipscriptables/rule.rb +95 -0
- data/lib/ipscriptables/ruleset.rb +103 -0
- data/lib/ipscriptables/ruleset/class_methods.rb +67 -0
- data/lib/ipscriptables/runtime.rb +97 -0
- data/lib/ipscriptables/table.rb +77 -0
- data/lib/ipscriptables/version.rb +5 -0
- data/spec/fixtures/clyhq.txt +40 -0
- data/spec/fixtures/docker-plus.txt +31 -0
- data/spec/fixtures/drumknott.txt +67 -0
- data/spec/fixtures/falcor.txt +39 -0
- data/spec/fixtures/ghq.txt +102 -0
- data/spec/fixtures/ip6tables-empty.txt +7 -0
- data/spec/fixtures/only-docker-c.txt +23 -0
- data/spec/fixtures/only-docker.txt +23 -0
- data/spec/fixtures/only_docker.rb +22 -0
- data/spec/fixtures/runtime.rb +7 -0
- data/spec/fixtures/runtime2.rb +16 -0
- data/spec/ipscriptables/dsl_spec.rb +74 -0
- data/spec/ipscriptables/helpers_spec.rb +58 -0
- data/spec/ipscriptables/rule_spec.rb +41 -0
- data/spec/ipscriptables/ruleset/class_methods_spec.rb +52 -0
- data/spec/ipscriptables/ruleset_spec.rb +199 -0
- data/spec/ipscriptables/runtime_spec.rb +227 -0
- data/spec/ipscriptables/table_spec.rb +32 -0
- data/spec/ipscriptables/version_spec.rb +12 -0
- data/spec/spec_helper.rb +60 -0
- 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
|