Skip to content

Commit 4af1f13

Browse files
author
Lev Popov
committed
support for multiple ipsets in a rule
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
1 parent 999990d commit 4af1f13

File tree

4 files changed

+47
-14
lines changed

4 files changed

+47
-14
lines changed

README.markdown

+1-1
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ If Puppet is managing the iptables or iptables-persistent packages, and the prov
572572

573573
* `ipsec_policy`: Sets the ipsec policy type. Valid values are 'none', 'ipsec'. Requires the `ipsec_policy` feature.
574574

575-
* `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.
575+
* `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.
576576

577577
* `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.
578578

lib/puppet/provider/firewall/iptables.rb

+22-5
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,14 @@ def self.rule_to_hash(line, table, counter)
344344
# --tcp-flags takes two values; we cheat by adding " around it
345345
# so it behaves like --comment
346346
values = values.gsub(/(!\s+)?--tcp-flags (\S*) (\S*)/, '--tcp-flags "\1\2 \3"')
347-
# ditto for --match-set
348-
values = values.sub(/(!\s+)?--match-set (\S*) (\S*)/, '--match-set "\1\2 \3"')
347+
# --match-set can have multiple values with weird iptables format
348+
if values =~ /-m set --match-set/
349+
values = values.gsub(/(!\s+)?--match-set (\S*) (\S*)/, '--match-set \1\2 \3')
350+
ind = values.index('-m set --match-set')
351+
sets = values.scan(/-m set --match-set ((?:!\s+)?\S* \S*)/)
352+
values = values.gsub(/-m set --match-set (!\s+)?\S* \S* /, '')
353+
values.insert(ind, "-m set --match-set \"#{sets.join(';')}\" ")
354+
end
349355
# we do a similar thing for negated address masks (source and destination).
350356
values = values.gsub(/(-\S+) (!)\s?(\S*)/,'\1 "\2 \3"')
351357
# the actual rule will have the ! mark before the option.
@@ -438,6 +444,8 @@ def self.rule_to_hash(line, table, counter)
438444
hash[prop] = hash[prop].split(',') if ! hash[prop].nil?
439445
end
440446

447+
hash[:ipset] = hash[:ipset].split(';') if ! hash[:ipset].nil?
448+
441449
## clean up DSCP class to HEX mappings
442450
valid_dscp_classes = {
443451
'0x0a' => 'af11',
@@ -496,7 +504,6 @@ def self.rule_to_hash(line, table, counter)
496504
:dport,
497505
:dst_range,
498506
:dst_type,
499-
:ipset,
500507
:port,
501508
:proto,
502509
:source,
@@ -638,7 +645,7 @@ def general_args
638645
#ruby 1.8.7 can't .match Symbols ------------------ ^
639646
resource_value = resource_value.to_s.sub!(/^!\s*/, '').to_sym
640647
args.insert(-2, '!')
641-
elsif resource_value.is_a?(Array)
648+
elsif resource_value.is_a?(Array) and res != :ipset
642649
should_negate = resource_value.index do |value|
643650
#ruby 1.8.7 can't .match symbols
644651
value.to_s.match(/^(!)\s+/)
@@ -669,10 +676,20 @@ def general_args
669676
end
670677
end
671678

679+
# ipset can accept multiple values with weird iptables arguments
680+
if res == :ipset
681+
resource_value.join(" #{[resource_map[res]].flatten.first} ").split(' ').each do |a|
682+
if a.sub!(/^!\s*/, '')
683+
# Negate ipset options
684+
args.insert(-2, '!')
685+
end
686+
687+
args << a if a.length > 0
688+
end
672689
# our tcp_flags takes a single string with comma lists separated
673690
# by space
674691
# --tcp-flags expects two arguments
675-
if res == :tcp_flags or res == :ipset
692+
elsif res == :tcp_flags
676693
one, two = resource_value.split(' ')
677694
args << one
678695
args << two

lib/puppet/type/firewall.rb

+11-2
Original file line numberDiff line numberDiff line change
@@ -1188,14 +1188,23 @@ def insync?(is)
11881188
EOS
11891189
end
11901190

1191-
newproperty(:ipset, :required_features => :ipset) do
1191+
newproperty(:ipset, :required_features => :ipset, :array_matching => :all) do
11921192
desc <<-EOS
11931193
Matches against the specified ipset list.
1194-
Requires ipset kernel module.
1194+
Requires ipset kernel module. Will accept a single element or an array.
11951195
The value is the name of the blacklist, followed by a space, and then
11961196
'src' and/or 'dst' separated by a comma.
11971197
For example: 'blacklist src,dst'
11981198
EOS
1199+
1200+
def is_to_s(value)
1201+
should_to_s(value)
1202+
end
1203+
1204+
def should_to_s(value)
1205+
value = [value] unless value.is_a?(Array)
1206+
value.join(', ')
1207+
end
11991208
end
12001209

12011210
newproperty(:checksum_fill, :required_features => :iptables) do

spec/acceptance/firewall_spec.rb

+13-6
Original file line numberDiff line numberDiff line change
@@ -1533,24 +1533,31 @@ class { '::firewall': }
15331533
require => Package['ipset'],
15341534
}
15351535
class { '::firewall': }
1536-
exec { 'create ipset':
1536+
exec { 'create ipset blacklist':
15371537
command => 'ipset create blacklist hash:ip,port family inet6 maxelem 1024 hashsize 65535 timeout 120',
15381538
path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
15391539
require => Package['ipset'],
15401540
}
1541-
exec { 'add blacklist':
1541+
-> exec { 'create ipset honeypot':
1542+
command => 'ipset create honeypot hash:ip family inet6 maxelem 1024 hashsize 65535 timeout 120',
1543+
path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
1544+
}
1545+
-> exec { 'add blacklist':
15421546
command => 'ipset add blacklist 2001:db8::1,80',
15431547
path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
1544-
require => Exec['create ipset'],
1548+
}
1549+
-> exec { 'add honeypot':
1550+
command => 'ipset add honeypot 2001:db8::5',
1551+
path => '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
15451552
}
15461553
firewall { '612 - test':
15471554
ensure => present,
15481555
chain => 'INPUT',
15491556
proto => tcp,
15501557
action => drop,
1551-
ipset => 'blacklist src,src',
1558+
ipset => ['blacklist src,dst', '! honeypot dst'],
15521559
provider => 'ip6tables',
1553-
require => Exec['add blacklist'],
1560+
require => Exec['add honeypot'],
15541561
}
15551562
EOS
15561563

@@ -1559,7 +1566,7 @@ class { '::firewall': }
15591566

15601567
it 'should contain the rule' do
15611568
shell('ip6tables-save') do |r|
1562-
expect(r.stdout).to match(/-A INPUT -p tcp -m comment --comment "612 - test" -m set --match-set blacklist src,src -j DROP/)
1569+
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/)
15631570
end
15641571
end
15651572
end

0 commit comments

Comments
 (0)