riptables 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6163673be40ac8debc91fbbb58b934d622a0551b
4
+ data.tar.gz: 834420f08005d80abf266219d82445f9187b42cb
5
+ SHA512:
6
+ metadata.gz: ffcca219d8afdab19f25844fa2e6352cd49f5149e7254fc70c691864db89c7698a4d7950639b9a93900165544cfe5bfb0b65901903352f1b3fa7d19eaaa71153
7
+ data.tar.gz: 97b4c44f8ec3b26978c6cf7e3bca149a7dfa79277f4f77db1e627beacf47f84b25e123444b427653e94bd8e1d087e6264a6a60ce26f48942e739ad7007b4c5e0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2014 Adam Cooke.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # Riptables
2
+
3
+ Riptables (pronounced ri-pee-tables) is a Ruby DSL for generating configuration
4
+ for IP tables. The following design goals were employed for development:
5
+
6
+ * Must support IPv4 and IPv6 rules
7
+ * Must allow a single file to contain configuration for multiple environments
8
+ based on a given `role` and `zone`.
9
+ * Must support any type of table or chain.
10
+ * Must support any rule or action without limitation.
11
+ * Must include a command line tool for exporting configuration.
12
+ * Should be simple to understand the configuration syntax.
13
+ * Should be well documentated.
14
+
15
+ ## `FirewallFile` Syntax
16
+
17
+ Riptables works with `FirewallFile` which contains the complete configuration for
18
+ all servers where this configuration will be distributed. In this example, we're
19
+ just going to configure a single rule to drop everything except SSH.
20
+
21
+ ```ruby
22
+ # Using the `table` method we define a new table. In this case, we'll be
23
+ # configuring a simple firewall.
24
+ table :filter do
25
+
26
+ # Set some default actions for the three main chains in the filter table.
27
+ # The action you enter will simply be passed to iptables. If it is a symbol
28
+ # it will be uppercased otherwise it will be passed through un-touched.
29
+ default_action :input, :drop
30
+ default_action :forward, :accept
31
+ default_action :output, :accept
32
+
33
+ # In it's most basic form, you can add rules by simply calling the name of the
34
+ # chain and a description.
35
+ input "Allow SSH" do
36
+ # Set the conditions for the rule you want to apply. This is passed unfettered
37
+ # to iptables so you can write anything you would normally before the -j flag.
38
+ rule "-p tcp --dport 22"
39
+ # Set the action to take if the rule is matched. If this is a symbol it will
40
+ # be uppercased automatically. If it's a string, it will be passed stright
41
+ # through after a -j flag.
42
+ action :accept
43
+ end
44
+
45
+ end
46
+ ```
47
+
48
+ ### Permutations
49
+
50
+ If you have rules which are always similar to other rules (for example a set of
51
+ IP ranges which must all be permitted) you can use permutations.
52
+
53
+ ```ruby
54
+ input "Allow web access" do
55
+ rule "-p tcp --dport {{port}}"
56
+ action :accept
57
+ permutation "Insecure", :port => "80"
58
+ permutation "Secure", :port => "443"
59
+ end
60
+ ```
61
+
62
+ Each permutation will be applied as its own rule using the base rule as a template.
63
+ Using the variable interpolation, you can insert any variable you wish in each
64
+ permutation. The final `:v => 4` option sets that this should only apply to the
65
+ IPv4 firewall - it can be set to 6 to only apply them to IPv6 firewalls.
66
+
67
+ ### Zones & Roles
68
+
69
+ If you have different types of servers and want to apply different rules based
70
+ on what and where a machine is, you can do so. You can either limit whole rules
71
+ or just permutations within a rule.
72
+
73
+ ```ruby
74
+ # Any rules which are defined within this role block will only be included when
75
+ # you generate an iptables config for the `vpn` role.
76
+ role :vpn do
77
+
78
+ input "Allow management access" do
79
+ rule "-s {{ip}}"
80
+ action :accept
81
+ permutation "Allow Internal", :ip => '10.0.0.0/16', :v => 4
82
+ permutation "Allow IPv6", :ip => '2a00:67a0:a:123::/64', :v => 6
83
+
84
+ # Any permutations within this block will only be included when you generate
85
+ # an iptavles config for any `eu-east` zone or 'us-west-4'.
86
+ zone /eu\-east\-(\d+)/, "us-west-4" do
87
+ permutation "aTech Media", :ip => "185.22.208.0/25", :v => 4
88
+ end
89
+ end
90
+
91
+ end
92
+ ```
93
+
94
+ ### IPv4 vs. IPv6
95
+
96
+ By default, any rule you configure will apply to both your IPv4 firewall and your
97
+ IPv6 firewall. However, you can define rule or permutations to only use one or
98
+ the other.
99
+
100
+ ```ruby
101
+ input "Block nasty IPv6 person" do
102
+ rule "-s 2a00:67a0:abc::1234/128"
103
+ action :drop
104
+ # Add the `version` option to restrict this rule to the IPv6 firewall only.
105
+ # You can also use `4` for the IPv4 firewall.
106
+ version 6
107
+ end
108
+ ```
109
+
110
+ You'll see in the previous example, you can pass the `:v` option to permutations
111
+ to restrict which firewall they belong to. Default rules will always apply to
112
+ both and cannot currently be different depending on IP version.
113
+
114
+ ## Command Line
115
+
116
+ The `riptables` command is used to generate your iptables-save files. These can
117
+ then be used with `iptables-restore`.
118
+
119
+ ```text
120
+ $ riptables
121
+ ```
122
+
123
+ The following options are supported and can be used interchagably:
124
+
125
+ * `-4` - return the IPv4 configuration (default)
126
+ * `-6` - return the IPv6 configuration (defaults to v4)
127
+ * `-f [PATH]` - path to your FirewallFile (defaults to ./FirewallFile)
128
+ * `--zone [ZONE]` - set the zone to export configuration for
129
+ * `--role [ROLE]` - set the role to export configuration for
130
+ * `--color` - return a [colorized output](http://s.adamcooke.io/14/Vmzd2.png) (useful for debugging)
data/bin/riptables ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'riptables'
4
+ require 'optparse'
5
+
6
+ options = {}
7
+ options[:conditions] = {}
8
+ options[:color] = false
9
+ ipv = 4
10
+ load_path = File.expand_path('./FirewallFile')
11
+
12
+ OptionParser.new do |opts|
13
+ opts.banner = "Usage: riptables [options]"
14
+ opts.on("-4", "Return IPv6 records") { ipv = 4 }
15
+ opts.on("-6", "Return IPv6 records") { ipv = 6 }
16
+ opts.on("-r", "--role [ROLE]", "The role to generate for") { |v| options[:conditions][:role] = v }
17
+ opts.on("-z", "--zone [ZONE]", "The zone to generate for") { |v| options[:conditions][:zone] = v }
18
+ opts.on("--color", "Colorize the output") { |v| options[:color] = true }
19
+ opts.on("--color", "Colorize the output") { |v| options[:color] = true }
20
+ opts.on("-f", "--file [PATH]", "The Riptables configuration file") { |v| load_path = v }
21
+ end.parse!
22
+
23
+ begin
24
+ Riptables.load_from_file(load_path)
25
+ Riptables.tables.each do |table|
26
+ puts table.export(options).to_savefile(ipv)
27
+ end
28
+ rescue Riptables::Error => e
29
+ $stderr.puts "\e[31m#{e}\e[0m"
30
+ exit 1
31
+ rescue => e
32
+ $stderr.puts "\e[31m#{e.class}: #{e.message}\e[0m"
33
+ exit 1
34
+ end
data/lib/riptables.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'riptables/dsl/root'
2
+ require 'riptables/error'
3
+
4
+ module Riptables
5
+
6
+
7
+ # Store all tables configured
8
+ #
9
+ def self.tables
10
+ @tables ||= []
11
+ end
12
+
13
+ #
14
+ # Parse a given file
15
+ #
16
+ def self.load_from_file(file)
17
+ if File.file?(file)
18
+ dsl = DSL::Root.new
19
+ dsl.instance_eval(File.read(file), file)
20
+ true
21
+ else
22
+ raise Error, "File not found at `#{file}`"
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,17 @@
1
+ module Riptables
2
+ class Chain
3
+
4
+ def initialize(table, name)
5
+ @table = table
6
+ @name = name
7
+ @default_action = :accept
8
+ @rules = []
9
+ end
10
+
11
+ attr_accessor :default_action
12
+ attr_reader :table
13
+ attr_reader :name
14
+ attr_reader :rules
15
+
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ require 'riptables/condition'
2
+
3
+ module Riptables
4
+ class Condition
5
+
6
+ def self.conditions
7
+ @conditions ||= []
8
+ end
9
+
10
+ def initialize(condition, &block)
11
+ @condition = [condition].flatten
12
+ @block = block
13
+ self.call
14
+ end
15
+
16
+ attr_reader :condition
17
+
18
+ def call
19
+ Condition.conditions << self
20
+ @block.call
21
+ Condition.conditions.delete(self)
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ require 'riptables/zone_condition'
2
+ require 'riptables/role_condition'
3
+
4
+ module Riptables
5
+ module DSL
6
+ class Global
7
+
8
+ #
9
+ # Any rules which are defined while inside this block should only apply
10
+ # to the associated zones.
11
+ #
12
+ def zone(*zones, &block)
13
+ ZoneCondition.new(zones) do
14
+ block.call
15
+ end
16
+ end
17
+
18
+ #
19
+ # Any rules which are defined while inside this block should only apply
20
+ # to the associated roles.
21
+ #
22
+ def role(*roles, &block)
23
+ RoleCondition.new(roles) do
24
+ block.call
25
+ end
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ require 'riptables/dsl/global'
2
+ require 'riptables/table'
3
+
4
+ module Riptables
5
+ module DSL
6
+ class Root < Global
7
+
8
+ #
9
+ # Rules within a given table
10
+ #
11
+ def table(name, &block)
12
+ table = Riptables::Table.new(name)
13
+ table.dsl.instance_eval(&block)
14
+ Riptables.tables << table
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ require 'riptables/dsl/global'
2
+ require 'riptables/rule_permutation'
3
+
4
+ module Riptables
5
+ module DSL
6
+ class Rule < Global
7
+
8
+ def initialize(rule)
9
+ @rule = rule
10
+ end
11
+
12
+ def rule(rule)
13
+ @rule.rule = rule
14
+ end
15
+
16
+ def action(action)
17
+ @rule.action = action
18
+ end
19
+
20
+ def permutation(description, options = {})
21
+ @rule.permutations << RulePermutation.new(@rule, description, options)
22
+ end
23
+
24
+ def version(number)
25
+ @rule.versions = [number]
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,43 @@
1
+ require 'riptables/dsl/global'
2
+ require 'riptables/rule'
3
+
4
+ module Riptables
5
+ module DSL
6
+ class Table < Global
7
+
8
+ def initialize(table)
9
+ @table = table
10
+ end
11
+
12
+ #
13
+ # Defines a default rule for a chain on this table
14
+ #
15
+ def default_action(chain, action)
16
+ @table.chain(chain).default_action = action
17
+ end
18
+
19
+ #
20
+ # Add a new rule in this table
21
+ #
22
+ def rule(chain, description = nil, &block)
23
+ rule = Riptables::Rule.new(@table.chain(chain))
24
+ rule.description = description
25
+ rule.dsl.instance_eval(&block)
26
+ @table.chain(chain).rules << rule
27
+ end
28
+
29
+ #
30
+ # Any method which do not exist are most likely just new rules when inside
31
+ # this block.
32
+ #
33
+ def method_missing(chain, *args, &block)
34
+ if block_given?
35
+ self.rule(chain, *args, &block)
36
+ else
37
+ super
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,4 @@
1
+ module Riptables
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,16 @@
1
+ require 'riptables/condition'
2
+
3
+ module Riptables
4
+ class RoleCondition < Condition
5
+
6
+ def matches?(conditions)
7
+ return false unless conditions[:role]
8
+ roles = conditions[:role].split(/\s?\,\s?/)
9
+ roles.each do |role|
10
+ return true if condition.any? { |c| c.is_a?(Regexp) ? c.match(role) : role.to_s == c.to_s }
11
+ end
12
+ return false
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ require 'riptables/dsl/rule'
2
+
3
+ module Riptables
4
+ class Rule
5
+
6
+ def initialize(chain)
7
+ @chain = chain
8
+ @permutations = []
9
+ @conditions = Condition.conditions.dup
10
+ @versions = [4, 6]
11
+ end
12
+
13
+ attr_accessor :description
14
+ attr_accessor :rule
15
+ attr_accessor :action
16
+ attr_accessor :conditions
17
+ attr_accessor :versions
18
+ attr_reader :chain
19
+ attr_reader :permutations
20
+ attr_reader :conditions
21
+
22
+ def dsl
23
+ @dsl ||= DSL::Rule.new(self)
24
+ end
25
+
26
+ def include?(conditions)
27
+ @conditions.all? { |c| c.matches?(conditions) }
28
+ end
29
+
30
+ def permuted_rules
31
+ if permutations.empty?
32
+ [self]
33
+ else
34
+ permutations.map(&:to_rule)
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ require 'riptables/condition'
2
+ require 'riptables/rule'
3
+
4
+ module Riptables
5
+ class RulePermutation
6
+
7
+ def initialize(rule, description, options = {})
8
+ @rule = rule
9
+ @description = description
10
+ @options = options
11
+ @conditions = Condition.conditions.dup - @rule.conditions
12
+ end
13
+
14
+ attr_reader :rule
15
+ attr_reader :description
16
+ attr_reader :options
17
+ attr_reader :conditions
18
+
19
+ #
20
+ # Convert this permutation into a full rule in its own right
21
+ #
22
+ def to_rule
23
+ new_rule = Rule.new(rule.chain)
24
+ new_rule.description = "#{rule.description} (#{self.description})"
25
+ new_rule.rule = rule.rule.gsub(/\{\{(\w+)\}\}/) do
26
+ if value = self.options[$1.to_sym]
27
+ value
28
+ else
29
+ "{{#{$1}}}"
30
+ end
31
+ end
32
+ new_rule.action = rule.action
33
+ new_rule.conditions = rule.conditions | self.conditions
34
+ if v = (self.options[:v] || self.options[:version])
35
+ new_rule.versions = [v.to_i]
36
+ end
37
+ new_rule
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ require 'riptables/dsl/table'
2
+ require 'riptables/chain'
3
+ require 'riptables/table_export'
4
+
5
+ module Riptables
6
+ class Table
7
+
8
+ attr_reader :name
9
+ attr_reader :chains
10
+
11
+ def initialize(name)
12
+ @name = name
13
+ @chains = {}
14
+ end
15
+
16
+ def dsl
17
+ @dsl ||= DSL::Table.new(self)
18
+ end
19
+
20
+ def chain(name)
21
+ @chains[name] ||= Chain.new(self, name)
22
+ end
23
+
24
+ def export(options = {})
25
+ TableExport.new(self, options)
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ module Riptables
2
+ class TableExport
3
+
4
+ def initialize(table, options = {})
5
+ @table = table
6
+ @options = options
7
+ end
8
+
9
+ def to_savefile(version = 4)
10
+ Array.new.tap do |s|
11
+ s << "*#{col 31, @table.name}"
12
+ @table.chains.each do |_, chain|
13
+ s << ":#{col 32, chain.name.to_s.upcase} #{col 35, chain.default_action.to_s.upcase} [0:0]"
14
+ end
15
+
16
+ @table.chains.each do |_, chain|
17
+ chain.rules.map(&:permuted_rules).flatten.each do |rule|
18
+ next unless rule.include?(@options[:conditions] || {})
19
+ next unless rule.versions.include?(version)
20
+ action = rule.action ? "-j #{rule.action.is_a?(Symbol) ? rule.action.upcase : rule.action}" : ''
21
+ comment = "-m comment --comment=\"#{rule.description.gsub('"', '\'')}\""
22
+ s << "-A #{col 32, chain.name.to_s.upcase} #{col 33, rule.rule} #{col 35, action} #{col 36, comment}"
23
+ end
24
+ end
25
+
26
+ s << "COMMIT"
27
+ s << "# Compiled by riptables on #{Time.now.strftime("%a %b %e %H:%M:%S %Y")}"
28
+ end.join("\n")
29
+ end
30
+
31
+ def col(number, text)
32
+ if @options[:color] == false
33
+ text
34
+ else
35
+ "\e[#{number}m#{text}\e[0m"
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ module Riptables
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,14 @@
1
+ require 'riptables/condition'
2
+
3
+ module Riptables
4
+ class ZoneCondition < Condition
5
+
6
+ def matches?(conditions)
7
+ conditions[:zone] &&
8
+ condition.any? do |c|
9
+ c.is_a?(Regexp) ? c.match(conditions[:zone]) : conditions[:zone].to_s == c.to_s
10
+ end
11
+ end
12
+
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: riptables
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Adam Cooke
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-21 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: 'An Ruby DSL for generating iptables configuration. '
14
+ email:
15
+ - me@adamcooke.io
16
+ executables:
17
+ - riptables
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - MIT-LICENSE
22
+ - README.md
23
+ - bin/riptables
24
+ - lib/riptables.rb
25
+ - lib/riptables/chain.rb
26
+ - lib/riptables/condition.rb
27
+ - lib/riptables/dsl/global.rb
28
+ - lib/riptables/dsl/root.rb
29
+ - lib/riptables/dsl/rule.rb
30
+ - lib/riptables/dsl/table.rb
31
+ - lib/riptables/error.rb
32
+ - lib/riptables/role_condition.rb
33
+ - lib/riptables/rule.rb
34
+ - lib/riptables/rule_permutation.rb
35
+ - lib/riptables/table.rb
36
+ - lib/riptables/table_export.rb
37
+ - lib/riptables/version.rb
38
+ - lib/riptables/zone_condition.rb
39
+ homepage: http://adamcooke.io
40
+ licenses:
41
+ - MIT
42
+ metadata: {}
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '2.0'
52
+ - - "<"
53
+ - !ruby/object:Gem::Version
54
+ version: '3'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubyforge_project:
62
+ rubygems_version: 2.2.2
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: An Ruby DSL for generating iptables configuration.
66
+ test_files: []
67
+ has_rdoc: