]> review.fuel-infra Code Review - puppet-modules/puppetlabs-firewall.git/commitdiff
(MODULES-8214) Handle src_type and dst_type as array
authorMateusz Gozdek <mateusz.gozdek@sociomantic.com>
Tue, 6 Nov 2018 00:16:35 +0000 (01:16 +0100)
committerMateusz Gozdek <mateusz.gozdek@sociomantic.com>
Mon, 26 Nov 2018 14:09:53 +0000 (15:09 +0100)
So it can be parsed when specified mutliple times, as well as being configured.

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

index 98f72644cd346f35c862587d11f26cf07d49bd1c..678d1b4d47e96f65853e9ef25ac3b37750f5fa95 100644 (file)
@@ -581,7 +581,7 @@ If Puppet is managing the iptables or iptables-persistent packages, and the prov
 
   The destination IP range is must in 'IP1-IP2' format. Values in the range must be valid IPv4 or IPv6 addresses. Requires the `iprange` feature.
 
-* `dst_type`: The destination address type. For example: `dst_type => 'LOCAL'`.
+* `dst_type`: The destination address type. Will accept a single element or an array. For example: `dst_type => ['LOCAL']`.
 
   Valid values are:
 
@@ -597,6 +597,10 @@ If Puppet is managing the iptables or iptables-persistent packages, and the prov
   * 'THROW': an unroutable address
   * 'XRESOLVE: an unresolvable address
 
+  In addition, it accepts '--limit-iface-in' and '--limit-iface-out' flags. For example: `dst_type => ['LOCAL --limit-iface-in']`.
+
+  It can also be negated using '!'. For example: `dst_type => ['! LOCAL']`.
+
   Requires the `address_type` feature.
 
 * `ensure`: Ensures that the resource is present. Valid values are 'present', 'absent'. The default is 'present'.
@@ -798,7 +802,7 @@ If Puppet is managing the iptables or iptables-persistent packages, and the prov
 
 * `src_range`: The source IP range. For example: `src_range => '192.168.1.1-192.168.1.10'`. The source IP range must be in 'IP1-IP2' format. Values in the range must be valid IPv4 or IPv6 addresses. Requires the `iprange` feature.
 
-* `src_type`: Specify the source address type. For example: `src_type => 'LOCAL'`.
+* `src_type`: Specify the source address type. Will accept a single element or an array. For example: `src_type => ['LOCAL']`.
 
   Valid values are:
 
@@ -814,6 +818,10 @@ If Puppet is managing the iptables or iptables-persistent packages, and the prov
   * 'THROW': an unroutable address.
   * 'XRESOLVE': an unresolvable address.
 
+  In addition, it accepts '--limit-iface-in' and '--limit-iface-out' flags. For example: `dst_type => ['LOCAL --limit-iface-in']`.
+
+  It can also be negated using '!'. For example: `dst_type => ['! LOCAL']`.
+
   Requires the `address_type` feature.
 
 * `stat_every`: Match one packet every nth packet. Requires `stat_mode => 'nth'`
