Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First commit of Open Source release

  • Loading branch information...
commit 08913b752f5a54e0e49874c281504357e35ce8bd 0 parents
@johnf johnf authored
Showing with 3,385 additions and 0 deletions.
  1. +6 −0 .gitignore
  2. +1 −0  .rbenv-version
  3. +16 −0 AUTHORS.md
  4. +93 −0 CHANGELOG.md
  5. +4 −0 Gemfile
  6. +62 −0 Gemfile.lock
  7. +19 −0 LICENCE
  8. +516 −0 README.md
  9. +135 −0 Rakefile
  10. +18 −0 bin/rbenv-sudo
  11. +207 −0 bin/ript
  12. +48 −0 dist/init.d
  13. +16 −0 examples/accept-multiple-from-and-to.rb
  14. +13 −0 examples/accept-with-a-list-of-ports.rb
  15. +14 −0 examples/accept-with-specific-port-and-interface.rb
  16. +11 −0 examples/accept-without-specific-from.rb
  17. +12 −0 examples/accept.rb
  18. +4 −0 examples/basic.rb
  19. +2 −0  examples/dash-in-partition-name.rb
  20. +11 −0 examples/drop.rb
  21. +2 −0  examples/duplicate-partition-names/foobar1.rb
  22. +2 −0  examples/duplicate-partition-names/foobar2.rb
  23. +12 −0 examples/errors-undefined-method-with-no-match.rb
  24. +12 −0 examples/errors-undefined-method.rb
  25. +16 −0 examples/forward-dnat-with-different-destination-port.rb
  26. +11 −0 examples/forward-dnat-with-explicit-from-and-port-mappings.rb
  27. +11 −0 examples/forward-dnat-with-explicit-from-and-ports.rb
  28. +11 −0 examples/forward-dnat-with-explicit-from.rb
  29. +15 −0 examples/forward-dnat-with-explicit-protocols.rb
  30. +13 −0 examples/forward-dnat-with-multiple-froms.rb
  31. +10 −0 examples/forward-dnat-with-multiple-ports.rb
  32. +15 −0 examples/forward-dnat-with-multiple-sources.rb
  33. +16 −0 examples/forward-dnat.rb
  34. +16 −0 examples/forward-snat-with-explicit-from.rb
  35. +13 −0 examples/forward-snat-with-multiple-sources.rb
  36. +9 −0 examples/forward-snat.rb
  37. +12 −0 examples/log-and-accept.rb
  38. +11 −0 examples/log-and-drop.rb
  39. +10 −0 examples/log-dnat.rb
  40. +13 −0 examples/log-snat.rb
  41. +11 −0 examples/log.rb
  42. +15 −0 examples/missing-address-definition-in-destination.rb
  43. +15 −0 examples/missing-address-definition-in-from.rb
  44. +14 −0 examples/multiple-partitions-in-this-file.rb
  45. +11 −0 examples/multiple-partitions/bar.rb
  46. +17 −0 examples/multiple-partitions/foo.rb
  47. +2 −0  examples/partition-name-exactly-20-characters.rb
  48. +2 −0  examples/partition-name-longer-than-20-characters.rb
  49. +10 −0 examples/postclean.rb
  50. +10 −0 examples/preclean.rb
  51. +9 −0 examples/raw-with-chain-deletion.rb
  52. +9 −0 examples/raw-with-flush.rb
  53. +50 −0 examples/raw.rb
  54. +11 −0 examples/reject.rb
  55. +2 −0  examples/space-in-partition-name.rb
  56. +115 −0 features/cli.feature
  57. +107 −0 features/dsl/errors.feature
  58. +187 −0 features/dsl/filter.feature
  59. +114 −0 features/dsl/logging.feature
  60. +271 −0 features/dsl/nat.feature
  61. +28 −0 features/dsl/raw.feature
  62. +58 −0 features/setup.feature
  63. +15 −0 features/step_definitions/cli_steps.rb
  64. +44 −0 features/step_definitions/example_steps.rb
  65. +25 −0 features/support/env.rb
  66. +20 −0 lib/ript/bootstrap.rb
  67. +14 −0 lib/ript/dsl.rb
  68. +7 −0 lib/ript/dsl/primitives.rb
  69. +78 −0 lib/ript/dsl/primitives/common.rb
  70. +145 −0 lib/ript/dsl/primitives/filter.rb
  71. +206 −0 lib/ript/dsl/primitives/nat.rb
  72. +45 −0 lib/ript/dsl/primitives/raw.rb
  73. +2 −0  lib/ript/exceptions.rb
  74. +162 −0 lib/ript/partition.rb
  75. +10 −0 lib/ript/patches.rb
  76. +70 −0 lib/ript/rule.rb
  77. +3 −0  lib/ript/version.rb
  78. +33 −0 ript.gemspec
