ipscriptables 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|