]> review.fuel-infra Code Review - puppet-modules/puppetlabs-firewall.git/commitdiff
(#10984) Initial creation of class firewall
authorSharif Nassar <sharif@mrwacky.com>
Sat, 12 Nov 2011 13:31:11 +0000 (05:31 -0800)
committerSharif Nassar <sharif@mrwacky.com>
Tue, 6 Dec 2011 18:15:56 +0000 (10:15 -0800)
* Add Exec[firewall-persist] to save rules.  This allows the host to
  have iptables rules on reboot, before puppet runs.
* Debian hates you.  Add iptables init scripts for loading iptables at
  boot on releases of Debian that do not have them already.
* Add brains to the iptables/ip6tables providers to ensure kernel modules
  are loaded.

.dir-locals.el [new file with mode: 0644]
LICENSE
README.markdown
lib/puppet/provider/firewall/ip6tables.rb
lib/puppet/provider/firewall/iptables.rb
manifests/init.pp [new file with mode: 0644]
spec/classes/firewall_spec.rb [new file with mode: 0755]
spec/spec_helper.rb
templates/debian/iptables-persistent.erb [new file with mode: 0755]
templates/redhat/ip6tables.erb [new file with mode: 0644]
test/init.pp [new file with mode: 0644]

diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644 (file)
index 0000000..699a9da
--- /dev/null
@@ -0,0 +1,8 @@
+;;; Directory Local Variables
+;;; See Info node `(emacs) Directory Variables' for more information.
+
+((puppet-mode
+  (puppet-indent-level . 2))
+ (ruby-mode
+  (ruby-indent-level . 2)))
+
diff --git a/LICENSE b/LICENSE
index e43c1c5cc95d0dc820a0a80374d3e4b8de9dd69b..b66330d45273389d2dad32fa5c951e1894f1d15b 100644 (file)
--- a/LICENSE
+++ b/LICENSE
@@ -2,6 +2,7 @@ Puppet Firewall Module - Puppet module for managing Firewalls
 
 Copyright (C) 2011 Puppet Labs, Inc.
 Copyright (C) 2011 Jonathan Boyett
+Copyright (C) 2011 Media Temple, Inc.
 
 Some of the iptables code was taken from puppet-iptables which was:
 
index a144deb606ffc2991ae3f6c579446fac6329e8ac..c3ef47e9813cd191cde741dc4c7c54260bc8a3ce 100644 (file)
@@ -1,4 +1,4 @@
-## puppetlabs-firewall module
+# puppetlabs-firewall module
 
 ## User Guide
 
@@ -67,6 +67,17 @@ need to run Puppet on the master first:
 You may also need to restart Apache, although this shouldn't always be the
 case.
 
+Add the following to your site.pp to make sure class firewall can
+persist iptables rules across reboots:
+
+    Firewall {
+        notify  => Exec['firewall-persist'],
+    }
+
+Load the firewall class that provides these helper methods:
+
+    class { 'firewall': }
+
 ### Examples
 
 Basic accept ICMP request example:
@@ -93,19 +104,6 @@ Source NAT example (perfect for a virtualization host):
       table  => 'nat',
     }
 
-You can make firewall rules persistent with the following iptables example:
-
-    exec { "persist-firewall":
-      command => $operatingsystem ? {
-        "debian" => "/sbin/iptables-save > /etc/iptables/rules.v4",
-        /(RedHat|CentOS)/ => "/sbin/iptables-save > /etc/sysconfig/iptables",
-      }
-      refreshonly => true,
-    }
-    Firewall {
-      notify => Exec["persist-firewall"]
-    }
-
 If you wish to ensure any reject rules are executed last, try using stages.
 The following example shows the creation of a class which is where your
 last rules should run, this however should belong in a puppet module.
index 58c68741be956ee17c844f727a449c72c0479b1a..f7bfc7ebbef8caaea9eda1ee627db3f8c6e59987 100644 (file)
@@ -50,4 +50,12 @@ Puppet::Type.type(:firewall).provide :ip6tables, :parent => :iptables, :source =
     :proto, :gid, :uid, :sport, :dport, :port, :name, :state, :icmp, :limit, :burst, :jump,
     :todest, :tosource, :toports, :log_level, :log_prefix, :reject]
 
+  ## Work-around for older vintages of iptables, where iptables-save
+  ## returns error if the kernel modules are not loaded.  Listing
+  ## out the current rules with iptables proper autoloads the kernel modules.
+  def self.iptables_init
+    if ! File.exists?('/proc/net/ip6_tables_names') 
+      iptables '-nL'
+    end
+  end
 end
index 8bd1f62cc4e76d62099d1d17174bc3e1133164c9..6c63322f6f6874f29aafb570aff7d8f1816738c1 100644 (file)
@@ -92,6 +92,8 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir
     rules = []
     counter = 1
 
+    self.iptables_init
+    
     # String#lines would be nice, but we need to support Ruby 1.8.5
     iptables_save.split("\n").each do |line|
       unless line =~ /^\#\s+|^\:\S+|^COMMIT/
@@ -108,6 +110,15 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir
     rules
   end
 
+  ## Work-around for older vintages of iptables, where iptables-save
+  ## returns error if the kernel modules are not loaded.  Listing
+  ## out the current rules with iptables proper autoloads the kernel modules.
+  def self.iptables_init
+    if ! File.exists?('/proc/net/ip_tables_names') 
+      iptables '-nL'
+    end
+  end
+    
   def self.rule_to_hash(line, table, counter)
     hash = {}
     keys = []
diff --git a/manifests/init.pp b/manifests/init.pp
new file mode 100644 (file)
index 0000000..957c80a
--- /dev/null
@@ -0,0 +1,187 @@
+# Class: firewall
+#
+# This module manages firewalls.
+#
+# Parameters:
+#
+# Actions:
+#
+# Sets up some resources to manage iptables configs on Linux.
+# on various flavours of Linux.
+# 1. A firewall-persist exec to make rebooting safe.
+# 2. Debian hates you and has no provisions for managing iptables by default.  Fixes that.
+#
+# Requires:
+#
+# Sample Usage:
+#
+# Put this in site.pp to ensure proper operation:
+# Firewall {
+#     notify  => Exec['firewall-persist'],
+# }
+#
+class firewall {
+
+  Exec {
+    path => [ '/bin', '/sbin' ],
+  }
+
+  case $kernel {
+    'Linux': {
+
+      case $operatingsystem {
+        Debian: {
+          ## The iptables in Lenny definitely works.
+          ## Older versions not tested.
+          ## Meanwhile, Debian 'testing' will break.
+          if $lsbmajdistrelease >= 5 {
+            $firewall_supports_ipv6 = true
+          }
+
+          file { '/etc/iptables':
+            ensure => directory,
+            group  => 'root',
+            mode   => '0750',
+            owner  => 'root',
+          }
+
+          $ip6tables_rules = '/etc/iptables/rules.v6'
+
+          ## Squeeze 'iptables-persistent' package has unique rules file.
+          case $lsbmajdistrelease {
+            6:       { $iptables_rules  = '/etc/iptables/rules' }
+            default: { $iptables_rules  = '/etc/iptables/rules.v4' }
+          }
+
+          ## Lenny has no intrinsic ability to manage iptables persistence.
+          ## So we magick it up here. Note: this persists IPv6 rules..
+          if $lsbmajdistrelease <= 5 {
+            file { '/etc/init.d/iptables-persistent':
+              content => template('firewall/debian/iptables-persistent.erb'),
+              group   => 'root',
+              mode    => '0755',
+              owner   => 'root',
+            }
+
+            $service_persist_requires = File['/etc/init.d/iptables-persistent']
+          }
+          else {
+            package { 'iptables-persistent':
+              ensure => present,
+            }
+            $service_persist_requires = Package['iptables-persistent']
+          }
+
+          service { 'iptables-persistent':
+            enable  => true,
+            require => $service_persist_requires,
+          }
+
+          ## Squeeze 'iptables-persistent' package is ignorant of IPv6.
+          ## So, we slap in a separate IPv6 persistence script just for it.
+          if $firewall_supports_ipv6 {
+
+            file { $ip6tables_rules:
+              group   => 'root',
+              mode    => '0600',
+              owner   => 'root',
+              require => Exec['firewall-persist'],
+            }
+
+            if $lsbmajdistrelease == 6 {
+              file { '/etc/init.d/ip6tables-persistent':
+                content => template('firewall/debian/iptables-persistent.erb'),
+                group   => 'root',
+                mode    => '0755',
+                owner   => 'root',
+              }
+
+              service { 'ip6tables-persistent':
+                enable  => true,
+                require => File['/etc/init.d/ip6tables-persistent'],
+              }
+
+            }
+          }
+        }
+        ## Since this RedHat section makes assumptions about release version numbering
+        ## It only makes sense for RHEL-alike OSes.
+        RedHat,CentOS,CloudLinux: {
+          ## ip6tables in CentOS 5.x does not support comments
+          ## The provider needs comments, so we play dumb on older versions.
+          if $lsbmajdistrelease >= 6 {
+            $firewall_supports_ipv6 = true
+          }
+
+          $ip6tables_rules = '/etc/sysconfig/ip6tables'
+          $iptables_rules  = '/etc/sysconfig/iptables'
+
+          package { 'iptables':
+            ensure => present,
+          }
+
+          service { 'iptables':
+            enable => true,
+          }
+
+          package { 'iptables-ipv6':
+            ensure => present,
+          }
+
+          service { 'ip6tables':
+            enable => true,
+          }
+
+          ## RHEL supports IPv6, but older versions do not support comments.
+          ## The provider requires comments, so we just force a REJECT on all filter chains there.
+          if $firewall_supports_ipv6 {
+            file { $ip6tables_rules:
+              group   => 'root',
+              mode    => '0600',
+              owner   => 'root',
+              require => Exec['firewall-persist'],
+            }
+          }
+          else {
+            file { $ip6tables_rules:
+              content => template('firewall/redhat/ip6tables.erb'),
+              group   => 'root',
+              mode    => '0600',
+              owner   => 'root',
+              require => Exec['firewall-persist'],
+            }
+            exec { 'set-ipv6-iptables-policy':
+              command     => '/sbin/service ip6tables restart',
+              subscribe   => File[$ip6tables_rules],
+              refreshonly => true,
+            }
+            warning("On '${lsbdistdescription}', the firewall provider does not support ip6tables. Setting a default REJECT rule")
+          }
+        }
+        default: { fail("$operatingsystem is not supported by the firewall class") }
+      }
+
+      file { $iptables_rules:
+        group   => 'root',
+        mode    => '0600',
+        owner   => 'root',
+        require => Exec['firewall-persist'],
+      }
+
+      ## Tell site.pp (see docs) to notify this exec for all firewall rules
+      ## Thus, ensuring good rules at boot time.
+      exec { 'firewall-persist':
+        command     => "iptables-save |sed -e '/^#/ d' > ${iptables_rules}; ip6tables-save |sed -e '/^#/ d' > ${ip6tables_rules}",
+        refreshonly => true,
+      }
+
+    }
+    default: {
+      warning("'firewall-persist' is currently a noop on ${kernel}")
+      exec { 'firewall-persist':
+        command     => '/bin/true',
+        refreshonly => true,
+      }
+    }
+  }
+}
diff --git a/spec/classes/firewall_spec.rb b/spec/classes/firewall_spec.rb
new file mode 100755 (executable)
index 0000000..4624927
--- /dev/null
@@ -0,0 +1,97 @@
+#!/usr/bin/env rspec
+require 'spec_helper'
+include PuppetSpec::Files
+
+describe 'firewall', :type => :class do
+
+  before do
+    @puppetdir = tmpdir('firewall')
+    manifestdir = File.join(@puppetdir, 'manifests')
+    Dir.mkdir(manifestdir)
+    FileUtils.touch(File.join(manifestdir, 'site.pp'))
+    Puppet[:confdir] = @puppetdir
+  end
+
+  after do
+    FileUtils.remove_entry_secure(@puppetdir)
+  end
+
+  ## Same results will apply for CentOS & CloudLinux
+  describe "RedHat generic tests" do
+    let(:facts) {
+      {
+        :operatingsystem => 'RedHat',
+        :kernel => 'Linux',
+        :lsbmajdistrelease => 5,
+      }
+    }
+    it { should contain_exec('firewall-persist') }
+    it { should contain_file('/etc/sysconfig/ip6tables').with_mode('0600') }
+    it { should contain_file('/etc/sysconfig/iptables').with_mode('0600') }
+    it { should contain_package('iptables').with_ensure('present') }
+    it { should contain_package('iptables-ipv6').with_ensure('present') }
+    it { should contain_service('iptables').with_enable(true) }
+    it { should contain_service('ip6tables').with_enable(true) }
+  end
+
+  ## Same results will apply for CentOS & CloudLinux
+  describe "RedHat 5.x tests" do
+    let(:facts) {
+      {
+        :operatingsystem => 'RedHat',
+        :kernel => 'Linux',
+        :lsbmajdistrelease => 5,
+      }
+    }
+    it { should contain_exec('set-ipv6-iptables-policy') }
+  end
+
+  describe "Debian generic tests" do
+    let(:facts) {
+      {
+        :operatingsystem => 'Debian',
+        :kernel => 'Linux',
+      }
+    }
+    it { should contain_exec('firewall-persist') }
+    it { should contain_file('/etc/iptables/rules.v6').with_mode('0600') }
+    it { should contain_service('iptables-persistent').with_enable(true) }
+  end
+  describe "Debian Lenny tests" do
+    let(:facts) {
+      {
+        :operatingsystem => 'Debian',
+        :kernel => 'Linux',
+        :lsbmajdistrelease => 5,
+      }
+    }
+    it { should contain_file('/etc/init.d/iptables-persistent') }
+    it { should contain_file('/etc/iptables/rules.v4').with_mode('0600') }
+
+  end
+  describe "Debian Squeeze tests" do
+    let(:facts) {
+      {
+        :operatingsystem => 'Debian',
+        :kernel => 'Linux',
+        :lsbmajdistrelease => 6,
+      }
+    }
+    it { should contain_file('/etc/init.d/ip6tables-persistent') }
+    it { should contain_file('/etc/iptables/rules').with_mode('0600') }
+    it { should contain_package('iptables-persistent').with_ensure('present') }
+    it { should contain_service('ip6tables-persistent').with_enable(true) }
+  end
+  describe "Debian Wheezy tests" do
+    let(:facts) {
+      {
+        :operatingsystem => 'Debian',
+        :kernel => 'Linux',
+        :lsbmajdistrelease => 7,
+      }
+    }
+    it { should contain_file('/etc/iptables/rules.v4').with_mode('0600') }
+    it { should contain_file('/etc/iptables/rules.v6').with_mode('0600') }
+    it { should contain_package('iptables-persistent').with_ensure('present') }
+  end
+end
index 8e9fbdba42be9f91db35de91c08141195460600f..68c773f60957cbdf74078f079dca83f4d9ca822d 100644 (file)
@@ -8,6 +8,7 @@ require 'puppet'
 require 'mocha'
 gem 'rspec', '>=2.0.0'
 require 'rspec/expectations'
+require 'rspec-puppet'
 
 # So everyone else doesn't have to include this base constant.
 module PuppetSpec
@@ -31,6 +32,8 @@ end
 RSpec.configure do |config|
   include PuppetSpec::Fixtures
 
+  config.module_path = File.join(File.dirname(__FILE__), '../../')
+
   config.mock_with :mocha
 
   config.before :each do
@@ -47,7 +50,6 @@ RSpec.configure do |config|
 
     # Set the confdir and vardir to gibberish so that tests
     # have to be correctly mocked.
-    Puppet[:confdir] = "/dev/null"
     Puppet[:vardir] = "/dev/null"
 
     # Avoid opening ports to the outside world
diff --git a/templates/debian/iptables-persistent.erb b/templates/debian/iptables-persistent.erb
new file mode 100755 (executable)
index 0000000..5d5645d
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+<% version = lsbmajdistrelease.to_i -%>
+### BEGIN INIT INFO
+# Provides:          <% if version <= 5 %>iptables<% else %>ip6tables<%end%>-persistent
+# Required-Start:    mountkernfs $local_fs
+# Required-Stop:     $local_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# X-Start-Before:    $network
+# X-Stop-After:      $network
+# Short-Description: Set up iptables rules
+### END INIT INFO
+
+. /lib/lsb/init-functions
+
+rc=0
+IP6TABLES_RULES=<%= ip6tables_rules %>
+<% if version <= 5 -%>
+IPTABLES_RULES=<%= iptables_rules %>
+<% end -%>
+
+case "$1" in
+    start|restart|reload|force-reload)
+        log_action_begin_msg "Loading iptables rules"
+
+<% if version <= 5 -%>
+        if [ -f $IPTABLES_RULES ]; then
+            log_action_cont_msg " IPv4"
+            iptables-restore < $IPTABLES_RULES 2> /dev/null
+            if [ $? -ne 0 ]; then
+                rc=1
+            fi
+        fi
+<% end -%>
+
+        if [ -f $IP6TABLES_RULES ]; then
+            log_action_cont_msg " IPv6"
+            ip6tables-restore < $IP6TABLES_RULES 2> /dev/null
+            if [ $? -ne 0 ]; then
+                rc=1
+            fi
+        fi
+
+        log_action_end_msg $rc
+        ;;
+    stop)
+        ;;
+    *)
+        echo "Usage: $0 {start|restart|reload|force-reload|save}" >&2
+        exit 1
+        ;;
+esac
+
+exit $rc
diff --git a/templates/redhat/ip6tables.erb b/templates/redhat/ip6tables.erb
new file mode 100644 (file)
index 0000000..8f140dc
--- /dev/null
@@ -0,0 +1,8 @@
+*filter
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+-A INPUT -j REJECT --reject-with icmp6-port-unreachable
+-A FORWARD -j REJECT --reject-with icmp6-port-unreachable
+-A OUTPUT -j REJECT --reject-with icmp6-port-unreachable
+COMMIT
diff --git a/test/init.pp b/test/init.pp
new file mode 100644 (file)
index 0000000..d74b305
--- /dev/null
@@ -0,0 +1,3 @@
+node default {
+  class { 'firewall': }
+}