]> review.fuel-infra Code Review - puppet-modules/puppetlabs-firewall.git/commitdiff
Add ipv6 fragmentation matchers and generify known_boolean handling.
authorGeorg Koester <georg.koester@gmail.com>
Fri, 12 Apr 2013 07:58:09 +0000 (00:58 -0700)
committerGeorg Koester <georg.koester@gmail.com>
Wed, 10 Jul 2013 09:23:52 +0000 (02:23 -0700)
Adds tests for ipv6, too.

ip6tables handles fragmentation differently. There's a special
module and a couple of matchers which are all needed to
implement a stateless firewall correctly.

known_boolean handling with etc has been generified.
The known_boolean functionality was partly tailored
to the :socket feature.

lib/puppet/provider/firewall/ip6tables.rb
lib/puppet/provider/firewall/iptables.rb
lib/puppet/type/firewall.rb
spec/fixtures/ip6tables/conversion_hash.rb [new file with mode: 0644]
spec/unit/puppet/provider/iptables_spec.rb

index 74e568808d134ed3bb4b06cd39015cbc9011ab65..8e34fbe1705e6a402ad92b8944d87c83775c65e8 100644 (file)
@@ -15,6 +15,9 @@ Puppet::Type.type(:firewall).provide :ip6tables, :parent => :iptables, :source =
   has_feature :mark
   has_feature :tcp_flags
   has_feature :pkttype
+  has_feature :ishasmorefrags
+  has_feature :islastfrag
+  has_feature :isfirstfrag
 
   optional_commands({
     :ip6tables      => 'ip6tables',
@@ -55,7 +58,10 @@ Puppet::Type.type(:firewall).provide :ip6tables, :parent => :iptables, :source =
     :toports => "--to-ports",
     :tosource => "--to-source",
     :uid => "-m owner --uid-owner",
-    :pkttype => "-m pkttype --pkt-type"
+    :pkttype => "-m pkttype --pkt-type",
+    :ishasmorefrags => "-m frag --fragid 0 --fragmore",
+    :islastfrag => "-m frag --fragid 0 --fraglast",
+    :isfirstfrag => "-m frag --fragid 0 --fragfirst",
   }
 
   # Create property methods dynamically
@@ -73,8 +79,15 @@ Puppet::Type.type(:firewall).provide :ip6tables, :parent => :iptables, :source =
   # we need it to properly parse and apply rules, if the order of resource
   # changes between puppet runs, the changed rules will be re-applied again.
   # This order can be determined by going through iptables source code or just tweaking and trying manually
+  # (Note: on my CentOS 6.4 ip6tables-save returns -m frag on the place
+  # I put it when calling the command. So compability with manual changes
+  # not provided with current parser [georg.koester])
   @resource_list = [:table, :source, :destination, :iniface, :outiface,
-    :proto, :gid, :uid, :sport, :dport, :port, :pkttype, :name, :state, :icmp, :limit, :burst, :jump,
+    :proto, :ishasmorefrags, :islastfrag, :isfirstfrag, :gid, :uid, :sport, :dport, :port, :pkttype, :name, :state, :icmp, :limit, :burst, :jump,
     :todest, :tosource, :toports, :log_level, :log_prefix, :reject]
 
+  # These are known booleans that do not take a value, but we want to munge
+  # to true if they exist.
+  @known_booleans = [:ishasmorefrags, :islastfrag, :isfirstfrag]
+
 end
index 1e8617e9a5cae78639a8d97c9f18b6db8df40573..7caf9526ae5022a016c943b2fc36f03b6441a9c9 100644 (file)
@@ -76,6 +76,11 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir
     :isfragment => "-f",
   }
 
+  # These are known booleans that do not take a value, but we want to munge
+  # to true if they exist.
+  @known_booleans = [:socket, :isfragment]
+
+
   # Create property methods dynamically
   (@resource_map.keys << :chain << :table << :action).each do |property|
     define_method "#{property}" do
