From: Georg Koester Date: Fri, 12 Apr 2013 07:58:09 +0000 (-0700) Subject: Add ipv6 fragmentation matchers and generify known_boolean handling. X-Git-Tag: 0.5.0~30^2~1 X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=ce61a24dbd408ae784e812297f77f6dac19ff4a5;p=puppet-modules%2Fpuppetlabs-firewall.git Add ipv6 fragmentation matchers and generify known_boolean handling. 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. --- diff --git a/lib/puppet/provider/firewall/ip6tables.rb b/lib/puppet/provider/firewall/ip6tables.rb index 74e5688..8e34fbe 100644 --- a/lib/puppet/provider/firewall/ip6tables.rb +++ b/lib/puppet/provider/firewall/ip6tables.rb @@ -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 diff --git a/lib/puppet/provider/firewall/iptables.rb b/lib/puppet/provider/firewall/iptables.rb index 1e8617e..7caf952 100644 --- a/lib/puppet/provider/firewall/iptables.rb +++ b/lib/puppet/provider/firewall/iptables.rb @@ -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 diff --git a/lib/puppet/type/firewall.rb b/lib/puppet/type/firewall.rb index 561cbf3..1c6f773 100644 --- a/lib/puppet/type/firewall.rb +++ b/lib/puppet/type/firewall.rb @@ -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 index 0000000..42816d6 --- /dev/null +++ b/spec/fixtures/ip6tables/conversion_hash.rb @@ -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"], + }, +} diff --git a/spec/unit/puppet/provider/iptables_spec.rb b/spec/unit/puppet/provider/iptables_spec.rb index c13b9c9..e3d62d0 100644 --- a/spec/unit/puppet/provider/iptables_spec.rb +++ b/spec/unit/puppet/provider/iptables_spec.rb @@ -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 +