6 .gitignore
@@ -0,0 +1,6 @@
+tmp/
+pkg/
+*.swp
+*~
+vendor
+.bundle
1  .rbenv-version
@@ -0,0 +1 @@
+1.9.2-p290
16 AUTHORS.md
@@ -0,0 +1,16 @@
+Ript was designed and built by:
+
+Lindsay Holmwood (@auxesis)
+Steve Fisher (@laminat0r)
+
+Patches have been merged from:
+
+Arthur Barton (@arthurbarton)
+John Ferlito (@johnf)
+Jesse Reynolds (@jessereynolds)
+
+Inspiration given by:
+
+Matt Moor (@mattm0)
+
+Ript is copyright Bulletproof Networks 2011-2012, all rights reserved.
93 CHANGELOG.md
@@ -0,0 +1,93 @@
+## Changelog
+
+# 0.8.4 - 2012/08/121
+ - Bug: DNAT rules from one port to another were adding a filter rule for the
+ source instead of destination port (@johnf)
+
+# 0.8.3 - 2012/07/19
+ - Bug: Default the protocol for filter rules to TCP, so filter rules are generated correctly (@auxesis)
+
+# 0.8.2 - 2012/07/19
+ - Bug: Fix a regression where we don't generate rules without an explicit from. (@auxesis)
+
+# 0.8.1 - 2012/07/17
+ - Bug: Generate the iptables clean commands in Ruby, to eliminate bogus clean command generation (@auxesis)
+ - Chore: Refactor test internals to re-use common iptables cleaning routines (@auxesis)
+
+# 0.8.0 - 2012/07/17
+ - Feature: Allow multiple froms to be specified in a DNAT rewrite (@auxesis)
+ - Feature: Provide a default label named "all", that represents the IPv4 zero-address 0.0.0.0/0 (@auxesis)
+
+# 0.7.1 - 2012/07/16
+ - Bug: Ensure the list of chains to clean up is unique, so we don't delete the same chains multiple times (@auxesis)
+
+# 0.7.0 - 2012/07/09
+ - Feature: Show a custom message if exceptions appear to be generated by Ript (@auxesis)
+ - Feature: Add support for specifying protocols in rewrites (@auxesis)
+ - Chore: Move example rules to examples/. Point tests at the new directory (@auxesis)
+
+# 0.6.1 - 2012/06/06
+ - Feature: Make init script executable (@johnf)
+
+# 0.6.0 - 2012/06/06
+ - Feature: add "rules save", outputs rules in a format suitable for iptables-restore (@johnf)
+ - Feature: Add an init script to dist/ that performs iptables-restore at boot (@johnf)
+
+# 0.5.0 - 2012/05/31
+ - Feature: rename "customer" to "partition", to make terminology more friendly for use on standalone hosts (thanks @jessereynolds)
+
+# 0.4.3 - 2012/05/27
+ - Bug: Fix clean subcommand so it ignores important chains (before-a, etc) (@johnf)
+
+# 0.4.2 - 2012/05/24
+ - Bug: Use the destination address in the FORWARD chain when building the implicit accept on DNAT, so traffic actually gets accepted (@auxesis)
+
+# 0.4.1 - 2012/05/23
+ - Bug: Emit --protocol when generating ACCEPT rules, so the --dport argument works (@auxesis)
+
+# 0.4.0 - 2012/05/23
+ - Feature: Automatically create ACCEPT rules on the FORWARD chain, so NAT works in environments where DROP is the default policy(@auxesis)
+ - Feature: Reject multiple partition definitions in the same file, to maintain clean definitions(@auxesis)
+ - Feature: Make the DSL documentation awesome(@auxesis)
+
+# 0.3.6 - 2012/05/03
+ - Bug: Tests were broken and weren't matching empty output correctly (@johnf)
+ - Bug: raw tables were being applied repeatedly (@johnf)
+
+# 0.3.5 - 2012/05/03
+ - Bug: Bring back generate functionality (@johnf)
+
+# 0.3.4 - 2012/05/03
+ - Chore: Remove timestamps from chain names (@johnf)
+ - Feature: Add partition-X chain (@johnf)
+ - Feature: Add cleanup functionality (@johnf)
+ - Chore: Update CLI arguments (@johnf)
+
+# 0.3.3 - 2012/05/02
+ - Bug: Split SNAT/DNAT partition rule generation into separate chains, so rules apply correctly (@johnf)
+ - Feature: Check that ript is being run as root (@arthurbarton)
+
+# 0.3.2 - 2012/04/25
+ - Feature: Add validation for duplicate partition names (@auxesis)
+ - Feature: Add validation for bad characters in partition names (@auxesis)
+ - Feature: Add validation for partition names longer than 12 characters (@auxesis)
+
+# 0.3.1 - 2012/04/24
+ - Feature: Add support for specifying multiple to addresses in a single accept/drop/reject definition (@auxesis)
+
+# 0.3.0 - 2012/04/23
+ - Feature: Attempt to suggest alternative method names when a user uses one that doesn't exist (@auxesis)
+ - Feature: Extend accept, reject, drop, log blocks in the DSL to handle interfaces, protocols, and ports (@auxesis)
+ - Feature: Allow ript to run against an arbitrary path or file to the relative path (@auxesis)
+ - Feature: Add logging support throughout the DSL (@auxesis)
+ - Chore: Rename 'address' to 'label' in the DSL, as that's what they are (@auxesis)
+ - Chore: Rename 'forward' to 'rewrite' in the DSL, to reduce terminology collisions (@auxesis)
+ - Chore: Add a test harness script for running ript + tests in an rbenv environment as root (@auxesis)
+
+# 0.2.0 - 2012/04/10
+ - Add support for SNAT rules (@auxesis)
+ - Split tests into more managable files (@auxesis)
+
+# 0.1.0 - 2012/03/26
+ - Add installation + development documentation. (@auxesis)
+ - Build a gem release. (@auxesis)
4 Gemfile
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+
+source :rubygems
+gemspec
62 Gemfile.lock
@@ -0,0 +1,62 @@
+PATH
+ remote: .
+ specs:
+ ript (0.8.4)
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ arr-pm (0.0.7)
+ cabin (> 0)
+ aruba (0.3.5)
+ childprocess (>= 0.1.7)
+ cucumber (>= 0.10.0)
+ rspec (>= 2.5.0)
+ backports (2.3.0)
+ builder (3.0.0)
+ cabin (0.4.4)
+ json
+ childprocess (0.1.8)
+ ffi (~> 1.0.6)
+ clamp (0.3.1)
+ colorize (0.5.8)
+ cucumber (1.1.9)
+ builder (>= 2.1.2)
+ diff-lcs (>= 1.1.2)
+ gherkin (~> 2.9.0)
+ json (>= 1.4.6)
+ term-ansicolor (>= 1.0.6)
+ diff-lcs (1.1.3)
+ ffi (1.0.7)
+ rake (>= 0.8.7)
+ fpm (0.4.5)
+ arr-pm (~> 0.0.7)
+ backports (= 2.3.0)
+ cabin (~> 0.4.3)
+ clamp
+ json
+ gherkin (2.9.3)
+ json (>= 1.4.6)
+ json (1.6.6)
+ rake (0.8.7)
+ rspec (2.5.0)
+ rspec-core (~> 2.5.0)
+ rspec-expectations (~> 2.5.0)
+ rspec-mocks (~> 2.5.0)
+ rspec-core (2.5.1)
+ rspec-expectations (2.5.0)
+ diff-lcs (~> 1.1.2)
+ rspec-mocks (2.5.0)
+ term-ansicolor (1.0.7)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ aruba
+ colorize
+ cucumber (>= 1.1.9)
+ fpm (>= 0.4.5)
+ rake
+ ript!
+ rspec
19 LICENCE
@@ -0,0 +1,19 @@
+Copyright 2011-2012 Bulletproof Networks. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
516 README.md
@@ -0,0 +1,516 @@
+Ript
+====
+
+Ript provides a clean Ruby DSL for describing firewall rules, and implements
+database migrations-like functionality for applying the rules with zero downtime.
+
+Ript works with `iptables` on Linux, and is written in Ruby.
+
+Installing
+----------
+
+Make sure you have Ruby 1.9.2 installed, and run:
+
+``` bash
+gem install ript
+```
+
+If you want the firewall rules to be reloaded at reboot, you will need to set up an
+init script.
+
+``` bash
+sudo cp "$(dirname $(dirname $(dirname $(gem which ript/dsl.rb))))"/dist/init.d /etc/init.d/ript
+sudo update-rc.d ript defaults
+sudo mkdir /var/lib/ript
+sudo chown root.adm /var/lib/ript
+sudo chmod 770 /var/lib/ript
+```
+
+Applying rules
+--------------
+
+ - Run `ript rules generate <path>` - will output all the generated rules by interpreting the file, or files in directory, `<path>`
+ - Run `ript rules diff <path>` - will output a diff of rules to apply based on what rules are currently loaded in memory
+ - Run `ript rules apply <path>` - will apply the aforementioned diff
+ - Run `ript rules diff <path>` - will display any rules not applied correctly
+ - Run `ript rules save` - will output the currently loaded rule in iptables-restore format
+ - Run `ript clean diff <path>` - will output iptables commands to delete unneeded rules
+ - Run `ript clean apply <path>` - will run the iptables commands to delete unneeded rules
+
+There are tests for this workflow in `features/cli.feature`
+
+Note: If you are using the supplied init script then you will need to add:
+``` bash
+ript rules save > /var/lib/ript/iptables.stat
+```
+to your workflow.
+
+Developing
+----------
+
+It is recommended to use a Ubuntu Lucid VM to develop Ript. If you develop on a machine without iptables some of the tests will fail.
+
+It is also recommended that you use [rbenv](http://rbenv.org/).
+
+``` bash
+rbenv install 1.9.2-p290
+gem install bundler
+rbenv rehash
+```
+
+Then to setup a Ript development environment, run:
+
+``` bash
+git clone git@github.com:bulletproofnetworks/ript.git
+cd ript
+bundle
+rbenv rehash
+```
+
+Then run the tests with:
+
+``` bash
+# Run all the tests
+sudo bin/rbenv-sudo rake features
+# Run a specific test file
+sudo bin/rbenv-sudo cucumber -r features/support/ -r features/step_definitions/ features/dsl/filter.feature
+# Run a specific test in a file
+sudo bin/rbenv-sudo cucumber -r features/support/ -r features/step_definitions/ features/dsl/filter.feature:13
+```
+
+ript commands can be run like so:
+
+```` bash
+sudo bin/rbenv-sudo bundle exec ript --help
+```
+
+Releasing
+---------
+
+1. Bump the version in `lib/ript/version.rb`
+2. Add an entry to `CHANGELOG.md`
+3. Run a `bundle` to update any RubyGems dependencies.
+4. `git commit` everything.
+5. git tag the version git tag X.Y.Z
+6. Build the gem with `rake build`
+
+This will build a `.gem` and a `.deb` in `pkg/`
+
+Design
+------
+
+ - Applying firewall rules should cause zero downtime.
+ - Making a change to a partition's rules should only ever affect that partition.
+ - Each partition has their own set of chains where their rules live.
+ - Each chain is self contained, and there a pointers to that chain from a
+ global chain where all partition pointers live.
+ - The pointer rules should be kept very simple, to reduce the chain traversal
+ time for packets.
+ - Rolling forward is as simple as creating a new chain, and inserting pointers
+ to the new chain in the global chain.
+ - Rolling back is as simple as deleting the pointers to the new chain from the
+ global chain. The new chain could be retained, but we choose delete it.
+ - Decommissioning a partition should be as simple as removing the partition's
+ rules file.
+ - Deleting the rules file will cause Ript to realise the partition's chains
+ should be deleted.
+
+The DSL
+-------
+
+The core of Ript is its easy to use DSL to describe iptables firewall rules.
+
+The DSL is flexible and handles both simple and complex use cases.
+
+### Introduction ###
+
+![Book cover - http://www.flickr.com/photos/sterlic/4299631538/sizes/z/in/photostream/](http://farm5.staticflickr.com/4116/4880818306_3bd230d0d4_z.jpg)
+
+Let's start from the beginning:
+
+``` ruby
+# partitions/joeblogsco.rb
+partition "joeblogsco" do
+ # Labels + rules go here
+end
+```
+
+All labels + rules in Ript are wrapped in a `partition` block, which partitions
+partition rules so they can be changed on a per-partition basis. This is integral
+to how Ript does zero-downtime rule migrations.
+
+So, what are labels?
+
+``` ruby
+# partitions/joeblogsco.rb
+partition "joeblogsco" do
+ label "www.joeblogsco.com", :address => "172.19.56.216"
+ label "api.joeblogsco.com", :address => "172.19.56.217"
+ label "joeblogsco subnet", :address => "192.168.5.224/27"
+ label "app-01", :address => "192.168.5.230"
+end
+```
+
+Labels are identifiers for addresses or subnets that you want to write rules
+for.
+
+What are rules?
+
+``` ruby
+# partitions/joeblogsco.rb
+partition "joeblogsco" do
+ label "www.joeblogsco.com", :address => "172.19.56.216"
+ label "api.joeblogsco.com", :address => "172.19.56.217"
+ label "joeblogsco subnet", :address => "192.168.5.224/27"
+ label "app-01", :address => "192.168.5.230"
+
+ rewrite "public website" do
+ ports 80
+ dnat "www.joeblogsco.com" => "app-01"
+ end
+end
+```
+
+Rules define how traffic flows from one place to another. Rules can either
+rewrite the source or destination of a packet (SNAT and DNAT), or permit/deny
+the flow of traffic:
+
+
+``` ruby
+# partitions/joeblogsco.rb
+partition "joeblogsco" do
+ label "www.joeblogsco.com", :address => "172.19.56.216"
+ label "api.joeblogsco.com", :address => "172.19.56.217"
+ label "joeblogsco subnet", :address => "192.168.5.224/27"
+ label "app-01", :address => "192.168.5.230"
+ label "trusted office", :address => "172.20.4.124"
+
+ rewrite "public website" do
+ ports 80
+ dnat "www.joeblogsco.com" => "app-01"
+ end
+
+ rewrite "public ssh access" do
+ ports 22
+ dnat "www.joeblogsco.com" => "app-01"
+ end
+
+end
+```
+
+In the above example, we are telling Ript we want SSH traffic to
+`www.joeblogsco.co` (`172.19.56.216`) which is on a public network to be sent
+to `app-01` (`192.168.5.230`), which is on a private network.
+
+Because the default policy is to drop packets that don't have an explicit
+match, we also need an `accept` rule so that the traffic being rewritten is also
+allowed to pass through.
+
+Ript knows this is generally what you want to do, so it actually creates this
+rule for you automatically. If we were to write it out, it would look something
+like this:
+
+``` ruby
+rewrite "public ssh access" do
+ ports 22
+ dnat "www.joeblogsco.com" => "app-01"
+end
+
+accept "allow public ssh access" do
+ protocols "tcp"
+ ports 22
+ to "www.joeblogsco.com"
+end
+```
+
+Ript's DSL is actually pretty smart, so we can clean up the above example a
+bit:
+
+``` ruby
+# partitions/joeblogsco.rb
+partition "joeblogsco" do
+ label "www.joeblogsco.com", :address => "172.19.56.216"
+ label "api.joeblogsco.com", :address => "172.19.56.217"
+ label "joeblogsco subnet", :address => "192.168.5.224/27"
+ label "app-01", :address => "192.168.5.230"
+ label "trusted office", :address => "172.20.4.124"
+
+ rewrite "public website + ssh access" do
+ ports 80, 22
+ dnat "www.joeblogsco.com" => "app-01"
+ end
+
+end
+```
+
+Here we have collapsed the two rewrite rules into one. Ript does the heavy
+lifting behind the scenes to generate the all the rules.
+
+If you want to be more specific about your rewrites (for example, you only want
+external SSH access from a specific jump host), it's really straight forward:
+
+``` ruby
+# partitions/joeblogsco.rb
+partition "joeblogsco" do
+ label "www.joeblogsco.com", :address => "172.19.56.216"
+ label "api.joeblogsco.com", :address => "172.19.56.217"
+ label "joeblogsco subnet", :address => "192.168.5.224/27"
+ label "app-01", :address => "192.168.5.230"
+ label "trusted office", :address => "172.20.4.124"
+
+ rewrite "public website" do
+ ports 80
+ dnat "www.joeblogsco.com" => "app-01"
+ end
+
+ rewrite "trusted ssh access" do
+ ports 22
+ from "trusted office"
+ dnat "www.joeblogsco.com" => "app-01"
+ end
+end
+```
+
+Some notes on the DSL so far:
+
+ - A label's scope is restricted to the partition block it is defined in. This
+ means you can use the same labels across different partitions and there won't
+ be naming colissions.
+
+ - The string argument passed to `rewrite`, `accept`, and other DSL rules is
+ used purely for documentation (think comments). Other people maintaining your
+ firewall rules will love you when you describe the intention of those rule in
+ these comments.
+
+ It's always best to write rules as if the person who ends up maintaining your
+ rules is a violent psychopath who knows where you live.
+
+ - Rules will default to the TCP protocol if you don't specify one. Valid
+ protocols can be found in `/etc/protocols` on any Linux system. Ript accepts
+ both the numeric and string identifiers (`udp` and `17` are both valid), but
+ strongly recommends you use the string identifiers.
+
+ - Given `accept` rules are created automatically when you define a rewrite, you
+ may be wondering if `accept` rules are used at all?
+
+ `accept` is very useful on standalone firewalls, when opening up specific
+ ports to the public internet.
+
+ For firewall configurations that are doing lots of public-to-private address
+ translation, you're going to use `accepts` very rarely.
+
+
+### Rule types ###
+
+![Ruler - http://www.flickr.com/photos/sterlic/4299631538/](http://farm3.staticflickr.com/2730/4299631538_220c9c9448_z.jpg)
+
+The introduction examples cover the common use cases, but Ript has support for
+many other types of rules.
+
+For example, SNAT:
+
+``` ruby
+# partitions/joeblogsco.rb
+partition "joeblogsco" do
+ label "www.joeblogsco.com", :address => "172.19.56.216"
+ label "joeblogsco subnet", :address => "192.168.5.224/27"
+ label "app-01", :address => "192.168.5.230"
+
+ rewrite "private to public" do
+ snat "joeblogsco subnet" => "www.joeblogsco.com"
+ end
+end
+```
+
+The above SNAT rule will rewrite all outgoing traffic from the
+`joeblogsco subnet` to appear as if it's originating from `www.joeblogsco.com`
+(`172.19.56.216`).
+
+If you need to explicitly drop traffic from somewhere, Ript makes this trivial:
+
+``` ruby
+# partitions/joeblogsco.rb
+partition "joeblogsco" do
+ label "www.joeblogsco.com", :address => "172.19.56.216"
+ label "app-01", :address => "192.168.5.230"
+ label "bad guy", :address => "172.19.110.247"
+
+ rewrite "public website + ssh access" do
+ ports 80, 22
+ dnat "www.joeblogsco.com" => "app-01"
+ end
+
+ drop "bad guy" do
+ from "bad guy"
+ to "www.joeblogsco.com"
+ end
+end
+```
+
+You can also broaden your drop to subnets, and restrict it down to a protocol:
+
+``` ruby
+# partitions/joeblogsco.rb
+partition "joeblogsco" do
+ label "www.joeblogsco.com", :address => "172.19.56.216"
+ label "app-01", :address => "192.168.5.230"
+ label "bad guys", :address => "10.0.0.0/8"
+
+ rewrite "public website + ssh access" do
+ ports 80, 22
+ dnat "www.joeblogsco.com" => "app-01"
+ end
+
+ drop "bad guys" do
+ protocols "udp"
+ from "bad guys"
+ to "www.joeblogsco.com"
+ end
+end
+```
+
+Alternatively, you can also reject the traffic:
+
+``` ruby
+# partitions/joeblogsco.rb
+partition "joeblogsco" do
+ label "www.joeblogsco.com", :address => "172.19.56.216"
+ label "app-01", :address => "192.168.5.230"
+ label "bad guys", :address => "10.0.0.0/8"
+
+ rewrite "public website + ssh access" do
+ ports 80, 22
+ dnat "www.joeblogsco.com" => "app-01"
+ end
+
+ reject "bad guys" do
+ protocols "udp"
+ from "bad guys"
+ to "www.joeblogsco.com"
+ end
+end
+```
+
+### Logging ###
+
+![Logs - http://www.flickr.com/photos/crawshawt/4636162605/](http://farm5.staticflickr.com/4020/4636162605_9ac8e91b56_z.jpg)
+
+Dropping and rejecting traffic is very useful, but if a tree falls in the
+forest and no-one is there to hear it...
+
+Ript makes flipping on logging extremely simple:
+
+``` ruby
+# partitions/joeblogsco.rb
+partition "joeblogsco" do
+ label "www.joeblogsco.com", :address => "172.19.56.216"
+ label "app-01", :address => "192.168.5.230"
+ label "bad guys", :address => "10.0.0.0/8"
+
+ rewrite "public website + ssh access", :log => true do
+ ports 80, 22
+ dnat "www.joeblogsco.com" => "app-01"
+ end
+
+ reject "bad guys", :log => true do
+ protocols "udp"
+ from "bad guys"
+ to "www.joeblogsco.com"
+ end
+end
+```
+
+You can pass `:log => true` to any rule, and Ript will automatically generate
+logging statements.
+
+
+### Shortcuts ###
+
+![Shorthand http://www.flickr.com/photos/sizemore/2215594186/](http://farm3.staticflickr.com/2397/2215594186_c979f71689_z.jpg)
+
+Ript provides shortcuts for setting up common rules:
+
+``` ruby
+partition "joeblogsco" do
+ label "joeblogsco uat subnet", :address => "192.168.5.0/24"
+ label "joeblogsco stage subnet", :address => "10.60.2.0/24"
+ label "joeblogsco prod subnet", :address => "10.60.3.0/24"
+ label "www.joeblogsco.com", :address => "172.19.56.216"
+
+ rewrite "private to public" do
+ snat [ "joeblogsco uat subnet",
+ "joeblogsco stage subnet",
+ "joeblogsco prod subnet" ] => "www.joeblogsco.com"
+ end
+end
+```
+
+Ript will expand the above to:
+
+``` ruby
+partition "joeblogsco" do
+ label "joeblogsco uat subnet", :address => "192.168.5.0/24"
+ label "joeblogsco stage subnet", :address => "10.60.2.0/24"
+ label "joeblogsco prod subnet", :address => "10.60.3.0/24"
+ label "www.joeblogsco.com", :address => "172.19.56.216"
+
+ rewrite "private to public" do
+ snat "joeblogsco uat subnet" => "www.joeblogsco.com"
+ end
+
+ rewrite "private to public" do
+ snat "joeblogsco stage subnet" => "www.joeblogsco.com"
+ end
+
+ rewrite "private to public" do
+ snat "joeblogsco prod subnet" => "www.joeblogsco.com"
+ end
+end
+```
+
+This also behaves exactly the same way with `accept`/`reject`/`drop` rules:
+
+``` ruby
+partition "tootyfruity" do
+ label "apple", :address => "192.168.0.1"
+ label "blueberry", :address => "192.168.0.2"
+ label "cranberry", :address => "192.168.0.3"
+ label "eggplant", :address => "192.168.0.4"
+ label "fennel", :address => "192.168.0.5"
+ label "grapefruit", :address => "192.168.0.6"
+
+ accept "fruits of the forest" do
+ protocols "tcp"
+ ports 22
+ from %w(apple blueberry cranberry eggplant fennel grapefruit)
+ to %w(apple blueberry cranberry eggplant fennel grapefruit)
+ end
+end
+```
+
+In the above example, Ript will generate rules for all the different
+combinations of `from` + `to` hosts.
+
+You can also specify ranges of ports to generate rules for, and setup port
+mappings:
+
+``` ruby
+partition "tootyfruity" do
+ label "apple", :address => "192.168.0.1"
+ label "blueberry", :address => "192.168.0.2"
+ label "cranberry", :address => "192.168.0.3"
+ label "eggplant", :address => "192.168.0.4"
+ label "fennel", :address => "192.168.0.5"
+ label "grapefruit", :address => "192.168.0.6"
+
+ rewrite "forward lots of ports, and don't make SSH public" do
+ protocols "tcp"
+ ports 80, 8600..8900, 443 => 4443, 2222 => 22
+ from %w(apple blueberry cranberry eggplant fennel grapefruit)
+ to %w(apple blueberry cranberry eggplant fennel grapefruit)
+ end
+end
+```
+
+The above example will generate a *lot* of rules, but it illustrates the power
+of the DSL.
135 Rakefile
@@ -0,0 +1,135 @@
+#!/usr/bin/env ruby
+
+#!/usr/bin/env ruby
+
+require 'rubygems'
+require 'bundler/setup'
+require 'cucumber'
+require 'cucumber/rake/task'
+require 'colorize'
+require 'pathname'
+$: << Pathname.new(__FILE__).join('lib').expand_path.to_s
+require 'ript/version'
+
+Cucumber::Rake::Task.new(:features) do |t|
+ t.cucumber_opts = "features --format pretty"
+end
+
+desc "Build packages for various platforms"
+#task :build => [ 'build:gem', 'build:deb' ]
+task :build => [ :verify, 'build:gem', 'build:deb' ]
+
+namespace :build do
+ desc "Build RubyGem"
+ task :gem do
+ build_output = `gem build ript.gemspec`
+ puts build_output
+
+ gem_filename = build_output[/File: (.*)/,1]
+ pkg_path = "pkg"
+ FileUtils.mkdir_p(pkg_path)
+ FileUtils.mv(gem_filename, pkg_path)
+
+ puts "Gem built at #{pkg_path}/#{gem_filename}".green
+ end
+
+ desc "Build a deb for Ubuntu"
+ task :deb => :gem do
+ gem_filename = "pkg/ript-#{Ript::VERSION}.gem"
+ deb_filename = "pkg/ript-#{Ript::VERSION}.deb"
+ system("rm -f #{deb_filename}")
+ build_output = `fpm -s gem -t deb -p #{deb_filename} #{gem_filename}`
+
+ require 'json'
+ json = build_output[/({.+})$/, 1]
+ data = JSON.parse(json)
+ if path = data["path"]
+ puts "Deb built at #{path}".green
+ end
+ end
+end
+
+namespace :verify do
+ desc "Verify the CHANGELOG is in order for a release"
+ task :changelog do
+ changelog_filename = "CHANGELOG.md"
+ version = Ript::VERSION
+
+ if not system("grep ^#{version} #{changelog_filename} 2>&1 >/dev/null")
+ puts "#{changelog_filename} doesn't have an entry for the version (#{version}) you are about to build.".red
+ exit 1
+ end
+ end
+
+ desc "Verify there are no uncommitted files"
+ task :uncommitted do
+ uncommitted = `git ls-files -m`.split("\n")
+ if uncommitted.size > 0
+ puts "The following files are uncommitted:".red
+ uncommitted.each do |filename|
+ puts " - #{filename}".red
+ end
+ exit 1
+ end
+ end
+
+ desc "Verify no requires of RubyGems have snuck in"
+ task :no_rubygems do
+ requires = `grep rubygems lib/ bin/ -rn |grep require`.split("\n")
+ if requires.size > 0
+ puts "The following files use RubyGems:".red
+ requires.each do |filename|
+ puts " - #{filename}".red
+ end
+ exit 1
+ end
+ end
+
+ task :all => [ :changelog, :uncommitted, :no_rubygems ]
+end
+
+task :verify => 'verify:all'
+
+
+
+desc "Clean out the state of iptables"
+task :clean_slate do
+ # Clean filter
+ system("sudo iptables --flush --table filter")
+ system("sudo iptables --delete-chain --table filter")
+ system("sudo iptables --table filter --policy INPUT ACCEPT")
+ system("sudo iptables --table filter --policy FORWARD ACCEPT")
+ system("sudo iptables --table filter --policy OUTPUT ACCEPT")
+
+ # Clean NAT
+ system("sudo iptables --flush --table nat")
+ system("sudo iptables --delete-chain --table nat")
+ system("sudo iptables --table nat --policy PREROUTING ACCEPT")
+ system("sudo iptables --table nat --policy POSTROUTING ACCEPT")
+ system("sudo iptables --table nat --policy OUTPUT ACCEPT")
+
+ # Clean mangle
+ system("sudo iptables --flush --table mangle")
+ system("sudo iptables --delete-chain --table mangle")
+ system("sudo iptables --table mangle --policy PREROUTING ACCEPT")
+ system("sudo iptables --table mangle --policy POSTROUTING ACCEPT")
+ system("sudo iptables --table mangle --policy INPUT ACCEPT")
+ system("sudo iptables --table mangle --policy FORWARD ACCEPT")
+ system("sudo iptables --table mangle --policy OUTPUT ACCEPT")
+
+ # Verify
+ puts "### FILTER ###"
+ system("sudo iptables --list --table filter")
+ puts
+
+ puts "### NAT ###"
+ system("sudo iptables --list --table nat")
+ puts
+
+ puts "### MANGLE ###"
+ system("sudo iptables --list --table mangle")
+ puts
+end
+
+
+
18 bin/rbenv-sudo
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+if [ "$(whoami)" != "root" ]; then
+ echo "You must be root to run this!"
+ exit 1
+fi
+
+if [ -d "$HOME/.rbenv" ]; then
+ export PATH="$HOME/.rbenv/bin:$PATH"
+fi
+if [ -d "/opt/rbenv" ]; then
+ export PATH="/opt/rbenv/bin:$PATH"
+ export RBENV_ROOT="/opt/rbenv"
+fi
+
+eval "$(rbenv init -)"
+
+$@
207 bin/ript
@@ -0,0 +1,207 @@
+#!/usr/bin/env ruby
+
+require 'pathname'
+$: << Pathname.new(__FILE__).parent.parent.join('lib').expand_path.to_s
+# so rules/ can be loaded
+$: << Pathname.new(__FILE__).parent.parent.expand_path.to_s
+$: << Dir.pwd
+require 'ript/dsl'
+
+if RUBY_VERSION =~ /^1.8/ then
+ puts "Ript requires Ruby 1.9 to run. Exiting."
+ exit
+end
+
+if Process.uid != 0 then
+ puts "You must run this as root!"
+ exit 1
+end
+
+if not ARGV[0]
+ puts "Usage: #{$0} <rulefile|directory>"
+ exit! 1
+end
+
+def types
+ {
+ :a => 'filter',
+ :d => 'nat',
+ :s => 'nat',
+ }
+end
+
+def current_chain_names_by_partition
+ # Collect the full iptables output
+ output = {}
+ types.each_pair do |type, table|
+ output[type] = `iptables --table #{table} --list partition-#{type} --numeric 2>&1 | grep -v 'No chain/target/match by that name'`.split("\n")
+ end
+
+
+ blacklist = %w(PREROUTING POSTROUTING OUTPUT INPUT FORWARD Chain target before-a after-a partition-a partition-d partition-s)
+ chains = {}
+
+ types.keys.each do |type|
+ chains[type] = {}
+ output[type].each do |line|
+ chain_name = line.split(/ /).first
+ next if blacklist.include? chain_name
+ partition = chain_name.split(/-/).first
+ chains[type][partition] ||= []
+ chains[type][partition] << chain_name
+ end
+ end
+
+ # Add the chains that aren't referenced anywhere to the end
+ ['nat', 'filter'].each do |table|
+ unlisted = `iptables --table #{table} --list --numeric 2>&1 | grep 'Chain'`.split("\n")
+ unlisted = unlisted.map {|l| l.split(/ /)[1]} - blacklist
+ unlisted.each do |chain_name|
+ partition, type = chain_name.split(/-/)
+ type = type[0].to_sym
+ chains[type][partition] ||= []
+ unless chains[type][partition].include? chain_name
+ chains[type][partition] << chain_name
+ end
+ end
+ end
+ chains
+end
+
+if ARGV[0] == 'rules'
+ if ARGV[1] == "generate" or ARGV[1] == "diff" then
+ path = Pathname.new(ARGV[2])
+
+ case
+ when path.directory?
+ path = (path + "**/*.rb").to_s
+ files = Pathname.glob(path)
+ files.each do |file|
+ require "#{file}"
+ end
+ when path.exist?
+ begin
+ require "#{path}"
+ rescue LoadError
+ puts "The specified rule file '#{path}' does not exist"
+ exit 160
+ end
+ else
+ puts "The specified rule file or directory '#{path}' does not exist"
+ exit 160
+ end
+
+ if `iptables --list partition-a --numeric 2>&1 | grep Chain` !~ /^Chain/
+ require 'ript/bootstrap'
+ puts "# bootstrap"
+ puts Ript::Bootstrap.partition.to_iptables
+ end
+
+ if ARGV[1] == "generate"
+ @partitions.each do |partition|
+ puts "# #{partition.name}-#{partition.id}"
+ puts partition.to_iptables
+ end
+ end
+
+ if ARGV[1] == "diff"
+ @partitions.each do |partition|
+ # We assume here that if a partition has a partition-a chain it will have all the others
+ chain_name = "#{partition.name}-#{partition.id.split('-').first}".sub(/-/, '-a')
+ unless current_chain_names_by_partition[:a].has_key?(partition.name) && current_chain_names_by_partition[:a][partition.name].include?(chain_name)
+ puts "# #{partition.name}-#{partition.id}"
+ puts partition.to_iptables
+ end
+ end
+ end
+
+ exit
+ end
+
+ if ARGV[1] == "apply" then
+ output = `#{$0} rules diff #{ARGV[2..-1].join(' ')} 2>&1`
+ puts "#{output}"
+ system("bash -c '#{output}'")
+
+ exit
+ end
+
+ if ARGV[1] == 'save' then
+ system('/sbin/iptables-save')
+ exit
+ end
+
+ puts "Usage: #{$0} <rules> <generate|diff|apply|save> <rulefile|directory>"
+ exit! 1
+end
+
+if ARGV[0] == "clean" then
+ if ARGV[1] == "diff" then
+ path = Pathname.new(ARGV[2])
+
+ case
+ when path.directory?
+ path = (path + "**/*.rb").to_s
+ files = Pathname.glob(path)
+ files.each do |file|
+ require "#{file}"
+ end
+ when path.exist?
+ begin
+ require "#{path}"
+ rescue LoadError => e
+ puts e
+ puts "The specified rule file '#{path}' does not exist"
+ exit 160
+ end
+ else
+ puts "The specified rule file or directory '#{path}' does not exist"
+ exit 160
+ end
+
+ current_chain_names_by_partition.each_pair do |type, partitions|
+ partitions.each_pair do |partition, chains|
+ # If we are in file mode don't remove other partitions
+ next if File.file?(path) && ! @partitions.map(&:name).include?(partition)
+ partition_obj = @partitions.find {|c| c.name == partition }
+ unless partition_obj.nil?
+ chain_name = "#{partition_obj.name}-#{type}#{partition_obj.id.split('-').first}"
+ chains = chains - [chain_name]
+ end
+
+ chains.uniq.each do |chain|
+ table = types[type]
+
+ clean_command = `iptables-save --table #{table} 2>&1 | grep -- '-A partition-#{type}' | grep -- '-j #{chain}'`.split("\n")
+ clean_command.map! {|line| "iptables --table #{table} #{line}" }
+ clean_command.map! {|line| line.gsub(" -A", " --delete") }
+ clean_command.map! {|line| line.gsub(" -s", " --source") }
+ clean_command.map! {|line| line.gsub(" -d", " --destination") }
+ clean_command.map! {|line| line.gsub(" -j", " --jump") }
+ clean_command.map! {|line| line.strip }
+ puts clean_command
+
+ puts "iptables --table #{table} --flush #{chain}"
+ puts "iptables --table #{table} --delete-chain #{chain}"
+
+ end
+ end
+ end
+ exit
+ end
+
+ if ARGV[1] == "apply" then
+ output = `#{$0} clean diff #{ARGV[2..-1].join(' ')} 2>&1`
+ puts "#{output}"
+ system("bash -c '#{output}'")
+
+ exit
+ end
+
+ puts "Usage: #{$0} <clean> <diff|apply> <rulefile|directory>"
+ exit! 1
+end
+
+
+puts "Usage: #{$0} <rules|clean|save> <generate|diff|apply> <rulefile|directory>"
+exit! 1
48 dist/init.d
@@ -0,0 +1,48 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: ript
+# Required-Start:
+# Required-Stop:
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: start and stop ript firewall
+# Description: Start, stop and save ript firewall
+### END INIT INFO
+
+# Author: John Ferlito <johnf@bulletproof.net>
+
+PATH=/sbin:/bin
+DESC="Restore ript firewall"
+NAME=ript
+IPTABLES_RESTORE=/sbin/iptables-restore
+IPTABLES_STATE=/var/lib/ript/iptables.state
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$IPTABLES_RESTORE" ] || exit 0
+
+# Exit if no rules
+[ -f "$IPTABLES_STATE" ] || exit 0
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+case "$1" in
+ start|restart|force-reload)
+ log_daemon_msg "Starting $DESC" "$NAME"
+ $IPTABLES_RESTORE < $IPTABLES_STATE
+ case "$?" in
+ 0|1) log_end_msg 0 ;;
+ 2) log_end_msg 1 ;;
+ esac
+ ;;
+ *)
+ echo "Usage: $SCRIPTNAME {start|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
16 examples/accept-multiple-from-and-to.rb
@@ -0,0 +1,16 @@
+partition "tootyfruity" do
+ label "apple", :address => "192.168.0.1"
+ label "blueberry", :address => "192.168.0.2"
+ label "cranberry", :address => "192.168.0.3"
+ label "eggplant", :address => "192.168.0.4"
+ label "fennel", :address => "192.168.0.5"
+ label "grapefruit", :address => "192.168.0.6"
+
+ accept "fruits of the forrest" do
+ protocols "tcp"
+ ports 22
+ from %w(apple blueberry cranberry eggplant fennel grapefruit)
+ to %w(apple blueberry cranberry eggplant fennel grapefruit)
+ end
+end
+
13 examples/accept-with-a-list-of-ports.rb
@@ -0,0 +1,13 @@
+partition "keepalived" do
+ label "primary lvs", :address => "172.16.0.216"
+ label "secondary lvs", :address => "172.16.0.217"
+ label "fw multicast", :address => "224.0.0.0/8"
+
+ accept "keepalive chatter on the fw multicast" do
+ protocols "tcp"
+ ports 80, 8600..8900
+ from "primary lvs", "secondary lvs"
+ to "fw multicast"
+ end
+end
+
14 examples/accept-with-specific-port-and-interface.rb
@@ -0,0 +1,14 @@
+partition "keepalived" do
+ label "foobar-lvs-04", :address => "192.168.0.76"
+ label "util-01", :address => "172.16.0.246"
+ label "util-02", :address => "172.16.0.247"
+
+ accept "ssh access between lvs/firewalls" do
+ interface "vlan+"
+ protocols "tcp"
+ ports 22
+ from "foobar-lvs-04", "util-01", "util-02"
+ to "foobar-lvs-04"
+ end
+end
+
11 examples/accept-without-specific-from.rb
@@ -0,0 +1,11 @@
+partition "joeblogsco" do
+
+ label "jbc.com", :address => "172.22.111.99"
+
+ accept "jbc.com web" do
+ ports 80, 443
+ to "jbc.com"
+ end
+
+end
+
12 examples/accept.rb
@@ -0,0 +1,12 @@
+partition "keepalived" do
+ label "primary lvs", :address => "172.16.0.216"
+ label "secondary lvs", :address => "172.16.0.217"
+ label "fw multicast", :address => "224.0.0.0/8"
+
+ accept "keepalive chatter on the fw multicast" do
+ protocols "vrrp"
+ from "primary lvs", "secondary lvs"
+ to "fw multicast"
+ end
+end
+
4 examples/basic.rb
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+
+partition "basic" do
+end
2  examples/dash-in-partition-name.rb
@@ -0,0 +1,2 @@
+partition "dash-in-my-name" do
+end
11 examples/drop.rb
@@ -0,0 +1,11 @@
+partition "bar" do
+ label "www.bar.com", :address => "172.23.0.95"
+ label "barprod-web-01", :address => "192.168.19.2"
+ label "localhost", :address => "127.0.0.1"
+
+ drop "localhost on www.bar.com" do
+ from "localhost"
+ to "www.bar.com"
+ end
+end
+
2  examples/duplicate-partition-names/foobar1.rb
@@ -0,0 +1,2 @@
+partition "foobar" do
+end
2  examples/duplicate-partition-names/foobar2.rb
@@ -0,0 +1,2 @@
+partition "foobar" do
+end
12 examples/errors-undefined-method-with-no-match.rb
@@ -0,0 +1,12 @@
+partition "keepalived" do
+ label "foobar-lvs-04", :address => "192.168.0.76"
+ label "util-01", :address => "172.16.0.246"
+ label "util-02", :address => "172.16.0.247"
+
+ accept "ssh access between lvs/firewalls with incorrect invocation" do
+ blahblahblah 22
+ from "foobar-lvs-04", "util-01", "util-02"
+ to "foobar-lvs-04"
+ end
+end
+
12 examples/errors-undefined-method.rb
@@ -0,0 +1,12 @@
+partition "keepalived" do
+ label "foobar-lvs-04", :address => "192.168.0.76"
+ label "util-01", :address => "172.16.0.246"
+ label "util-02", :address => "172.16.0.247"
+
+ accept "ssh access between lvs/firewalls with incorrect invocation" do
+ port 22
+ from "foobar-lvs-04", "util-01", "util-02"
+ to "foobar-lvs-04"
+ end
+end
+
16 examples/forward-dnat-with-different-destination-port.rb
@@ -0,0 +1,16 @@
+partition "foo" do
+ label "www.foo.com", :address => "172.23.0.88"
+ label "foo-web-01", :address => "192.168.38.1"
+ label "stage.foo.com", :address => "172.23.0.90"
+ label "foo-web-02", :address => "192.168.38.2"
+
+ rewrite "foo.com public website" do
+ ports 25, 80, 11, 22 => 9876, 443 => 4443
+ dnat "www.foo.com" => "foo-web-01"
+ end
+
+ rewrite "foo.com stage website" do
+ ports 22 => 9876, 443 => 4443
+ dnat "stage.foo.com" => "foo-web-02"
+ end
+end
11 examples/forward-dnat-with-explicit-from-and-port-mappings.rb
@@ -0,0 +1,11 @@
+partition "bar" do
+ label :jamdev, :address => "192.168.23.70/27"
+ label "www.bar.com", :address => "172.23.0.95"
+ label "barprod-blackhole-01", :address => "192.168.27.66"
+
+ rewrite "bar" do
+ ports 139 => 2011
+ from :jamdev
+ dnat "www.bar.com" => "barprod-blackhole-01"
+ end
+end
11 examples/forward-dnat-with-explicit-from-and-ports.rb
@@ -0,0 +1,11 @@
+partition "bar" do
+ label :jamdev, :address => "192.168.23.70/27"
+ label "www.bar.com", :address => "172.23.0.95"
+ label "barprod-blackhole-01", :address => "192.168.27.66"
+
+ rewrite "bar" do
+ ports 82
+ from :jamdev
+ dnat "www.bar.com" => "barprod-blackhole-01"
+ end
+end
11 examples/forward-dnat-with-explicit-from.rb
@@ -0,0 +1,11 @@
+partition "bar" do
+ label :jamdev, :address => "192.168.23.70/27"
+ label "www.bar.com", :address => "172.23.0.95"
+ label "barprod-blackhole-01", :address => "192.168.27.66"
+
+ rewrite "bar" do
+ ports 80
+ from :jamdev
+ dnat "www.bar.com" => "barprod-blackhole-01"
+ end
+end
15 examples/forward-dnat-with-explicit-protocols.rb
@@ -0,0 +1,15 @@
+partition 'cpm' do
+
+ label 'internal', :address => '192.168.0.133'
+ label 'external', :address => '172.18.88.33'
+ label 'office', :address => '172.19.4.55'
+
+ rewrite 'incoming dns' do
+ protocols 'udp', 'tcp'
+ ports 53
+ from 'office'
+ to 'external'
+ dnat 'external' => 'internal'
+ end
+
+end
13 examples/forward-dnat-with-multiple-froms.rb
@@ -0,0 +1,13 @@
+partition "joeblogsco" do
+ label :office1, :address => "1.2.3.4"
+ label :office2, :address => "4.5.6.7"
+ label :office3, :address => "7.8.9.10"
+ label "www.joeblogsco.com", :address => "172.19.10.99"
+ label "joeblogsco-app-01", :address => "192.168.27.66"
+
+ rewrite "bar" do
+ ports 80
+ from :office1, :office2, :office3
+ dnat "www.joeblogsco.com" => "joeblogsco-app-01"
+ end
+end
10 examples/forward-dnat-with-multiple-ports.rb
@@ -0,0 +1,10 @@
+partition "bar" do
+ label "www.bar.com", :address => "172.23.0.95"
+ label "barprod-web-01", :address => "192.168.19.2"
+
+ rewrite "bar.com public website" do
+ ports 80, 22
+ dnat "www.bar.com" => "barprod-web-01"
+ end
+end
+
15 examples/forward-dnat-with-multiple-sources.rb
@@ -0,0 +1,15 @@
+partition "bar" do
+ label :jamdev, :address => "192.168.23.70/27"
+
+ label "www.bar.com", :address => "172.23.0.95"
+ label "secure.bar.com", :address => "172.23.0.96"
+ label "static.bar.com", :address => "172.23.0.97"
+ label "barprod-proxy-01", :address => "192.168.27.88"
+
+ rewrite "bar" do
+ ports 80
+ dnat [ "www.bar.com",
+ "secure.bar.com",
+ "static.bar.com" ] => "barprod-proxy-01"
+ end
+end
16 examples/forward-dnat.rb
@@ -0,0 +1,16 @@
+partition "bar" do
+ label "www.bar.com", :address => "172.23.0.95"
+ label "barprod-web-01", :address => "192.168.19.2"
+ label "barprod-web-02", :address => "192.168.19.4"
+
+ rewrite "bar.com public website" do
+ ports 80
+ dnat "www.bar.com" => "barprod-web-01"
+ end
+
+ rewrite "bar.com public website" do
+ ports 22
+ dnat "www.bar.com" => "barprod-web-02"
+ end
+end
+
16 examples/forward-snat-with-explicit-from.rb
@@ -0,0 +1,16 @@
+partition "bar" do
+ label "www.bar.com", :address => "172.23.0.95"
+ label "api.bar.com", :address => "172.24.0.99"
+ label "barprod-api-01", :address => "10.55.0.45"
+ label "bar prod subnet", :address => "10.55.0.0/24"
+
+
+ # FIXME: should things with a netmask be inserted lower into the chain?
+ rewrite "bar" do
+ snat "barprod-api-01" => "api.bar.com"
+ end
+
+ rewrite "bar prod outbound" do
+ snat "bar prod subnet" => "www.bar.com"
+ end
+end
13 examples/forward-snat-with-multiple-sources.rb
@@ -0,0 +1,13 @@
+partition "bar" do
+ label "bar uat subnet", :address => "10.33.0.0/24"
+ label "bar stage subnet", :address => "10.44.0.0/24"
+ label "bar prod subnet", :address => "10.55.0.0/24"
+
+ label "www.bar.com", :address => "172.23.0.95"
+
+ rewrite "bar" do
+ snat [ "bar uat subnet",
+ "bar stage subnet",
+ "bar prod subnet" ] => "www.bar.com"
+ end
+end
9 examples/forward-snat.rb
@@ -0,0 +1,9 @@
+partition "bar" do
+ label "www.bar.com", :address => "172.23.0.95"
+ label "bar subnet", :address => "10.30.0.0/24"
+
+ rewrite "bar.com public website" do
+ snat "bar subnet" => "www.bar.com"
+ end
+end
+
12 examples/log-and-accept.rb
@@ -0,0 +1,12 @@
+partition "keepalived" do
+ label "primary lvs", :address => "172.16.0.216"
+ label "secondary lvs", :address => "172.16.0.217"
+ label "fw multicast", :address => "224.0.0.0/8"
+
+ accept "keepalive chatter on the fw multicast", :log => true do
+ protocols "vrrp"
+ from "primary lvs", "secondary lvs"
+ to "fw multicast"
+ end
+end
+
11 examples/log-and-drop.rb
@@ -0,0 +1,11 @@
+partition "bar" do
+ label "www.bar.com", :address => "172.23.0.95"
+ label "barprod-web-01", :address => "192.168.19.2"
+ label "localhost", :address => "127.0.0.1"
+
+ drop "localhost on www.bar.com", :log => true do
+ from "localhost"
+ to "www.bar.com"
+ end
+end
+
10 examples/log-dnat.rb
@@ -0,0 +1,10 @@
+partition "bar" do
+ label "www.bar.com", :address => "172.23.0.95"
+ label "barprod-web-01", :address => "192.168.19.2"
+
+ rewrite "bar.com public website", :log => true do
+ ports 80, 22
+ dnat "www.bar.com" => "barprod-web-01"
+ end
+end
+
13 examples/log-snat.rb
@@ -0,0 +1,13 @@
+partition "bar" do
+ label "bar uat subnet", :address => "10.33.0.0/24"
+ label "bar stage subnet", :address => "10.44.0.0/24"
+ label "bar prod subnet", :address => "10.55.0.0/24"
+
+ label "www.bar.com", :address => "172.23.0.95"
+
+ rewrite "bar", :log => true do
+ snat [ "bar uat subnet",
+ "bar stage subnet",
+ "bar prod subnet" ] => "www.bar.com"
+ end
+end
11 examples/log.rb
@@ -0,0 +1,11 @@
+partition "bar" do
+ label "www.bar.com", :address => "172.23.0.95"
+ label "barprod-web-01", :address => "192.168.19.2"
+ label "localhost", :address => "127.0.0.1"
+
+ log "localhost on www.bar.com" do
+ from "localhost"
+ to "www.bar.com"
+ end
+end
+
15 examples/missing-address-definition-in-destination.rb
@@ -0,0 +1,15 @@
+partition "bar" do
+ label "www.bar.com", :address => "172.23.0.95"
+ label "barprod-web-01", :address => "192.168.19.2"
+
+ rewrite "bar.com public website" do
+ ports 80
+ dnat "www.bar.com" => "barprod-web-01"
+ end
+
+ rewrite "bar.com public website" do
+ ports 22
+ dnat "www.bar.com" => "barprod-web-02"
+ end
+end
+
15 examples/missing-address-definition-in-from.rb
@@ -0,0 +1,15 @@
+partition "bar" do
+ label "www.bar.com", :address => "172.23.0.95"
+ label "barprod-web-01", :address => "192.168.19.2"
+
+ rewrite "bar.com public website" do
+ ports 80
+ dnat "www.bar.com" => "barprod-web-01"
+ end
+
+ drop "bad guy" do
+ from "bad guy"
+ to "www.bar.com"
+ end
+end
+
14 examples/multiple-partitions-in-this-file.rb
@@ -0,0 +1,14 @@
+%w(bar foo blum frub).each do |name|
+ partition(name) do
+ label :jamdev, :address => "192.168.23.70/27"
+ label "www.bar.com", :address => "172.23.0.95"
+ label "barprod-blackhole-01", :address => "192.168.27.66"
+
+ rewrite "bar" do
+ ports 80
+ from :jamdev
+ dnat "www.bar.com" => "barprod-blackhole-01"
+ end
+ end
+end
+
11 examples/multiple-partitions/bar.rb
@@ -0,0 +1,11 @@
+partition "bar" do
+ label "www.bar.com", :address => "172.23.0.95", :interface => "vlan44"
+ label "barprod-web-01", :address => "192.168.19.2"
+ label "localhost", :address => "127.0.0.1"
+
+ drop "localhost on www.bar.com" do
+ from "localhost"
+ to "www.bar.com"
+ end
+end
+
17 examples/multiple-partitions/foo.rb
@@ -0,0 +1,17 @@
+partition "foo" do
+ label "www.foo.com", :address => "172.23.0.88", :interface => "vlan44"
+ label "foo-web-01", :address => "192.168.38.1"
+ label "stage.foo.com", :address => "172.23.0.90", :interface => "vlan44"
+ label "foo-web-02", :address => "192.168.38.2"
+
+ rewrite "foo.com public website" do
+ ports 25, 80, 11, 22 => 9876, 443 => 4443
+ dnat "www.foo.com" => "foo-web-01"
+ end
+
+ rewrite "foo.com stage website" do
+ ports 22 => 9876, 443 => 4443
+ dnat "stage.foo.com" => "foo-web-02"
+ end
+end
+
2  examples/partition-name-exactly-20-characters.rb
@@ -0,0 +1,2 @@
+partition "name_exactly_20_char" do
+end
2  examples/partition-name-longer-than-20-characters.rb
@@ -0,0 +1,2 @@
+partition "name_longer_than_21_characters" do
+end
10 examples/postclean.rb
@@ -0,0 +1,10 @@
+partition "supercow" do
+ label "cow", :address => "172.27.1.2"
+ label "person", :address => "172.29.2.3"
+
+ accept "moo" do
+ from "cow"
+ to "person"
+ end
+end
+
10 examples/preclean.rb
@@ -0,0 +1,10 @@
+partition "supercow" do
+ label "cow", :address => "172.27.1.1"
+ label "person", :address => "172.29.2.2"
+
+ accept "moo" do
+ from "cow"
+ to "person"
+ end
+end
+
9 examples/raw-with-chain-deletion.rb
@@ -0,0 +1,9 @@
+partition "boilerplate" do
+ raw <<-RAW
+# Delete created chains
+iptables -X
+iptables -t nat -X
+iptables -t mangle -X
+ RAW
+end
+
9 examples/raw-with-flush.rb
@@ -0,0 +1,9 @@
+partition "boilerplate" do
+ raw <<-RAW
+# Flush everything
+iptables -t filter -F
+iptables -t nat -F
+iptables -t mangle -F
+ RAW
+end
+
50 examples/raw.rb
@@ -0,0 +1,50 @@
+partition "setup" do
+ raw <<-RAW
+####################
+# policy #
+####################
+iptables --policy INPUT DROP
+iptables --policy OUTPUT DROP
+iptables --policy FORWARD DROP
+iptables --table mangle --policy PREROUTING ACCEPT
+iptables --table mangle --policy OUTPUT ACCEPT
+
+####################
+# before #
+####################
+# Clean all traffic by sending it through a "before" chain.
+iptables --new-chain before-a
+
+iptables --insert INPUT 1 --jump before-a
+iptables --insert OUTPUT 1 --jump before-a
+iptables --insert FORWARD 1 --jump before-a
+
+# ICMP cleaning
+iptables --append before-a --protocol ICMP --icmp-type echo-reply --jump ACCEPT
+iptables --append before-a --protocol ICMP --icmp-type destination-unreachable --jump ACCEPT
+iptables --append before-a --protocol ICMP --icmp-type source-quench --jump ACCEPT
+iptables --append before-a --protocol ICMP --icmp-type echo-request --jump ACCEPT
+iptables --append before-a --protocol ICMP --icmp-type time-exceeded --jump ACCEPT
+iptables --append before-a --protocol ICMP --icmp-type parameter-problem --jump ACCEPT
+iptables --append before-a --protocol ICMP --jump LOG --log-prefix "INVALID_ICMP " --log-level debug
+iptables --append before-a --protocol ICMP --jump DROP
+
+# State cleaning
+iptables --append before-a --match state --state INVALID --jump LOG --log-prefix "INVALID_STATE " --log-level debug
+iptables --append before-a --match state --state INVALID --jump DROP
+iptables --append before-a --protocol TCP --match state --state ESTABLISHED,RELATED --jump ACCEPT
+iptables --append before-a --protocol UDP --match state --state ESTABLISHED,RELATED --jump ACCEPT
+
+# Allow loopback
+iptables --insert before-a --protocol ALL --in-interface lo --jump ACCEPT
+iptables --insert before-a --protocol ALL --out-interface lo --jump ACCEPT
+
+####################
+# after #
+####################
+# Clean all traffic by sending it through an "after" chain.
+iptables --new-chain after-a
+iptables --append after-a --jump LOG --log-prefix "END_DROP " --log-level debug
+ RAW
+end
+
11 examples/reject.rb
@@ -0,0 +1,11 @@
+partition "bar" do
+ label "www.bar.com", :address => "172.23.0.95"
+ label "barprod-web-01", :address => "192.168.19.2"
+ label "localhost", :address => "127.0.0.1"
+
+ reject "localhost on www.bar.com" do
+ from "localhost"
+ to "www.bar.com"
+ end
+end
+
2  examples/space-in-partition-name.rb
@@ -0,0 +1,2 @@
+partition "space in my name" do
+end
115 features/cli.feature
@@ -0,0 +1,115 @@
+Feature: Ript cli utility
+
+ @sudo @timeout-10
+ Scenario: Check rules to apply
+ Given I have no iptables rules loaded
+ When I run `ript rules diff examples/basic.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain basic-d\w+
+ iptables --table nat --new-chain basic-s\w+
+ iptables --table filter --new-chain basic-a\w+
+ """
+ Then the created chain name in all tables should match
+
+ @sudo @timeout-10
+ Scenario: Apply rules
+ Given I have no iptables rules loaded
+ When I run `ript rules diff examples/basic.rb`
+ Then the output from "ript rules diff examples/basic.rb" should match:
+ """
+ iptables --table nat --new-chain basic-d\w+
+ iptables --table nat --new-chain basic-s\w+
+ iptables --table filter --new-chain basic-a\w+
+ """
+ When I run `ript rules apply examples/basic.rb`
+ Then the output from "ript rules diff examples/basic.rb" should match:
+ """
+ iptables --table nat --new-chain basic-d\w+
+ iptables --table nat --new-chain basic-s\w+
+ iptables --table filter --new-chain basic-a\w+
+ """
+ When I run `ript rules diff examples/basic.rb `
+ Then the output from "ript rules diff examples/basic.rb " should contain exactly:
+ """
+ """
+ Then the created chain name in all tables should match
+
+ @sudo @timeout-10
+ Scenario: Clean rules
+ Given I have no iptables rules loaded
+ When I run `ript rules apply examples/preclean.rb`
+ Then the output from "ript rules apply examples/preclean.rb" should match:
+ """
+ iptables --table filter --new-chain partition-a
+ iptables --table filter --insert INPUT 1 --jump partition-a
+ iptables --table filter --insert OUTPUT 1 --jump partition-a
+ iptables --table filter --insert FORWARD 1 --jump partition-a
+ iptables --table nat --new-chain partition-d
+ iptables --table nat --insert PREROUTING 1 --jump partition-d
+ iptables --table nat --new-chain partition-s
+ iptables --table nat --insert POSTROUTING 1 --jump partition-s
+
+
+ # supercow-\w+
+ iptables --table nat --new-chain supercow-d\w+
+ iptables --table nat --new-chain supercow-s\w+
+ iptables --table filter --new-chain supercow-a\w+
+ iptables --table filter --append supercow-a\w+ --protocol TCP --destination 172.29.2.2 --source 172.27.1.1 --jump ACCEPT
+ iptables --table filter --insert partition-a --destination 172.29.2.2 --jump supercow-a\w+
+ """
+ When I run `ript rules apply examples/postclean.rb`
+ Then the output from "ript rules apply examples/postclean.rb" should match:
+ """
+ # supercow-\w+
+ iptables --table nat --new-chain supercow-d\w+
+ iptables --table nat --new-chain supercow-s\w+
+ iptables --table filter --new-chain supercow-a\w+
+ iptables --table filter --append supercow-a\w+ --protocol TCP --destination 172.29.2.3 --source 172.27.1.2 --jump ACCEPT
+ iptables --table filter --insert partition-a --destination 172.29.2.3 --jump supercow-a\w+
+ """
+ When I run `ript rules diff examples/postclean.rb`
+ Then the output from "ript rules diff examples/postclean.rb" should contain exactly:
+ """
+ """
+ When I run `ript clean apply examples/postclean.rb `
+ Then the output from "ript clean apply examples/postclean.rb " should match:
+ """
+ iptables --table filter --delete partition-a --destination 172.29.2.2/32 --jump supercow-a\w+
+ iptables --table filter --flush supercow-a\w+
+ iptables --table filter --delete-chain supercow-a\w+
+ iptables --table nat --flush supercow-d\w+
+ iptables --table nat --delete-chain supercow-d\w+
+ iptables --table nat --flush supercow-s\w+
+ iptables --table nat --delete-chain supercow-s\w+
+ """
+ When I run `ript clean diff examples/postclean.rb`
+ Then the output from "ript clean diff examples/postclean.rb" should contain exactly:
+ """
+ """
+
+ @sudo @timeout-10
+ Scenario: raw rules should only apply once
+ Given I have no iptables rules loaded
+ When I run `ript rules apply examples/raw.rb`
+ Then the output from "ript rules apply examples/raw.rb" should match:
+ """
+ iptables --new-chain before-a
+ """
+ When I run `ript rules diff examples/raw.rb`
+ Then the output from "ript rules diff examples/raw.rb" should contain exactly:
+ """
+ """
+
+ @sudo @timeout-10
+ Scenario: Rule saving works
+ Given I have no iptables rules loaded
+ When I run `ript rules save`
+ Then the output from "ript rules save" should match:
+ """
+ \*filter
+ :INPUT ACCEPT \[\d+:\d+\]
+ :FORWARD ACCEPT \[\d+:\d+\]
+ :OUTPUT ACCEPT \[\d+:\d+\]
+ COMMIT
+ """
107 features/dsl/errors.feature
@@ -0,0 +1,107 @@
+Feature: Error handling
+ To ensure that rules apply cleanly
+ Ript should validate user input
+ And fail gracefully
+
+ @errors @name
+ Scenario: Name errors - undefined method
+ # should verify no spaces or dashes
+ When I run `ript rules generate examples/errors-undefined-method.rb`
+ Then the output should match:
+ """
+ You tried using the '.+' method on line \d+ in .+/errors-undefined-method.rb
+ This method doesn't exist in the DSL. Did you mean:
+
+ - ports
+
+ Aborting.
+ """
+ When I run `ript rules generate examples/errors-undefined-method-with-no-match.rb`
+ Then the output should match:
+ """
+ You tried using the '.+' method on line \d+ in .+/errors-undefined-method-with-no-match.rb
+ This method doesn't exist in the DSL. There aren't any other methods with similar names. :-\(
+ Aborting.
+ """
+
+ @errors @parse @duplicate
+ Scenario: Parse errors - duplicate partition name
+ # should verify no spaces or dashes
+ When I run `ript rules generate examples/duplicate-partition-names/`
+ Then the output should match:
+ """
+ Error: Partition name '\w+' is already defined!
+ """
+
+ @errors @parse
+ Scenario: Parse errors - bad characters in partition name
+ # should verify no spaces or dashes
+ When I run `ript rules generate examples/space-in-partition-name.rb`
+ Then the output should match:
+ """
+ Error: Partition name '.+' can't contain whitespace.
+ """
+ When I run `ript rules generate examples/dash-in-partition-name.rb`
+ Then the output should match:
+ """
+ Error: Partition name '.+' can't contain dashes
+ """
+
+ @errors @parse
+ Scenario: Parse errors - partition name longer than characters
+ When I run `ript rules generate examples/partition-name-longer-than-20-characters.rb`
+ Then the output should match:
+ """
+ Error: Partition name '.+' cannot be longer than 20 characters.
+ """
+ When I run `ript rules generate examples/partition-name-exactly-20-characters.rb`
+ Then the output should match:
+ """
+ name_exactly_20_char
+ """
+
+
+ @errors @parse
+ Scenario: Parse errors - spaces and dashes
+ When I run `ript rules generate examples/space-in-partition-name.rb`
+ Then the output should contain:
+ """
+ Partition name 'space in my name' can't contain whitespace
+ """
+ When I run `ript rules generate examples/dash-in-partition-name.rb`
+ Then the output should contain:
+ """
+ Partition name 'dash-in-my-name' can't contain dashes ('-')
+ """
+
+ @errors @parse
+ Scenario: Parse errors - missing address definition
+ When I run `ript rules generate examples/missing-address-definition-in-destination.rb`
+ Then the output should contain:
+ """
+ Address 'barprod-web-02' (a destination) isn't defined
+ """
+
+ @errors
+ Scenario: Parse errors - missing address definition
+ When I run `ript rules generate examples/missing-address-definition-in-from.rb`
+ Then the output should contain:
+ """
+ Address 'bad guy' (a from) isn't defined
+ """
+
+ @errors
+ Scenario: Load errors - missing rule file
+ When I run `ript rules generate examples/non-existent-lalalalala.rb`
+ Then the output should match:
+ """
+ The specified rule file or directory 'examples/non-existent-lalalalala.rb' does not exist
+ """
+
+ @errors @parse
+ Scenario: Multiple partition definitions in the same file
+ When I run `ript rules generate examples/multiple-partitions-in-this-file.rb`
+ Then the output should match:
+ """
+ Multiple partition definitions are not permitted in the same file.
+ """
187 features/dsl/filter.feature
@@ -0,0 +1,187 @@
+Feature: Ript DSL
+
+ Scenario: Basic partition
+ When I run `ript rules generate examples/basic.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain basic-d\w+
+ iptables --table nat --new-chain basic-s\w+
+ iptables --table filter --new-chain basic-a\w+
+ """
+ Then the created chain name in all tables should match
+
+ @filter @drop
+ Scenario: Drop someone
+ When I run `ript rules generate examples/drop.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain bar-d\w+
+ iptables --table nat --new-chain bar-s\w+
+ iptables --table filter --new-chain bar-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --insert partition-a --destination 172.23.0.95 --jump bar-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --append bar-a\w+ --protocol TCP --destination 172.23.0.95 --source 127.0.0.1 --jump DROP
+ """
+ Then the created chain name in all tables should match
+
+ @filter @accept
+ Scenario: Accept someone
+ When I run `ript rules generate examples/accept.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain keepalived-d\w+
+ iptables --table nat --new-chain keepalived-s\w+
+ iptables --table filter --new-chain keepalived-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --insert partition-a --destination 224.0.0.0/8 --jump keepalived-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --append keepalived-a\w+ --protocol vrrp --destination 224.0.0.0/8 --source 172.16.0.216 --jump ACCEPT
+ iptables --table filter --append keepalived-a\w+ --protocol vrrp --destination 224.0.0.0/8 --source 172.16.0.217 --jump ACCEPT
+ """
+ Then the created chain name in all tables should match
+
+ @filter @accept
+ Scenario: Accept someone with a specific port and interface
+ When I run `ript rules generate examples/accept-with-specific-port-and-interface.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain keepalived-d\w+
+ iptables --table nat --new-chain keepalived-s\w+
+ iptables --table filter --new-chain keepalived-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --insert partition-a --destination 192.168.0.76 --jump keepalived-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --append keepalived-a\w+ --in-interface vlan\+ --protocol tcp --dport 22 --destination 192.168.0.76 --source 192.168.0.76 --jump ACCEPT
+ """
+ Then the created chain name in all tables should match
+
+ @filter @reject
+ Scenario: Reject someone
+ When I run `ript rules generate examples/reject.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain bar-d\w+
+ iptables --table nat --new-chain bar-s\w+
+ iptables --table filter --new-chain bar-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --insert partition-a --destination 172.23.0.95 --jump bar-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --append bar-a\w+ --protocol TCP --destination 172.23.0.95 --source 127.0.0.1 --jump REJECT
+ """
+ Then the created chain name in all tables should match
+
+ @filter @log
+ Scenario: Log someone
+ When I run `ript rules generate examples/log.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain bar-d\w+
+ iptables --table nat --new-chain bar-s\w+
+ iptables --table filter --new-chain bar-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --insert partition-a --destination 172.23.0.95 --jump bar-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --append bar-a\w+ --protocol TCP --destination 172.23.0.95 --source 127.0.0.1 --jump LOG
+ """
+ Then the created chain name in all tables should match
+
+ @filter @accept @port-range
+ Scenario: Accept a list of ports
+ When I run `ript rules generate examples/accept-with-a-list-of-ports.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain keepalived-d\w+
+ iptables --table nat --new-chain keepalived-s\w+
+ iptables --table filter --new-chain keepalived-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --insert partition-a --destination 224.0.0.0/8 --jump keepalived-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --append keepalived-a\w+ --protocol tcp --dport 80 --destination 224.0.0.0/8 --source 172.16.0.216 --jump ACCEPT
+ iptables --table filter --append keepalived-a\w+ --protocol tcp --dport 8600:8900 --destination 224.0.0.0/8 --source 172.16.0.216 --jump ACCEPT
+ """
+ Then the created chain name in all tables should match
+
+ @filter @accept @multiple
+ Scenario: Accept multiple from and to
+ When I run `ript rules generate examples/accept-multiple-from-and-to.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain tootyfruity-d\w+
+ iptables --table nat --new-chain tootyfruity-s\w+
+ iptables --table filter --new-chain tootyfruity-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --insert partition-a --destination 192.168.0.1 --jump tootyfruity-a\w+
+ iptables --table filter --insert partition-a --destination 192.168.0.2 --jump tootyfruity-a\w+
+ iptables --table filter --insert partition-a --destination 192.168.0.3 --jump tootyfruity-a\w+
+ iptables --table filter --insert partition-a --destination 192.168.0.4 --jump tootyfruity-a\w+
+ iptables --table filter --insert partition-a --destination 192.168.0.5 --jump tootyfruity-a\w+
+ iptables --table filter --insert partition-a --destination 192.168.0.6 --jump tootyfruity-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --append tootyfruity-a\w+ --protocol tcp --dport 22 --destination 192.168.0.1 --source 192.168.0.1 --jump ACCEPT
+ iptables --table filter --append tootyfruity-a\w+ --protocol tcp --dport 22 --destination 192.168.0.2 --source 192.168.0.1 --jump ACCEPT
+ iptables --table filter --append tootyfruity-a\w+ --protocol tcp --dport 22 --destination 192.168.0.3 --source 192.168.0.1 --jump ACCEPT
+ iptables --table filter --append tootyfruity-a\w+ --protocol tcp --dport 22 --destination 192.168.0.4 --source 192.168.0.1 --jump ACCEPT
+ iptables --table filter --append tootyfruity-a\w+ --protocol tcp --dport 22 --destination 192.168.0.5 --source 192.168.0.1 --jump ACCEPT
+ iptables --table filter --append tootyfruity-a\w+ --protocol tcp --dport 22 --destination 192.168.0.6 --source 192.168.0.1 --jump ACCEPT
+ iptables --table filter --append tootyfruity-a\w+ --protocol tcp --dport 22 --destination 192.168.0.1 --source 192.168.0.2 --jump ACCEPT
+ iptables --table filter --append tootyfruity-a\w+ --protocol tcp --dport 22 --destination 192.168.0.2 --source 192.168.0.2 --jump ACCEPT
+ iptables --table filter --append tootyfruity-a\w+ --protocol tcp --dport 22 --destination 192.168.0.3 --source 192.168.0.2 --jump ACCEPT
+ iptables --table filter --append tootyfruity-a\w+ --protocol tcp --dport 22 --destination 192.168.0.4 --source 192.168.0.2 --jump ACCEPT
+ iptables --table filter --append tootyfruity-a\w+ --protocol tcp --dport 22 --destination 192.168.0.5 --source 192.168.0.2 --jump ACCEPT
+ iptables --table filter --append tootyfruity-a\w+ --protocol tcp --dport 22 --destination 192.168.0.6 --source 192.168.0.2 --jump ACCEPT
+ """
+ Then the created chain name in all tables should match
+
+ @filter @accept @regression
+ Scenario: Accept someone without a specific from
+ When I run `ript rules generate examples/accept-without-specific-from.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain joeblogsco-d\w+
+ iptables --table nat --new-chain joeblogsco-s\w+
+ iptables --table filter --new-chain joeblogsco-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --append joeblogsco-a\w+ --protocol TCP --dport 80 --destination 172.22.111.99 --source 0.0.0.0/0 --jump ACCEPT
+ iptables --table filter --append joeblogsco-a\w+ --protocol TCP --dport 443 --destination 172.22.111.99 --source 0.0.0.0/0 --jump ACCEPT
+ """
+ Then the output should match:
+ """
+ iptables --table filter --insert partition-a --destination 172.22.111.99 --jump joeblogsco-a\w+
+ """
+ Then the created chain name in all tables should match
+
+ @filter @regression
+ Scenario: Always include protocol when specifying port
+ When I generate rules for packet filtering
+ Then I should see a protocol specified when a port is specified
114 features/dsl/logging.feature
@@ -0,0 +1,114 @@
+Feature: Logging
+ When debugging complex problems
+ A user may want to know
+ If certain rules are being used
+
+ @log @filter @accept
+ Scenario: Log and accept
+ When I run `ript rules generate examples/log-and-accept.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain keepalived-d\w+
+ iptables --table nat --new-chain keepalived-s\w+
+ iptables --table filter --new-chain keepalived-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --insert partition-a --destination 224.0.0.0/8 --jump keepalived-a\w+
+ iptables --table filter --insert partition-a --destination 224.0.0.0/8 --jump LOG
+ """
+ Then the output should match:
+ """
+ iptables --table filter --append keepalived-a\w+ --protocol vrrp --destination 224.0.0.0/8 --source 172.16.0.216 --jump LOG
+ iptables --table filter --append keepalived-a\w+ --protocol vrrp --destination 224.0.0.0/8 --source 172.16.0.216 --jump ACCEPT
+ iptables --table filter --append keepalived-a\w+ --protocol vrrp --destination 224.0.0.0/8 --source 172.16.0.217 --jump LOG
+ iptables --table filter --append keepalived-a\w+ --protocol vrrp --destination 224.0.0.0/8 --source 172.16.0.217 --jump ACCEPT
+ """
+ Then the created chain name in all tables should match
+
+ @log @filter @drop
+ Scenario: Log and drop
+ When I run `ript rules generate examples/log-and-drop.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain bar-d\w+
+ iptables --table nat --new-chain bar-s\w+
+ iptables --table filter --new-chain bar-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table filter --insert partition-a --destination 172.23.0.95 --jump bar-a\w+
+ iptables --table filter --insert partition-a --destination 172.23.0.95 --jump LOG
+ """
+ Then the output should match:
+ """
+ iptables --table filter --append bar-a\w+ --protocol TCP --destination 172.23.0.95 --source 127.0.0.1 --jump LOG
+ iptables --table filter --append bar-a\w+ --protocol TCP --destination 172.23.0.95 --source 127.0.0.1 --jump DROP
+ """
+ Then the created chain name in all tables should match
+
+ @log @nat @dnat
+ Scenario: Logging complex DNAT
+ When I run `ript rules generate examples/log-dnat.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain bar-d\w+
+ iptables --table nat --new-chain bar-s\w+
+ iptables --table filter --new-chain bar-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table nat --insert partition-d --destination 172.23.0.95 --jump bar-d\w+
+ iptables --table nat --insert partition-d --destination 172.23.0.95 --jump LOG
+ """
+ Then the output should match:
+ """
+ iptables --table nat --append bar-d\w+ --protocol TCP --destination 172.23.0.95 --dport 80 --jump LOG --to-destination 192.168.19.2
+ iptables --table nat --append bar-d\w+ --protocol TCP --destination 172.23.0.95 --dport 80 --jump DNAT --to-destination 192.168.19.2
+ iptables --table filter --append bar-a\w+ --protocol TCP --destination 192.168.19.2 --dport 80 --jump LOG
+ iptables --table filter --append bar-a\w+ --protocol TCP --destination 192.168.19.2 --dport 80 --jump ACCEPT
+ iptables --table nat --append bar-d\w+ --protocol TCP --destination 172.23.0.95 --dport 22 --jump LOG --to-destination 192.168.19.2
+ iptables --table nat --append bar-d\w+ --protocol TCP --destination 172.23.0.95 --dport 22 --jump DNAT --to-destination 192.168.19.2
+ iptables --table filter --append bar-a\w+ --protocol TCP --destination 192.168.19.2 --dport 22 --jump LOG
+ iptables --table filter --append bar-a\w+ --protocol TCP --destination 192.168.19.2 --dport 22 --jump ACCEPT
+ """
+ Then the created chain name in all tables should match
+
+ @log @nat @snat
+ Scenario: Logging complex SNAT
+ When I run `ript rules generate examples/log-snat.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain bar-d\w+
+ iptables --table nat --new-chain bar-s\w+
+ iptables --table filter --new-chain bar-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table nat --append bar-s\w+ --source 10.33.0.0/24 --jump LOG --to-source 172.23.0.95
+ iptables --table nat --append bar-s\w+ --source 10.33.0.0/24 --jump SNAT --to-source 172.23.0.95
+ iptables --table filter --append bar-a\w+ --source 10.33.0.0/24 --jump LOG
+ iptables --table filter --append bar-a\w+ --source 10.33.0.0/24 --jump ACCEPT
+ iptables --table nat --append bar-s\w+ --source 10.44.0.0/24 --jump LOG --to-source 172.23.0.95
+ iptables --table nat --append bar-s\w+ --source 10.44.0.0/24 --jump SNAT --to-source 172.23.0.95
+ iptables --table filter --append bar-a\w+ --source 10.44.0.0/24 --jump LOG
+ iptables --table filter --append bar-a\w+ --source 10.44.0.0/24 --jump ACCEPT
+ iptables --table nat --append bar-s\w+ --source 10.55.0.0/24 --jump LOG --to-source 172.23.0.95
+ iptables --table nat --append bar-s\w+ --source 10.55.0.0/24 --jump SNAT --to-source 172.23.0.95
+ iptables --table filter --append bar-a\w+ --source 10.55.0.0/24 --jump LOG
+ iptables --table filter --append bar-a\w+ --source 10.55.0.0/24 --jump ACCEPT
+ iptables --table nat --insert partition-s --source 10.33.0.0/24 --jump bar-s\w+
+ iptables --table nat --insert partition-s --source 10.33.0.0/24 --jump LOG
+ iptables --table nat --insert partition-s --source 10.44.0.0/24 --jump bar-s\w+
+ iptables --table nat --insert partition-s --source 10.44.0.0/24 --jump LOG
+ iptables --table nat --insert partition-s --source 10.55.0.0/24 --jump bar-s\w+
+ iptables --table nat --insert partition-s --source 10.55.0.0/24 --jump LOG
+ iptables --table filter --insert partition-a --source 10.33.0.0/24 --jump bar-a\w+
+ iptables --table filter --insert partition-a --source 10.33.0.0/24 --jump LOG
+ iptables --table filter --insert partition-a --source 10.44.0.0/24 --jump bar-a\w+
+ iptables --table filter --insert partition-a --source 10.44.0.0/24 --jump LOG
+ iptables --table filter --insert partition-a --source 10.55.0.0/24 --jump bar-a\w+
+ iptables --table filter --insert partition-a --source 10.55.0.0/24 --jump LOG
+ """
+ Then the created chain name in all tables should match
+
271 features/dsl/nat.feature
@@ -0,0 +1,271 @@
+Feature: Ript DSL
+
+ Scenario: Basic partition
+ When I run `ript rules generate examples/basic.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain basic-d\w+
+ iptables --table nat --new-chain basic-s\w+
+ iptables --table filter --new-chain basic-a\w+
+ """
+ Then the created chain name in all tables should match
+
+ @nat @dnat
+ Scenario: Basic DNAT forward
+ When I run `ript rules generate examples/forward-dnat.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain bar-d\w+
+ iptables --table nat --new-chain bar-s\w+
+ iptables --table filter --new-chain bar-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table nat --append bar-d\w+ --protocol TCP --destination 172.23.0.95 --dport 80 --jump DNAT --to-destination 192.168.19.2
+ iptables --table filter --append bar-a\w+ --protocol TCP --destination 192.168.19.2 --dport 80 --jump ACCEPT
+ """
+ Then the output should match:
+ """
+ iptables --table nat --insert partition-d --destination 172.23.0.95 --jump bar-d\w+
+ iptables --table filter --insert partition-a --destination 192.168.19.2 --jump bar-a\w+
+ """
+ Then the created chain name in all tables should match
+
+ @nat @dnat
+ Scenario: DNAT forward with multiple ports
+ When I run `ript rules generate examples/forward-dnat-with-multiple-ports.rb`
+ Then the output should match:
+ """
+ iptables --table nat --new-chain bar-d\w+
+ iptables --table nat --new-chain bar-s\w+
+ iptables --table filter --new-chain bar-a\w+
+ """
+ Then the output should match:
+ """
+ iptables --table nat --insert partition-d --destination 172.23.0.95 --jump bar-d\w+
+ """
+ Then the output should match:
+ """
+ iptables --table nat --append bar-d\w+ --protocol TCP --destination 172.23.0.95 --dport 80 --jump DNAT --to-destination 192.168.19.2
+ """
+ Then the output should match:
+ """
+ iptables --table nat --append bar-d\w+ --protocol TCP --destination 172.23.0.95 --dport 22 --jump DNAT --to-destination 192.168.19.2