@@ -154,10 +159,6 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir
     keys = []
     values = line.dup
 
-    # These are known booleans that do not take a value, but we want to munge
-    # to true if they exist.
-    known_booleans = [:socket, :isfragment]
-
     ####################
     # PRE-PARSE CLUDGING
     ####################
@@ -167,14 +168,15 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir
     values = values.sub(/--tcp-flags (\S*) (\S*)/, '--tcp-flags "\1 \2"')
 
     # Trick the system for booleans
-    known_booleans.each do |bool|
-      if bool == :socket then
-        values = values.sub(/#{@resource_map[bool]}/, '-m socket true')
-      end
+    @known_booleans.each do |bool|
+      # append "true" because all params are expected to have values
       if bool == :isfragment then
+        # -f requires special matching:
         # only replace those -f that are not followed by an l to
         # distinguish between -f and the '-f' inside of --tcp-flags.
         values = values.sub(/-f(?!l)(?=.*--comment)/, '-f true')
+      else
+        values = values.sub(/#{@resource_map[bool]}/, "#{@resource_map[bool]} true")
       end
     end
 
@@ -214,7 +216,7 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir
     end
 
     # Convert booleans removing the previous cludge we did
-    known_booleans.each do |bool|
+    @known_booleans.each do |bool|
       if hash[bool] != nil then
         unless hash[bool] == "true" then
           raise "Parser error: #{bool} was meant to be a boolean but received value: #{hash[bool]}."
@@ -309,13 +311,14 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir
     args = []
     resource_list = self.class.instance_variable_get('@resource_list')
     resource_map = self.class.instance_variable_get('@resource_map')
+    known_booleans = self.class.instance_variable_get('@known_booleans')
 
     resource_list.each do |res|
       resource_value = nil
       if (resource[res]) then
         resource_value = resource[res]
         # If socket is true then do not add the value as -m socket is standalone
-        if res == :socket then
+        if known_booleans.include?(res) then
           resource_value = nil
         end
         if res == :isfragment then
index 561cbf3ba1dac7a06ce87733705ba66d79984a24..1c6f7732fb7710d46c9a1c7bf98d78d421e8d80a 100644 (file)
@@ -45,6 +45,9 @@ Puppet::Type.newtype(:firewall) do
   feature :isfragment, "Match fragments"
   feature :address_type, "The ability match on source or destination address type"
   feature :iprange, "The ability match on source or destination IP range "
+  feature :ishasmorefrags, "Match a non-last fragment of a fragmented ipv6 packet - might be first"
+  feature :islastfrag, "Match the last fragment of an ipv6 packet"
+  feature :isfirstfrag, "Match the first fragment of a fragmented ipv6 packet"
 
   # provider specific features
   feature :iptables, "The provider provides iptables features."
@@ -649,6 +652,31 @@ Puppet::Type.newtype(:firewall) do
     newvalues(:true, :false)
   end
 
+  newproperty(:ishasmorefrags, :required_features => :ishasmorefrags) do
+    desc <<-EOS
+      If true, matches if the packet has it's 'more fragments' bit set. ipv6.
+    EOS
+
+    newvalues(:true, :false)
+  end
+
+  newproperty(:islastfrag, :required_features => :islastfrag) do
+    desc <<-EOS
+      If true, matches if the packet is the last fragment. ipv6.
+    EOS
+
+    newvalues(:true, :false)
+  end
+
+  newproperty(:isfirstfrag, :required_features => :isfirstfrag) do
+    desc <<-EOS
+      If true, matches if the packet is the first fragment. 
+      Sadly cannot be negated. ipv6.
+    EOS
+
+    newvalues(:true, :false)
+  end
+
   newparam(:line) do
     desc <<-EOS
       Read-only property for caching the rule line.
diff --git a/spec/fixtures/ip6tables/conversion_hash.rb b/spec/fixtures/ip6tables/conversion_hash.rb
new file mode 100644 (file)
index 0000000..42816d6
--- /dev/null
@@ -0,0 +1,98 @@
+# These hashes allow us to iterate across a series of test data
+# creating rspec examples for each parameter to ensure the input :line
+# extrapolates to the desired value for the parameter in question. And
+# vice-versa
+
+# This hash is for testing a line conversion to a hash of parameters
+# which will be used to create a resource.
+ARGS_TO_HASH6 = {
+  'source_destination_ipv6_no_cidr' => {
+    :line => '-A INPUT -s 2001:db8:85a3::8a2e:370:7334 -d 2001:db8:85a3::8a2e:370:7334 -m comment --comment "000 source destination ipv6 no cidr"',
+    :table => 'filter',
+    :provider => 'ip6tables',
+    :params => {
+      :source => '2001:db8:85a3::8a2e:370:7334/128',
+      :destination => '2001:db8:85a3::8a2e:370:7334/128',
+    },
+  },
+  'source_destination_ipv6_netmask' => {
+    :line => '-A INPUT -s 2001:db8:1234::/ffff:ffff:ffff:0000:0000:0000:0000:0000 -d 2001:db8:4321::/ffff:ffff:ffff:0000:0000:0000:0000:0000 -m comment --comment "000 source destination ipv6 netmask"',
+    :table => 'filter',
+    :provider => 'ip6tables',
+    :params => {
+      :source => '2001:db8:1234::/48',
+      :destination => '2001:db8:4321::/48',
+    },
+  },
+}
+
+# This hash is for testing converting a hash to an argument line.
+HASH_TO_ARGS6 = {
+  'zero_prefixlen_ipv6' => {
+    :params => {
+      :name => '100 zero prefix length ipv6',
+      :table => 'filter',
+      :provider => 'ip6tables',
+      :source => '::/0',
+      :destination => '::/0',
+    },
+    :args => ['-t', :filter, '-p', :tcp, '-m', 'comment', '--comment', '100 zero prefix length ipv6'],
+  },
+  'source_destination_ipv4_no_cidr' => {
+    :params => {
+      :name => '000 source destination ipv4 no cidr',
+      :table => 'filter',
+      :provider => 'ip6tables',
+      :source => '1.1.1.1',
+      :destination => '2.2.2.2',
+    },
+    :args => ['-t', :filter, '-s', '1.1.1.1/32', '-d', '2.2.2.2/32', '-p', :tcp, '-m', 'comment', '--comment', '000 source destination ipv4 no cidr'],
+  },
+ 'source_destination_ipv6_no_cidr' => {
+    :params => {
+      :name => '000 source destination ipv6 no cidr',
+      :table => 'filter',
+      :provider => 'ip6tables',
+      :source => '2001:db8:1234::',
+      :destination => '2001:db8:4321::',
+    },
+    :args => ['-t', :filter, '-s', '2001:db8:1234::/128', '-d', '2001:db8:4321::/128', '-p', :tcp, '-m', 'comment', '--comment', '000 source destination ipv6 no cidr'],
+  },
+ 'source_destination_ipv6_netmask' => {
+    :params => {
+      :name => '000 source destination ipv6 netmask',
+      :table => 'filter',
+      :provider => 'ip6tables',
+      :source => '2001:db8:1234::/ffff:ffff:ffff:0000:0000:0000:0000:0000',
+      :destination => '2001:db8:4321::/ffff:ffff:ffff:0000:0000:0000:0000:0000',
+    },
+    :args => ['-t', :filter, '-s', '2001:db8:1234::/48', '-d', '2001:db8:4321::/48', '-p', :tcp, '-m', 'comment', '--comment', '000 source destination ipv6 netmask'],
+  },
+  'frag_ishasmorefrags' => {
+    :params => {
+      :name => "100 has more fragments",
+      :ishasmorefrags => true,
+      :provider => 'ip6tables',
+      :table => "filter",
+    },
+    :args => ["-t", :filter, "-p", :tcp, "-m", "frag", "--fragid", "0", "--fragmore", "-m", "comment", "--comment", "100 has more fragments"],
+  },
+  'frag_islastfrag' => {
+    :params => {
+      :name => "100 last fragment",
+      :islastfrag => true,
+      :provider => 'ip6tables',
+      :table => "filter",
+    },
+    :args => ["-t", :filter, "-p", :tcp, "-m", "frag", "--fragid", "0", "--fraglast", "-m", "comment", "--comment", "100 last fragment"],
+  },
+  'frag_isfirstfrags' => {
+    :params => {
+      :name => "100 first fragment",
+      :isfirstfrag => true,
+      :provider => 'ip6tables',
+      :table => "filter",
+    },
+    :args => ["-t", :filter, "-p", :tcp, "-m", "frag", "--fragid", "0", "--fragfirst", "-m", "comment", "--comment", "100 first fragment"],
+  },
+}
index c13b9c9ec6e360a42eef4e45c1602a9a47e782f9..e3d62d0fec5f7267b3fdf6be39bdaf7447092dd0 100644 (file)
@@ -162,3 +162,80 @@ describe 'iptables provider' do
     end
   end
 end
+
+describe 'ip6tables provider' do
+  let(:provider6) { Puppet::Type.type(:firewall).provider(:ip6tables) }
+  let(:resource) {
+    Puppet::Type.type(:firewall).new({
+      :name  => '000 test foo',
+      :action  => 'accept',
+      :provider => "ip6tables",
+    })
+  }
+
+  before :each do
+    Puppet::Type::Firewall.stubs(:ip6tables).returns provider6
+    provider6.stubs(:command).with(:ip6tables_save).returns "/sbin/ip6tables-save"
+
+    # Stub iptables version
+    Facter.fact(:ip6tables_version).stubs(:value).returns("1.4.7")
+
+    Puppet::Util::Execution.stubs(:execute).returns ""
+    Puppet::Util.stubs(:which).with("ip6tables-save").
+      returns "/sbin/ip6tables-save"
+  end
+
+  it 'should be able to get a list of existing rules' do
+    provider6.instances.each do |rule|
+      rule.should be_instance_of(provider6)
+      rule.properties[:provider6].to_s.should == provider6.name.to_s
+    end
+  end
+
+  it 'should ignore lines with fatal errors' do
+    Puppet::Util::Execution.stubs(:execute).with(['/sbin/ip6tables-save']).
+      returns("FATAL: Could not load /lib/modules/2.6.18-028stab095.1/modules.dep: No such file or directory")
+
+    provider6.instances.length.should == 0
+  end
+
+  # Load in ruby hash for test fixtures.
+  load 'spec/fixtures/ip6tables/conversion_hash.rb'
+
+  describe 'when converting rules to resources' do
+    ARGS_TO_HASH6.each do |test_name,data|
+      describe "for test data '#{test_name}'" do
+        let(:resource) { provider6.rule_to_hash(data[:line], data[:table], 0) }
+
+        # If this option is enabled, make sure the parameters exactly match
+        if data[:compare_all] then
+          it "the parameter hash keys should be the same as returned by rules_to_hash" do
+            resource.keys.should =~ data[:params].keys
+          end
+        end
+
+        # Iterate across each parameter, creating an example for comparison
+        data[:params].each do |param_name, param_value|
+          it "the parameter '#{param_name.to_s}' should match #{param_value.inspect}" do
+            resource[param_name].should == data[:params][param_name]
+          end
+        end
+      end
+    end
+  end
+
+  describe 'when working out general_args' do
+    HASH_TO_ARGS6.each do |test_name,data|
+      describe "for test data '#{test_name}'" do
+        let(:resource) { Puppet::Type.type(:firewall).new(data[:params]) }
+        let(:provider6) { Puppet::Type.type(:firewall).provider(:ip6tables) }
+        let(:instance) { provider6.new(resource) }
+
+        it 'general_args should be valid' do
+          instance.general_args.flatten.should == data[:args]
+        end
+      end
+    end
+  end
+end
+