]> review.fuel-infra Code Review - puppet-modules/puppetlabs-firewall.git/commitdiff
support for multiple ipsets in a rule
authorLev Popov <lev@spotify.com>
Tue, 8 Mar 2016 22:07:01 +0000 (01:07 +0300)
committerLev Popov <lev@spotify.com>
Tue, 8 Mar 2016 23:51:15 +0000 (02:51 +0300)
Support for multiple ipsets in a single rule. This feature is very handy
if you need to match source and destination from different ipsets.
Iptables arguments are a bit wierd, but it works, details are in
https://utcc.utoronto.ca/~cks/space/blog/linux/IptablesIpsetsMultipleMatches

README.markdown
lib/puppet/provider/firewall/iptables.rb
lib/puppet/type/firewall.rb
spec/acceptance/firewall_spec.rb

index e76f822376fcff6723e5747df0e3629d30ad41e9..896c28ca2e21da36aa212a2fdeb50611a5b78006 100644 (file)
@@ -572,7 +572,7 @@ If Puppet is managing the iptables or iptables-persistent packages, and the prov
 
 * `ipsec_policy`: Sets the ipsec policy type. Valid values are 'none', 'ipsec'. Requires the `ipsec_policy` feature.
 
-* `ipset`: Matches IP sets. Value must be 'ipset_name (src|dst|src,dst)' and can be negated by putting ! in front. Requires ipset kernel module.
+* `ipset`: Matches IP sets. Value must be 'ipset_name (src|dst|src,dst)' and can be negated by putting ! in front. Requires ipset kernel module. Will accept a single element or an array.
 
 * `isfirstfrag`: If true, matches when the packet is the first fragment of a fragmented ipv6 packet. Cannot be negated. Supported by ipv6 only. Valid values are 'true', 'false'. Requires the `isfirstfrag` feature.
 
index dd92362ac8d34686ba9935150086e21fc5a4bfda..e9ce2ebd66a9dbea26d80df2dc3045b98f4a076b 100644 (file)
@@ -344,8 +344,14 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir
     # --tcp-flags takes two values; we cheat by adding " around it
     # so it behaves like --comment
     values = values.gsub(/(!\s+)?--tcp-flags (\S*) (\S*)/, '--tcp-flags "\1\2 \3"')
-    # ditto for --match-set
-    values = values.sub(/(!\s+)?--match-set (\S*) (\S*)/, '--match-set "\1\2 \3"')
+    # --match-set can have multiple values with weird iptables format
+    if values =~ /-m set --match-set/
+      values = values.gsub(/(!\s+)?--match-set (\S*) (\S*)/, '--match-set \1\2 \3')
+      ind  = values.index('-m set --match-set')
+      sets = values.scan(/-m set --match-set ((?:!\s+)?\S* \S*)/)
+      values = values.gsub(/-m set --match-set (!\s+)?\S* \S* /, '')
+      values.insert(ind, "-m set --match-set \"#{sets.join(';')}\" ")
+    end
     # we do a similar thing for negated address masks (source and destination).
     values = values.gsub(/(-\S+) (!)\s?(\S*)/,'\1 "\2 \3"')
     # the actual rule will have the ! mark before the option.
@@ -438,6 +444,8 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir
       hash[prop] = hash[prop].split(',') if ! hash[prop].nil?
     end
 
