From 000a586c1f51e7e351637b73ea6b98e249125056 Mon Sep 17 00:00:00 2001 From: Dan Carley Date: Tue, 26 Feb 2013 21:07:01 +0000 Subject: [PATCH] Firewall and firewallchain persistence Call the necessary commands from the provider to persist rules between reboots. Tested on the following distros: - CentOS 5.8 - CentOS 6.3 - Ubuntu 10.04 - Ubuntu 12.04 - Debian 6 Cavaets: - Persistence may fail on the first run if Firewall resources are actioned before the Package resource. - Older iptables-persistent doesn't support the restoration of ip6tables. - ebtables cannot be restored. --- README.markdown | 38 +++++-------- lib/facter/iptables_persistent_version.rb | 13 +++++ lib/puppet/provider/firewall/ip6tables.rb | 2 + lib/puppet/provider/firewall/iptables.rb | 3 + .../provider/firewallchain/iptables_chain.rb | 3 + lib/puppet/type/firewallchain.rb | 10 ++++ lib/puppet/util/firewall.rb | 55 +++++++++++++++++++ manifests/init.pp | 2 +- spec/classes/firewall_linux_debian_spec.rb | 4 +- spec/classes/firewall_linux_redhat_spec.rb | 2 +- .../iptables_persistent_version_spec.rb | 32 +++++++++++ 11 files changed, 136 insertions(+), 28 deletions(-) create mode 100644 lib/facter/iptables_persistent_version.rb create mode 100644 spec/unit/facter/iptables_persistent_version_spec.rb diff --git a/README.markdown b/README.markdown index 5003a42..0686715 100644 --- a/README.markdown +++ b/README.markdown @@ -79,33 +79,15 @@ case. ### Recommended Setup At the moment you need to provide some setup outside of what we provide in the -module to support proper ordering, purging and firewall peristence. +module to support proper ordering and purging. -So It is recommended that you provide the following in top scope somewhere -(such as your site.pp): +Persistence of rules between reboots is handled automatically for the +supported distributions listed below. Although there are known issues with +ip6tables on older Debian/Ubuntu and ebtables. - # Always persist firewall rules - exec { 'persist-firewall': - command => $operatingsystem ? { - 'debian' => '/sbin/iptables-save > /etc/iptables/rules.v4', - /(RedHat|CentOS)/ => '/sbin/iptables-save > /etc/sysconfig/iptables', - }, - refreshonly => true, - } +It is recommended that you provide the following in top scope somewhere +(such as your site.pp): - # These defaults ensure that the persistence command is executed after - # every change to the firewall, and that pre & post classes are run in the - # right order to avoid potentially locking you out of your box during the - # first puppet run. - Firewall { - notify => Exec['persist-firewall'], - before => Class['my_fw::post'], - require => Class['my_fw::pre'], - } - Firewallchain { - notify => Exec['persist-firewall'], - } - # Purge unmanaged firewall resources # # This will clear any existing rules, and make sure that only rules @@ -113,6 +95,14 @@ So It is recommended that you provide the following in top scope somewhere resources { "firewall": purge => true } + + # These defaults ensure that the pre & post classes are run in the right + # order to avoid potentially locking you out of your box during the + # first puppet run. + Firewall { + before => Class['my_fw::post'], + require => Class['my_fw::pre'], + } You also need to declare the 'my_fw::pre' & 'my_fw::post' classes so that dependencies are satisfied. This can be achieved using an External Node diff --git a/lib/facter/iptables_persistent_version.rb b/lib/facter/iptables_persistent_version.rb new file mode 100644 index 0000000..2bae97a --- /dev/null +++ b/lib/facter/iptables_persistent_version.rb @@ -0,0 +1,13 @@ +Facter.add(:iptables_persistent_version) do + confine :operatingsystem => %w{Debian Ubuntu} + setcode do + cmd = "dpkg-query -Wf '${Version}' iptables-persistent" + version = Facter::Util::Resolution.exec(cmd) + + if version.nil? or !version.match(/\d+\.\d+/) + nil + else + version + end + end +end diff --git a/lib/puppet/provider/firewall/ip6tables.rb b/lib/puppet/provider/firewall/ip6tables.rb index 5759920..3654046 100644 --- a/lib/puppet/provider/firewall/ip6tables.rb +++ b/lib/puppet/provider/firewall/ip6tables.rb @@ -29,6 +29,8 @@ Puppet::Type.type(:firewall).provide :ip6tables, :parent => :iptables, :source = ip6tables_save(*args) end + @protocol = "IPv6" + @resource_map = { :burst => "--limit-burst", :destination => "-d", diff --git a/lib/puppet/provider/firewall/iptables.rb b/lib/puppet/provider/firewall/iptables.rb index b34e736..b193cd1 100644 --- a/lib/puppet/provider/firewall/iptables.rb +++ b/lib/puppet/provider/firewall/iptables.rb @@ -36,6 +36,8 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir mark_flag = '--set-xmark' end + @protocol = "IPv4" + @resource_map = { :burst => "--limit-burst", :destination => "-d", @@ -100,6 +102,7 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir notice("Properties changed - updating rule") update end + persist_iptables(self.class.instance_variable_get(:@protocol)) @property_hash.clear end diff --git a/lib/puppet/provider/firewallchain/iptables_chain.rb b/lib/puppet/provider/firewallchain/iptables_chain.rb index 0efb55d..049fb4e 100644 --- a/lib/puppet/provider/firewallchain/iptables_chain.rb +++ b/lib/puppet/provider/firewallchain/iptables_chain.rb @@ -1,4 +1,6 @@ Puppet::Type.type(:firewallchain).provide :iptables_chain do + include Puppet::Util::Firewall + @doc = "Iptables chain provider" has_feature :iptables_chain @@ -96,6 +98,7 @@ Puppet::Type.type(:firewallchain).provide :iptables_chain do def flush debug("[flush]") + persist_iptables(@resource[:name].match(Nameformat)[3]) # Clear the property hash so we re-initialize with updated values @property_hash.clear end diff --git a/lib/puppet/type/firewallchain.rb b/lib/puppet/type/firewallchain.rb index 0bab44a..7eade4b 100644 --- a/lib/puppet/type/firewallchain.rb +++ b/lib/puppet/type/firewallchain.rb @@ -1,4 +1,14 @@ +# This is a workaround for bug: #4248 whereby ruby files outside of the normal +# provider/type path do not load until pluginsync has occured on the puppetmaster +# +# In this case I'm trying the relative path first, then falling back to normal +# mechanisms. This should be fixed in future versions of puppet but it looks +# like we'll need to maintain this for some time perhaps. +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__),"..","..")) +require 'puppet/util/firewall' + Puppet::Type.newtype(:firewallchain) do + include Puppet::Util::Firewall @doc = <<-EOS This type provides the capability to manage rule chains for firewalls. diff --git a/lib/puppet/util/firewall.rb b/lib/puppet/util/firewall.rb index cdf20aa..b388bca 100644 --- a/lib/puppet/util/firewall.rb +++ b/lib/puppet/util/firewall.rb @@ -129,4 +129,59 @@ module Puppet::Util::Firewall end return nil end + + def persist_iptables(proto) + debug("[persist_iptables]") + + # Basic normalisation for older Facter + os_key = Facter.value(:osfamily) + os_key ||= case Facter.value(:operatingsystem) + when 'RedHat', 'CentOS', 'Fedora' + 'RedHat' + when 'Debian', 'Ubuntu' + 'Ubuntu' + else + Facter.value(:operatingsystem) + end + + # Older iptables-persistent doesn't provide save action. + if os_key == 'Debian' + persist_ver = Facter.value(:iptables_persistent_version) + if (persist_ver and Puppet::Util::Package.versioncmp(persist_ver, '0.5.0') < 0) + os_key = 'Debian_manual' + end + end + + cmd = case os_key.to_sym + when :RedHat + case proto.to_sym + when :IPv4 + %w{/sbin/service iptables save} + when :IPv6 + %w{/sbin/service ip6tables save} + end + when :Debian + case proto.to_sym + when :IPv4, :IPv6 + %w{/usr/sbin/service iptables-persistent save} + end + when :Debian_manual + case proto.to_sym + when :IPv4 + ["/bin/sh", "-c", "/sbin/iptables-save > /etc/iptables/rules"] + end + end + + # Catch unsupported OSs from the case statement above. + if cmd.nil? + debug('firewall: Rule persistence is not supported for this type/OS') + return + end + + begin + execute(cmd) + rescue Puppet::ExecutionFailure => detail + warning("Unable to persist firewall rules: #{detail}") + end + end end diff --git a/manifests/init.pp b/manifests/init.pp index 2aa6155..9730d3b 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -1,7 +1,7 @@ # Class: firewall # # Manages the installation of packages for operating systems that are -# currently supported by the firewall type. +# currently supported by the firewall type. # class firewall { case $::kernel { diff --git a/spec/classes/firewall_linux_debian_spec.rb b/spec/classes/firewall_linux_debian_spec.rb index 2d7581e..3215ce7 100644 --- a/spec/classes/firewall_linux_debian_spec.rb +++ b/spec/classes/firewall_linux_debian_spec.rb @@ -2,11 +2,11 @@ require 'spec_helper' describe 'firewall::linux::debian' do it { should contain_package('iptables-persistent').with( - :ensure => 'present', + :ensure => 'present' )} it { should contain_service('iptables-persistent').with( :ensure => nil, :enable => 'true', - :require => 'Package[iptables-persistent]', + :require => 'Package[iptables-persistent]' )} end diff --git a/spec/classes/firewall_linux_redhat_spec.rb b/spec/classes/firewall_linux_redhat_spec.rb index 89f30fc..a4a307b 100644 --- a/spec/classes/firewall_linux_redhat_spec.rb +++ b/spec/classes/firewall_linux_redhat_spec.rb @@ -3,6 +3,6 @@ require 'spec_helper' describe 'firewall::linux::redhat' do it { should contain_service('iptables').with( :ensure => 'running', - :enable => 'true', + :enable => 'true' )} end diff --git a/spec/unit/facter/iptables_persistent_version_spec.rb b/spec/unit/facter/iptables_persistent_version_spec.rb new file mode 100644 index 0000000..2c79a1b --- /dev/null +++ b/spec/unit/facter/iptables_persistent_version_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe "Facter::Util::Fact iptables_persistent_version" do + before { Facter.clear } + let(:dpkg_cmd) { "dpkg-query -Wf '${Version}' iptables-persistent" } + + { + "Debian" => "0.0.20090701", + "Ubuntu" => "0.5.3ubuntu2", + }.each do |os, ver| + describe "#{os} package installed" do + before { + Facter.fact(:operatingsystem).stubs(:value).returns(os) + Facter::Util::Resolution.stubs(:exec).with(dpkg_cmd).returns(ver) + } + it { Facter.fact(:iptables_persistent_version).value.should == ver } + end + end + + describe 'Ubuntu package not installed' do + before { + Facter.fact(:operatingsystem).stubs(:value).returns("Ubuntu") + Facter::Util::Resolution.stubs(:exec).with(dpkg_cmd).returns(nil) + } + it { Facter.fact(:iptables_persistent_version).value.should be_nil } + end + + describe 'CentOS not supported' do + before { Facter.fact(:operatingsystem).stubs(:value).returns("CentOS") } + it { Facter.fact(:iptables_persistent_version).value.should be_nil } + end +end -- 2.45.2