index 3dd657c4592e89165c8d6d03d32ff3e304049fcc..dcbe70947202a3a25557f2a2b5e88abea7d93ae6 100644 (file)
@@ -403,6 +403,20 @@ Puppet::Type.type(:firewall).provide :iptables, parent: Puppet::Provider::Firewa
       values = values.gsub(%r{-m comment --comment ([^"].*?)[ $]}, '')
       values.insert(ind, "-m comment --comment \"#{comments.join(';')}\" ")
     end
+    if values =~ %r{-m addrtype (!\s+)?--src-type}
+      values = values.gsub(%r{(!\s+)?--src-type (\S*)(\s--limit-iface-(in|out))?}, '--src-type \1\2\3')
+      ind = values.index('-m addrtype --src-type')
+      types = values.scan(%r{-m addrtype --src-type ((?:!\s+)?\S*(?: --limit-iface-(?:in|out))?)})
+      values = values.gsub(%r{-m addrtype --src-type ((?:!\s+)?\S*(?: --limit-iface-(?:in|out))?) ?}, '')
+      values.insert(ind, "-m addrtype --src-type \"#{types.join(';')}\" ")
+    end
+    if values =~ %r{-m addrtype (!\s+)?--dst-type}
+      values = values.gsub(%r{(!\s+)?--dst-type (\S*)(\s--limit-iface-(in|out))?}, '--dst-type \1\2\3')
+      ind = values.index('-m addrtype --dst-type')
+      types = values.scan(%r{-m addrtype --dst-type ((?:!\s+)?\S*(?: --limit-iface-(?:in|out))?)})
+      values = values.gsub(%r{-m addrtype --dst-type ((?:!\s+)?\S*(?: --limit-iface-(?:in|out))?) ?}, '')
+      values.insert(ind, "-m addrtype --dst-type \"#{types.join(';')}\" ")
+    end
     # the actual rule will have the ! mark before the option.
     values = values.gsub(%r{(!)\s*(-\S+)\s*(\S*)}, '\2 "\1 \3"')
     # we do a similar thing for negated address masks (source and destination).
@@ -512,7 +526,9 @@ Puppet::Type.type(:firewall).provide :iptables, parent: Puppet::Provider::Firewa
       hash[prop] = hash[prop].split(',') unless hash[prop].nil?
     end
 
-    hash[:ipset] = hash[:ipset].split(';') unless hash[:ipset].nil?
+    [:ipset, :dst_type, :src_type].each do |prop|
+      hash[prop] = hash[prop].split(';') unless hash[prop].nil?
+    end
 
     ## clean up DSCP class to HEX mappings
     valid_dscp_classes = {
@@ -571,7 +587,6 @@ Puppet::Type.type(:firewall).provide :iptables, parent: Puppet::Provider::Firewa
       :destination,
       :dport,
       :dst_range,
-      :dst_type,
       :port,
       :physdev_is_bridged,
       :physdev_is_in,
@@ -580,7 +595,6 @@ Puppet::Type.type(:firewall).provide :iptables, parent: Puppet::Provider::Firewa
       :source,
       :sport,
       :src_range,
-      :src_type,
       :state,
     ].each do |prop|
       if hash[prop] && hash[prop].is_a?(Array)
@@ -709,6 +723,19 @@ Puppet::Type.type(:firewall).provide :iptables, parent: Puppet::Provider::Firewa
       raise "#{nflog_feature} is not available on iptables version #{iptables_version}" if resource[nflog_feature] && (iptables_version && iptables_version < '1.3.7')
     end
 
+    [:dst_type, :src_type].each do |prop|
+      next unless resource[prop]
+
+      resource[prop].each do |type|
+        if type =~ %r{--limit-iface-(in|out)} && (iptables_version && iptables_version < '1.4.1')
+          raise '--limit-iface-in and --limit-iface-out are available from iptables version 1.4.1'
+        end
+      end
+
+      raise "Multiple #{prop} elements are available from iptables version 1.4.1" if resource[prop].length > 1 && (iptables_version && iptables_version < '1.4.1')
+      raise "#{prop} elements must be unique" if resource[prop].map { |type| type.to_s.gsub(%r{--limit-iface-(in|out)}, '') }.uniq.length != resource[prop].length
+    end
+
     resource_list.each do |res|
       resource_value = nil
       if resource[res]
@@ -737,7 +764,7 @@ Puppet::Type.type(:firewall).provide :iptables, parent: Puppet::Provider::Firewa
         # ruby 1.8.7 can't .match Symbols ------------------ ^
         resource_value = resource_value.to_s.sub!(%r{^!\s*}, '').to_sym
         args.insert(-2, '!')
-      elsif resource_value.is_a?(Array) && res != :ipset
+      elsif resource_value.is_a?(Array) && ![:ipset, :dst_type, :src_type].include?(res)
         should_negate = resource_value.index do |value|
           # ruby 1.8.7 can't .match symbols
           value.to_s.match(%r{^(!)\s+})
@@ -770,7 +797,7 @@ Puppet::Type.type(:firewall).provide :iptables, parent: Puppet::Provider::Firewa
       end
 
       # ipset can accept multiple values with weird iptables arguments
-      if res == :ipset
+      if [:ipset, :dst_type, :src_type].include?(res)
         resource_value.join(" #{[resource_map[res]].flatten.first} ").split(' ').each do |a|
           if a.sub!(%r{^!\s*}, '')
             # Negate ipset options
index 1f4ff0e0752c2a91ea8b8dd932f5e23a6df96076..b9feff49bceec3ade4be0ad8ad6713fb2c27a8fa 100644 (file)
@@ -331,11 +331,11 @@ Puppet::Type.newtype(:firewall) do
     end
   end
 
-  newproperty(:dst_type, required_features: :address_type) do
+  newproperty(:dst_type, required_features: :address_type, array_matching: :all) do
     desc <<-PUPPETCODE
       The destination address type. For example:
 
-          dst_type => 'LOCAL'
+          dst_type => ['LOCAL']
 
       Can be one of:
 
@@ -351,19 +351,36 @@ Puppet::Type.newtype(:firewall) do
       * THROW - undocumented
       * NAT - undocumented
       * XRESOLVE - undocumented
+
+      In addition, it accepts '--limit-iface-in' and '--limit-iface-out' flags, specified as:
+
+          dst_type => ['LOCAL --limit-iface-in']
+
+      It can also be negated using '!':
+
+          dst_type => ['! LOCAL']
+
+      Will accept a single element or an array.
     PUPPETCODE
 
     newvalues(*[:UNSPEC, :UNICAST, :LOCAL, :BROADCAST, :ANYCAST, :MULTICAST,
                 :BLACKHOLE, :UNREACHABLE, :PROHIBIT, :THROW, :NAT, :XRESOLVE].map { |address_type|
-                [address_type, "! #{address_type}".to_sym]
+                [
+                  address_type,
+                  "! #{address_type}".to_sym,
+                  "#{address_type} --limit-iface-in".to_sym,
+                  "#{address_type} --limit-iface-out".to_sym,
+                  "! #{address_type} --limit-iface-in".to_sym,
+                  "! #{address_type} --limit-iface-out".to_sym,
+                ]
               }.flatten)
   end
 
-  newproperty(:src_type, required_features: :address_type) do
+  newproperty(:src_type, required_features: :address_type, array_matching: :all) do
     desc <<-PUPPETCODE
       The source address type. For example:
 
-          src_type => 'LOCAL'
+          src_type => ['LOCAL']
 
       Can be one of:
 
@@ -379,11 +396,28 @@ Puppet::Type.newtype(:firewall) do
       * THROW - undocumented
       * NAT - undocumented
       * XRESOLVE - undocumented
+
+      In addition, it accepts '--limit-iface-in' and '--limit-iface-out' flags, specified as:
+
+          src_type => ['LOCAL --limit-iface-in']
+
+      It can also be negated using '!':
+
+          src_type => ['! LOCAL']
+
+      Will accept a single element or an array.
     PUPPETCODE
 
     newvalues(*[:UNSPEC, :UNICAST, :LOCAL, :BROADCAST, :ANYCAST, :MULTICAST,
                 :BLACKHOLE, :UNREACHABLE, :PROHIBIT, :THROW, :NAT, :XRESOLVE].map { |address_type|
-                [address_type, "! #{address_type}".to_sym]
+                [
+                  address_type,
+                  "! #{address_type}".to_sym,
+                  "#{address_type} --limit-iface-in".to_sym,
+                  "#{address_type} --limit-iface-out".to_sym,
+                  "! #{address_type} --limit-iface-in".to_sym,
+                  "! #{address_type} --limit-iface-out".to_sym,
+                ]
               }.flatten)
   end
 
index 4bb93da9c65ca0703a5e886a8bfa255b910c189f..9f0236722b7167ac239101ac0414c4a41838dc04 100644 (file)
@@ -586,6 +586,117 @@ describe 'firewall basics', docker: true do
           end
         end
       end
+
+      context 'when LOCAL --limit-iface-in', unless: (fact('operatingsystem') == 'RedHat' && fact('operatingsystemmajrelease') <= '5') ||
+                                                     (fact('operatingsystem') == 'CentOS' && fact('operatingsystemmajrelease') <= '5') do
+        pp97 = <<-PUPPETCODE
+            class { '::firewall': }
+            firewall { '613 - test':
+              proto   => tcp,
+              action  => accept,
+              #{type} => 'LOCAL --limit-iface-in',
+            }
+        PUPPETCODE
+        it 'applies' do
+          apply_manifest(pp97, catch_failures: true)
+        end
+
+        it 'contains the rule' do
+          shell('iptables-save') do |r|
+            expect(r.stdout).to match(%r{-A INPUT -p tcp -m addrtype\s.*\sLOCAL --limit-iface-in -m comment --comment "613 - test" -j ACCEPT})
+          end
+        end
+      end
+
+      context 'when LOCAL --limit-iface-in fail', if: (fact('operatingsystem') == 'RedHat' && fact('operatingsystemmajrelease') <= '5') ||
+                                                      (fact('operatingsystem') == 'CentOS' && fact('operatingsystemmajrelease') <= '5') do
+        pp98 = <<-PUPPETCODE
+            class { '::firewall': }
+            firewall { '614 - test':
+              proto   => tcp,
+              action  => accept,
+              #{type} => 'LOCAL --limit-iface-in',
+            }
+        PUPPETCODE
+        it 'fails' do
+          apply_manifest(pp98, expect_failures: true) do |r|
+            expect(r.stderr).to match(%r{--limit-iface-in and --limit-iface-out are available from iptables version})
+          end
+        end
+
+        it 'does not contain the rule' do
+          shell('iptables-save') do |r|
+            expect(r.stdout).not_to match(%r{-A INPUT -p tcp -m addrtype\s.*\sLOCAL --limit-iface-in -m comment --comment "614 - test" -j ACCEPT})
+          end
+        end
+      end
+
+      context 'when duplicated LOCAL', unless: (fact('operatingsystem') == 'RedHat' && fact('operatingsystemmajrelease') <= '5') ||
+                                               (fact('operatingsystem') == 'CentOS' && fact('operatingsystemmajrelease') <= '5') do
+        pp99 = <<-PUPPETCODE
+            class { '::firewall': }
+            firewall { '615 - test':
+              proto   => tcp,
+              action  => accept,
+              #{type} => ['LOCAL', 'LOCAL'],
+            }
+        PUPPETCODE
+        it 'fails' do
+          apply_manifest(pp99, expect_failures: true) do |r|
+            expect(r.stderr).to match(%r{#{type} elements must be unique})
+          end
+        end
+
+        it 'does not contain the rule' do
+          shell('iptables-save') do |r|
+            expect(r.stdout).not_to match(%r{-A INPUT -p tcp -m addrtype --#{type.tr('_', '-')} LOCAL -m addrtype --#{type.tr('_', '-')} LOCAL -m comment --comment "615 - test" -j ACCEPT})
+          end
+        end
+      end
+
+      context 'when multiple addrtype', unless: (fact('operatingsystem') == 'RedHat' && fact('operatingsystemmajrelease') <= '5') ||
+                                                (fact('operatingsystem') == 'CentOS' && fact('operatingsystemmajrelease') <= '5') do
+        pp100 = <<-PUPPETCODE
+            class { '::firewall': }
+            firewall { '616 - test':
+              proto   => tcp,
+              action  => accept,
+              #{type} => ['LOCAL', '! LOCAL'],
+            }
+        PUPPETCODE
+        it 'applies' do
+          apply_manifest(pp100, catch_failures: true)
+        end
+
+        it 'contains the rule' do
+          shell('iptables-save') do |r|
+            expect(r.stdout).to match(%r{-A INPUT -p tcp -m addrtype --#{type.tr('_', '-')} LOCAL -m addrtype ! --#{type.tr('_', '-')} LOCAL -m comment --comment "616 - test" -j ACCEPT})
+          end
+        end
+      end
+
+      context 'when multiple addrtype fail', if: (fact('operatingsystem') == 'RedHat' && fact('operatingsystemmajrelease') <= '5') ||
+                                                 (fact('operatingsystem') == 'CentOS' && fact('operatingsystemmajrelease') <= '5') do
+        pp101 = <<-PUPPETCODE
+            class { '::firewall': }
+            firewall { '616 - test':
+              proto   => tcp,
+              action  => accept,
+              #{type} => ['LOCAL', '! LOCAL'],
+            }
+        PUPPETCODE
+        it 'fails' do
+          apply_manifest(pp101, expect_failures: true) do |r|
+            expect(r.stderr).to match(%r{Multiple #{type} elements are available from iptables version})
+          end
+        end
+
+        it 'does not contain the rule' do
+          shell('iptables-save') do |r|
+            expect(r.stdout).not_to match(%r{-A INPUT -p tcp -m addrtype --#{type.tr('_', '-')} LOCAL -m addrtype ! --#{type.tr('_', '-')} LOCAL -m comment --comment "616 - test" -j ACCEPT})
+          end
+        end
+      end
     end
   end
 
@@ -1574,6 +1685,120 @@ describe 'firewall basics', docker: true do
               end
             end
           end
+
+          context 'when LOCAL --limit-iface-in', unless: (fact('operatingsystem') == 'RedHat' && fact('operatingsystemmajrelease') <= '5') ||
+                                                         (fact('operatingsystem') == 'CentOS' && fact('operatingsystemmajrelease') <= '5') do
+            pp102 = <<-PUPPETCODE
+                class { '::firewall': }
+                firewall { '617 - test':
+                  proto   => tcp,
+                  action  => accept,
+                  #{type} => 'LOCAL --limit-iface-in',
+                }
+            PUPPETCODE
+            it 'applies' do
+              apply_manifest(pp102, catch_failures: true)
+            end
+
+            it 'contains the rule' do
+              shell('iptables-save') do |r|
+                expect(r.stdout).to match(%r{-A INPUT -p tcp -m addrtype\s.*\sLOCAL --limit-iface-in -m comment --comment "617 - test" -j ACCEPT})
+              end
+            end
+          end
+
+          context 'when LOCAL --limit-iface-in fail', if: (fact('operatingsystem') == 'RedHat' && fact('operatingsystemmajrelease') <= '5') ||
+                                                          (fact('operatingsystem') == 'CentOS' && fact('operatingsystemmajrelease') <= '5') do
+            pp103 = <<-PUPPETCODE
+                class { '::firewall': }
+                firewall { '618 - test':
+                  proto   => tcp,
+                  action  => accept,
+                  #{type} => 'LOCAL --limit-iface-in',
+                }
+            PUPPETCODE
+            it 'fails' do
+              apply_manifest(pp103, expect_failures: true) do |r|
+                expect(r.stderr).to match(%r{--limit-iface-in and --limit-iface-out are available from iptables version})
+              end
+            end
+
+            it 'does not contain the rule' do
+              shell('iptables-save') do |r|
+                expect(r.stdout).not_to match(%r{-A INPUT -p tcp -m addrtype\s.*\sLOCAL --limit-iface-in -m comment --comment "618 - test" -j ACCEPT})
+              end
+            end
+          end
+
+          context 'when duplicated LOCAL', unless: (fact('operatingsystem') == 'RedHat' && fact('operatingsystemmajrelease') <= '5') ||
+                                                   (fact('operatingsystem') == 'CentOS' && fact('operatingsystemmajrelease') <= '5') do
+            pp104 = <<-PUPPETCODE
+                class { '::firewall': }
+                firewall { '619 - test':
+                  proto    => tcp,
+                  action   => accept,
+                  #{type}  => ['LOCAL', 'LOCAL'],
+                  provider => 'ip6tables',
+                }
+            PUPPETCODE
+            it 'fails' do
+              apply_manifest(pp104, expect_failures: true) do |r|
+                expect(r.stderr).to match(%r{#{type} elements must be unique})
+              end
+            end
+
+            it 'does not contain the rule' do
+              shell('ip6tables-save') do |r|
+                expect(r.stdout).not_to match(%r{-A INPUT -p tcp -m addrtype\s.*\sLOCAL -m addrtype\s.*\sLOCAL -m comment --comment "619 - test" -j ACCEPT})
+              end
+            end
+          end
+
+          context 'when multiple addrtype', unless: (fact('operatingsystem') == 'RedHat' && fact('operatingsystemmajrelease') <= '5') ||
+                                                    (fact('operatingsystem') == 'CentOS' && fact('operatingsystemmajrelease') <= '5') do
+            pp105 = <<-PUPPETCODE
+                class { '::firewall': }
+                firewall { '620 - test':
+                  proto    => tcp,
+                  action   => accept,
+                  #{type}  => ['LOCAL', '! LOCAL'],
+                  provider => 'ip6tables',
+                }
+            PUPPETCODE
+            it 'applies' do
+              apply_manifest(pp105, catch_failures: true)
+            end
+
+            it 'contains the rule' do
+              shell('ip6tables-save') do |r|
+                expect(r.stdout).to match(%r{-A INPUT -p tcp -m addrtype --#{type.tr('_', '-')} LOCAL -m addrtype ! --#{type.tr('_', '-')} LOCAL -m comment --comment "620 - test" -j ACCEPT})
+              end
+            end
+          end
+
+          context 'when multiple addrtype fail', if: (fact('operatingsystem') == 'RedHat' && fact('operatingsystemmajrelease') <= '5') ||
+                                                     (fact('operatingsystem') == 'CentOS' && fact('operatingsystemmajrelease') <= '5') do
+            pp106 = <<-PUPPETCODE
+                class { '::firewall': }
+                firewall { '616 - test':
+                  proto    => tcp,
+                  action   => accept,
+                  #{type}  => ['LOCAL', '! LOCAL'],
+                  provider => 'ip6tables',
+                }
+            PUPPETCODE
+            it 'fails' do
+              apply_manifest(pp106, expect_failures: true) do |r|
+                expect(r.stderr).to match(%r{Multiple #{type} elements are available from iptables version})
+              end
+            end
+
+            it 'does not contain the rule' do
+              shell('ip6tables-save') do |r|
+                expect(r.stdout).not_to match(%r{-A INPUT -p tcp -m addrtype --#{type.tr('_', '-')} LOCAL -m addrtype ! --#{type.tr('_', '-')} LOCAL -m comment --comment "616 - test" -j ACCEPT})
+              end
+            end
+          end
         end
       end
     end
index 237b56926f190111d66d698d1e88bcd2b9cb3704..166d9352b7641ea1a5f06268d637935ecd0a764a 100644 (file)
@@ -176,14 +176,14 @@ ARGS_TO_HASH = {
     line: '-A INPUT -m addrtype --dst-type LOCAL',
     table: 'filter',
     params: {
-      dst_type: 'LOCAL',
+      dst_type: ['LOCAL'],
     },
   },
   'src_type_1' => {
     line: '-A INPUT -m addrtype --src-type LOCAL',
     table: 'filter',
     params: {
-      src_type: 'LOCAL',
+      src_type: ['LOCAL'],
     },
   },
   'dst_range_1' => {
@@ -370,6 +370,39 @@ ARGS_TO_HASH = {
       action: 'drop',
     },
   },
+  'addrtype_limit_iface_out' => {
+    line: '-A cali-POSTROUTING -o tunl0 -m comment --comment "000 cali:JHlpT-eSqR1TvyYm" -m addrtype --src-type LOCAL --limit-iface-out -j MASQUERADE',
+    table: 'filter',
+    params: {
+      chain: 'cali-POSTROUTING',
+      outiface: 'tunl0',
+      name: '000 cali:JHlpT-eSqR1TvyYm',
+      jump: 'MASQUERADE',
+      src_type: ['LOCAL --limit-iface-out'],
+    },
+  },
+  'addrtype_negated' => {
+    line: '-A cali-POSTROUTING -o tunl0 -m comment --comment "000 cali:JHlpT-eSqR1TvyYm" -m addrtype ! --src-type LOCAL -j MASQUERADE',
+    table: 'filter',
+    params: {
+      chain: 'cali-POSTROUTING',
+      outiface: 'tunl0',
+      name: '000 cali:JHlpT-eSqR1TvyYm',
+      jump: 'MASQUERADE',
+      src_type: ['! LOCAL'],
+    },
+  },
+  'addrtype_multiple' => {
+    line: '-A cali-POSTROUTING -o tunl0 -m comment --comment "000 cali:JHlpT-eSqR1TvyYm" -m addrtype ! --src-type LOCAL --limit-iface-out -m addrtype --src-type LOCAL -j MASQUERADE',
+    table: 'filter',
+    params: {
+      chain: 'cali-POSTROUTING',
+      outiface: 'tunl0',
+      name: '000 cali:JHlpT-eSqR1TvyYm',
+      jump: 'MASQUERADE',
+      src_type: ['! LOCAL --limit-iface-out', 'LOCAL'],
+    },
+  },
   'iniface_1_negated' => {
     line: '-A INPUT ! -i eth0 -j DROP -m comment --comment "060 iniface"',
     table: 'filter',
@@ -838,7 +871,31 @@ HASH_TO_ARGS = {
       table: 'filter',
       dst_type: 'LOCAL',
     },
-    args: ['-t', :filter, '-p', :tcp, '-m', 'addrtype', '--dst-type', :LOCAL, '-m', 'comment', '--comment', '000 dst_type'],
+    args: ['-t', :filter, '-p', :tcp, '-m', 'addrtype', '--dst-type', 'LOCAL', '-m', 'comment', '--comment', '000 dst_type'],
+  },
+  'dst_type_as_array' => {
+    params: {
+      name: '000 dst_type',
+      table: 'filter',
+      dst_type: ['LOCAL'],
+    },
+    args: ['-t', :filter, '-p', :tcp, '-m', 'addrtype', '--dst-type', 'LOCAL', '-m', 'comment', '--comment', '000 dst_type'],
+  },
+  'dst_type_multiple' => {
+    params: {
+      name: '000 dst_type',
+      table: 'filter',
+      dst_type: ['LOCAL', '! LOCAL'],
+    },
+    args: ['-t', :filter, '-p', :tcp, '-m', 'addrtype', '--dst-type', 'LOCAL', '-m', 'addrtype', '!', '--dst-type', 'LOCAL', '-m', 'comment', '--comment', '000 dst_type'],
+  },
+  'dst_type_limit' => {
+    params: {
+      name: '000 dst_type',
+      table: 'filter',
+      dst_type: ['LOCAL --limit-iface-in'],
+    },
+    args: ['-t', :filter, '-p', :tcp, '-m', 'addrtype', '--dst-type', 'LOCAL', '--limit-iface-in', '-m', 'comment', '--comment', '000 dst_type'],
   },
   'src_type_1' => {
     params: {
@@ -846,7 +903,23 @@ HASH_TO_ARGS = {
       table: 'filter',
       src_type: 'LOCAL',
     },
-    args: ['-t', :filter, '-p', :tcp, '-m', 'addrtype', '--src-type', :LOCAL, '-m', 'comment', '--comment', '000 src_type'],
+    args: ['-t', :filter, '-p', :tcp, '-m', 'addrtype', '--src-type', 'LOCAL', '-m', 'comment', '--comment', '000 src_type'],
+  },
+  'src_type_as_array' => {
+    params: {
+      name: '000 src_type',
+      table: 'filter',
+      src_type: ['LOCAL'],
+    },
+    args: ['-t', :filter, '-p', :tcp, '-m', 'addrtype', '--src-type', 'LOCAL', '-m', 'comment', '--comment', '000 src_type'],
+  },
+  'src_type_multiple' => {
+    params: {
+      name: '000 src_type',
+      table: 'filter',
+      src_type: ['LOCAL', '! LOCAL'],
+    },
+    args: ['-t', :filter, '-p', :tcp, '-m', 'addrtype', '--src-type', 'LOCAL', '-m', 'addrtype', '!', '--src-type', 'LOCAL', '-m', 'comment', '--comment', '000 src_type'],
   },
   'dst_range_1' => {
     params: {
index 5a9d42fa48347107781b20786637904acde65f18..e09c8981026023e4ca9359a537ba3b03bba864fc 100755 (executable)
@@ -198,9 +198,13 @@ describe firewall do # rubocop:disable RSpec/MultipleDescribes
 
     [:UNSPEC, :UNICAST, :LOCAL, :BROADCAST, :ANYCAST, :MULTICAST, :BLACKHOLE,
      :UNREACHABLE, :PROHIBIT, :THROW, :NAT, :XRESOLVE].each do |type|
-      it "should accept #{addrtype} value #{type}" do
-        resource[addrtype] = type
-        expect(resource[addrtype]).to eql type
+      ['! ', ''].each do |negation|
+        ['', ' --limit-iface-in', ' --limit-iface-out'].each do |limit|
+          it "should accept #{addrtype} value #{negation}#{type}#{limit}" do
+            resource[addrtype] = type
+            expect(resource[addrtype]).to eql [type]
+          end
+        end
       end
     end