riptables 1.0.0

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