+    hash[:ipset] = hash[:ipset].split(';') if ! hash[:ipset].nil?
+
     ## clean up DSCP class to HEX mappings
     valid_dscp_classes = {
       '0x0a' => 'af11',
@@ -496,7 +504,6 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir
       :dport,
       :dst_range,
       :dst_type,
-      :ipset,
       :port,
       :proto,
       :source,
@@ -638,7 +645,7 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir
         #ruby 1.8.7 can't .match Symbols ------------------ ^
         resource_value = resource_value.to_s.sub!(/^!\s*/, '').to_sym
         args.insert(-2, '!')
-      elsif resource_value.is_a?(Array)
+      elsif resource_value.is_a?(Array) and res != :ipset
         should_negate = resource_value.index do |value|
           #ruby 1.8.7 can't .match symbols
           value.to_s.match(/^(!)\s+/)
@@ -669,10 +676,20 @@ Puppet::Type.type(:firewall).provide :iptables, :parent => Puppet::Provider::Fir
         end
       end
 
+      # ipset can accept multiple values with weird iptables arguments
+      if res == :ipset
+        resource_value.join(" #{[resource_map[res]].flatten.first} ").split(' ').each do |a|
+          if a.sub!(/^!\s*/, '')
+            # Negate ipset options
+            args.insert(-2, '!')
+          end
+
+          args << a if a.length > 0
+        end
       # our tcp_flags takes a single string with comma lists separated
       # by space
       # --tcp-flags expects two arguments
-      if res == :tcp_flags or res == :ipset
+      elsif res == :tcp_flags
         one, two = resource_value.split(' ')
         args << one
         args << two
index 2529d19803ab170bef7657336484df6a0e9aef4d..b3bdd80468dd6977bec3ca759b610d26aa67d1b3 100644 (file)
@@ -1188,14 +1188,23 @@ Puppet::Type.newtype(:firewall) do
     EOS
   end
 
-  newproperty(:ipset, :required_features => :ipset) do
+  newproperty(:ipset, :required_features => :ipset, :array_matching => :all) do
     desc <<-EOS
       Matches against the specified ipset list.
-      Requires ipset kernel module.
+      Requires ipset kernel module. Will accept a single element or an array.
       The value is the name of the blacklist, followed by a space, and then
       'src' and/or 'dst' separated by a comma.
       For example: 'blacklist src,dst'
     EOS
+
+    def is_to_s(value)
+      should_to_s(value)
+    end
+
+    def should_to_s(value)
+      value = [value] unless value.is_a?(Array)
+      value.join(', ')
+    end
   end
 
   newproperty(:checksum_fill, :required_features => :iptables) do
index def7d178c698220db06cf6e3285780938cb5ef6b..7fa7969a4bc2e7c24f987bb7a272860bb69937d3 100644 (file)
@@ -1533,24 +1533,31 @@ describe 'firewall type', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfami
               require => Package['ipset'],
             }
             class { '::firewall': }
-            exec { 'create ipset':
+            exec { 'create ipset blacklist':
               command => 'ipset create blacklist hash:ip,port family inet6 maxelem 1024 hashsize 65535 timeout 120',
               path    => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
               require => Package['ipset'],
             }
-            exec { 'add blacklist':
+            -> exec { 'create ipset honeypot':
+              command => 'ipset create honeypot hash:ip family inet6 maxelem 1024 hashsize 65535 timeout 120',
+              path    => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
+            }
+            -> exec { 'add blacklist':
               command => 'ipset add blacklist 2001:db8::1,80',
               path    => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
-              require => Exec['create ipset'],
+            }
+            -> exec { 'add honeypot':
+              command => 'ipset add honeypot 2001:db8::5',
+              path    => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
             }
             firewall { '612 - test':
               ensure   => present,
               chain    => 'INPUT',
               proto    => tcp,
               action   => drop,
-              ipset    => 'blacklist src,src',
+              ipset    => ['blacklist src,dst', '! honeypot dst'],
               provider => 'ip6tables',
-              require  => Exec['add blacklist'],
+              require  => Exec['add honeypot'],
             }
             EOS
 
@@ -1559,7 +1566,7 @@ describe 'firewall type', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfami
 
           it 'should contain the rule' do
             shell('ip6tables-save') do |r|
-              expect(r.stdout).to match(/-A INPUT -p tcp -m comment --comment "612 - test" -m set --match-set blacklist src,src -j DROP/)
+              expect(r.stdout).to match(/-A INPUT -p tcp -m comment --comment "612 - test" -m set --match-set blacklist src,dst -m set ! --match-set honeypot dst -j DROP/)
             end
           end
         end