From: Sharif Nassar Date: Sat, 12 Nov 2011 13:31:11 +0000 (-0800) Subject: (#10984) Initial creation of class firewall X-Git-Tag: 0.1.0~45^2 X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=a60634b2fc475fe65da7d2153de7eb724ce1385c;p=puppet-modules%2Fpuppetlabs-firewall.git (#10984) Initial creation of class firewall * 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. --- diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..699a9da --- /dev/null +++ b/.dir-locals.el @@ -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 e43c1c5..b66330d 100644 --- 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: diff --git a/README.markdown b/README.markdown index a144deb..c3ef47e 100644 --- a/README.markdown +++ b/README.markdown @@ -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. diff --git a/lib/puppet/provider/firewall/ip6tables.rb b/lib/puppet/provider/firewall/ip6tables.rb index 58c6874..f7bfc7e 100644 --- a/lib/puppet/provider/firewall/ip6tables.rb +++ b/lib/puppet/provider/firewall/ip6tables.rb @@ -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 diff --git a/lib/puppet/provider/firewall/iptables.rb b/lib/puppet/provider/firewall/iptables.rb index 8bd1f62..6c63322 100644 --- a/lib/puppet/provider/firewall/iptables.rb +++ b/lib/puppet/provider/firewall/iptables.rb @@ -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 index 0000000..957c80a --- /dev/null +++ b/manifests/init.pp @@ -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 index 0000000..4624927 --- /dev/null +++ b/spec/classes/firewall_spec.rb @@ -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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8e9fbdb..68c773f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -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 index 0000000..5d5645d --- /dev/null +++ b/templates/debian/iptables-persistent.erb @@ -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 index 0000000..8f140dc --- /dev/null +++ b/templates/redhat/ip6tables.erb @@ -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 index 0000000..d74b305 --- /dev/null +++ b/test/init.pp @@ -0,0 +1,3 @@ +node default { + class { 'firewall': } +}