141 changes: 88 additions & 53 deletions README.markdown
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#firewall
# firewall

[![Build Status](https://travis-ci.org/puppetlabs/puppetlabs-firewall.png?branch=master)](https://travis-ci.org/puppetlabs/puppetlabs-firewall)

####Table of Contents
#### Table of Contents

1. [Overview - What is the firewall module?](#overview)
2. [Module Description - What does the module do?](#module-description)
Expand Down Expand Up @@ -341,6 +339,19 @@ firewall { '100 my rule':
}
~~~

Setup NFLOG for a rule.

~~~puppet
firewall {'666 for NFLOG':
proto => 'all',
jump => 'NFLOG',
nflog_group => 3,
nflog_prefix => "nflog-test",
nflog_range = 256,
nflog_threshold => 1,
}
~~~

### Additional Information

Access the inline documentation:
Expand Down Expand Up @@ -393,6 +404,10 @@ Parameter that controls the state of the iptables package on your system, allowi

`ensure` can either be 'present' or 'latest'. Defaults to 'present'.

#### ebtables_manage

Parameter that controls whether puppet manages the ebtables package or not. If managed, the package will use the value of `pkg_ensure` as its ensure value.

#### service_name

Specify the name of the IPv4 iptables service. Defaults defined in `firewall::params`.
Expand All @@ -405,7 +420,7 @@ Specify the name of the IPv6 ip6tables service. Defaults defined in `firewall::p

Specify the platform-specific package(s) to install. Defaults defined in `firewall::params`.

###Type: firewall
### Type: firewall

This type enables you to manage firewall rules within Puppet.

Expand All @@ -419,7 +434,7 @@ This type enables you to manage firewall rules within Puppet.
* `iptables`: Iptables type provider
* Required binaries: `iptables-save`, `iptables`.
* Default for `kernel` == `linux`.
* Supported features: `address_type`, `clusterip`, `connection_limiting`, `dnat`, `icmp_match`, `interface_match`, `iprange`, `ipsec_dir`, `ipsec_policy`, `ipset`, `iptables`, `isfragment`, `length`, `log_level`, `log_prefix`, `log_uid`, `mark`, `mask`, `mss`, `netmap`, `owner`, `pkttype`, `queue_bypass`, `queue_num`, `rate_limiting`, `recent_limiting`, `reject_type`, `snat`, `socket`, `state_match`, `string_matching`, `tcp_flags`.
* Supported features: `address_type`, `clusterip`, `connection_limiting`, `dnat`, `icmp_match`, `interface_match`, `iprange`, `ipsec_dir`, `ipsec_policy`, `ipset`, `iptables`, `isfragment`, `length`, `log_level`, `log_prefix`, `log_uid`, `mark`, `mask`, `mss`, `netmap`, `nflog_group`, `nflog_prefix`, `nflog_range`, `nflog_threshold`, `owner`, `pkttype`, `queue_bypass`, `queue_num`, `rate_limiting`, `recent_limiting`, `reject_type`, `snat`, `socket`, `state_match`, `string_matching`, `tcp_flags`.

**Autorequires:**

Expand Down Expand Up @@ -471,6 +486,14 @@ If Puppet is managing the iptables or iptables-persistent packages, and the prov

* `mask`: The ability to match recent rules based on the ipv4 mask.

* `nflog_group`: The ability to set the group number for NFLOG.

* `nflog_prefix`: The ability to set a prefix for nflog messages.

* `nflog_range`: The ability to set nflog\_range.

* `nflog_threshold`: The ability to set nflog\_threshold.

* `owner`: The ability to match owners.

* `pkttype`: The ability to match a packet type.
Expand Down Expand Up @@ -590,7 +613,7 @@ If Puppet is managing the iptables or iptables-persistent packages, and the prov

* `islastfrag`: If true, matches when the packet is the last fragment of a fragmented ipv6 packet. Supported by ipv6 only. Valid values are 'true', 'false'. Requires the `islastfrag`.

* `jump`: The value for the iptables `--jump` parameter. Any valid chain name is allowed, but normal values are: 'QUEUE', 'RETURN', 'DNAT', 'SNAT', 'LOG', 'MASQUERADE', 'REDIRECT', 'MARK', 'TCPMSS', 'DSCP'.
* `jump`: The value for the iptables `--jump` parameter. Any valid chain name is allowed, but normal values are: 'QUEUE', 'RETURN', 'DNAT', 'SNAT', 'LOG', 'MASQUERADE', 'REDIRECT', 'MARK', 'TCPMSS', 'DSCP', 'NFLOG'.

For the values 'ACCEPT', 'DROP', and 'REJECT', you must use the generic `action` parameter. This is to enforce the use of generic parameters where possible for maximum cross-platform modeling.

Expand All @@ -610,6 +633,14 @@ If Puppet is managing the iptables or iptables-persistent packages, and the prov

* `log_uid`: The ability to log the userid of the process which generated the packet.

* `nflog_group`: When combined with `jump => 'NFLOG'` grants the ability to specify the NFLOG group number. Requires the `nflog_group` feature.

* `nflog_prefix`: When combined with `jump => 'NFLOG'` grants the ability to specify a prefix for log entries. Requires the `nflog_prefix` feature.

* `nflog_range`: When combined with `jump => 'NFLOG'` grants the ability to specify the number of bytes to be copied to userspace. Requires the `nflog_range` feature.

* `nflog_threshold`: When combined with `jump => 'NFLOG'` grants the ability to specify the size of the NFLOG threshold. Requires the `nflog_threshold` feature.

* `mask`: Sets the mask to use when `recent` is enabled. Requires the `mask` feature.

* `month_days`: Only match on the given days of the month. Possible values are '1' to '31'. Note that specifying '31' will not match on months that do not have a 31st day; the same goes for 28- or 29-day February.
Expand All @@ -621,12 +652,12 @@ If Puppet is managing the iptables or iptables-persistent packages, and the prov
* `name`: The canonical name of the rule. This name is also used for ordering, so make sure you prefix the rule with a number. For example:

~~~puppet
firewall { '000 this runs first':
# this rule will run first
}
firewall { '999 this runs last':
# this rule will run last
}
firewall { '000 this runs first':
# this rule will run first
}
firewall { '999 this runs last':
# this rule will run last
}
~~~

Depending on the provider, the name of the rule can be stored using the comment feature of the underlying firewall subsystem. Values must match '/^\d+[[:graph:][:space:]]+$/'.
Expand All @@ -639,6 +670,10 @@ firewall { '999 this runs last':

* `physdev_is_bridged`: Match if the packet is transversing a bridge. Valid values are true or false.

* `physdev_is_in`: Match if the packet has entered through a bridge interface. Valid values are true or false.

* `physdev_is_bridged`: Match if the packet will leave through a bridge interface. Valid values are true or false.

* `pkttype`: Sets the packet type to match. Valid values are: 'unicast', 'broadcast', and'multicast'. Requires the `pkttype` feature.

* `port`: *DEPRECATED* Using the unspecific 'port' parameter can lead to firewall rules that are unexpectedly too lax. It is recommended to always use the specific dport and sport parameters to avoid this ambiguity. The destination or source port to match for this filter (if the protocol supports ports). Will accept a single element or an array. For some firewall providers you can pass a range of ports in the format: 'start number-end number'. For example, '1-1024' would cover ports 1 to 1024.
Expand Down Expand Up @@ -675,29 +710,29 @@ firewall { '999 this runs last':

* `recent`: Enable the recent module. Valid values are: 'set', 'update', 'rcheck', or 'remove'. For example:

~~~puppet
# If anyone's appeared on the 'badguy' blacklist within
# the last 60 seconds, drop their traffic, and update the timestamp.
firewall { '100 Drop badguy traffic':
recent => 'update',
rseconds => 60,
rsource => true,
rname => 'badguy',
action => 'DROP',
chain => 'FORWARD',
}
# No-one should be sending us traffic on eth0 from localhost
# Blacklist them
firewall { '101 blacklist strange traffic':
recent => 'set',
rsource => true,
rname => 'badguy',
destination => '127.0.0.0/8',
iniface => 'eth0',
action => 'DROP',
chain => 'FORWARD',
}
~~~
~~~puppet
# If anyone's appeared on the 'badguy' blacklist within
# the last 60 seconds, drop their traffic, and update the timestamp.
firewall { '100 Drop badguy traffic':
recent => 'update',
rseconds => 60,
rsource => true,
rname => 'badguy',
action => 'DROP',
chain => 'FORWARD',
}
# No-one should be sending us traffic on eth0 from localhost
# Blacklist them
firewall { '101 blacklist strange traffic':
recent => 'set',
rsource => true,
rname => 'badguy',
destination => '127.0.0.0/8',
iniface => 'eth0',
action => 'DROP',
chain => 'FORWARD',
}
~~~

Requires the `recent_limiting` feature.

Expand Down Expand Up @@ -813,17 +848,17 @@ Currently this type supports only iptables, ip6tables, and ebtables on Linux. It
* `ignore`: Regex to perform on firewall rules to exempt unmanaged rules from purging (when enabled). This is matched against the output of iptables-save. This can be a single regex or an array of them. To support flags, use the ruby inline flag mechanism: a regex such as '/foo/i' can be written as '(?i)foo' or '(?i:foo)'. Only when purge is 'true'.

Full example:
~~~puppet
firewallchain { 'INPUT:filter:IPv4':
purge => true,
ignore => [
# ignore the fail2ban jump rule
'-j fail2ban-ssh',
# ignore any rules with "ignore" (case insensitive) in the comment in the rule
'--comment "[^"](?i:ignore)[^"]"',
],
}
~~~
~~~puppet
firewallchain { 'INPUT:filter:IPv4':
purge => true,
ignore => [
# ignore the fail2ban jump rule
'-j fail2ban-ssh',
# ignore any rules with "ignore" (case insensitive) in the comment in the rule
'--comment "[^"](?i:ignore)[^"]"',
],
}
~~~

* `name`: Specify the canonical name of the chain. For iptables the format must be {chain}:{table}:{protocol}.

Expand All @@ -844,13 +879,13 @@ firewallchain { 'INPUT:filter:IPv4':

* `purge`: Purge unmanaged firewall rules in this chain. Valid values are 'false', 'true'.

**Note** This `purge` is purging unmanaged rules in a firewall chain, not unmanaged firewall chains. To purge unmanaged firewall chains, use the following instead.
**Note** This `purge` is purging unmanaged rules in a firewall chain, not unmanaged firewall chains. To purge unmanaged firewall chains, use the following instead.

~~~puppet
resources { 'firewallchain':
purge => true,
}
~~~
~~~puppet
resources { 'firewallchain':
purge => true,
}
~~~

### Fact: ip6tables_version

Expand Down
7 changes: 3 additions & 4 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
require 'puppet_blacksmith/rake_tasks'
require 'puppet-lint/tasks/puppet-lint'
require 'puppetlabs_spec_helper/rake_tasks'
require 'puppet-lint/tasks/puppet-lint'
require 'puppet_blacksmith/rake_tasks' if Bundler.rubygems.find_name('puppet-blacksmith').any?

PuppetLint.configuration.fail_on_warnings = true
PuppetLint.configuration.send('relative')
PuppetLint.configuration.send('disable_documentation')
PuppetLint.configuration.send('disable_single_quote_string_with_variables')

desc 'Generate pooler nodesets'
task :gen_nodeset do
Expand Down
19 changes: 14 additions & 5 deletions lib/puppet/provider/firewall/ip6tables.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ def self.iptables_save(*args)
:physdev_in => "--physdev-in",
:physdev_out => "--physdev-out",
:physdev_is_bridged => "--physdev-is-bridged",
:physdev_is_in => "--physdev-is-in",
:physdev_is_out => "--physdev-is-out",
:date_start => "--datestart",
:date_stop => "--datestop",
:time_start => "--timestart",
Expand All @@ -153,6 +155,8 @@ def self.iptables_save(*args)
:week_days => "--weekdays",
:time_contiguous => "--contiguous",
:kernel_timezone => "--kerneltz",
:src_cc => "--source-country",
:dst_cc => "--destination-country",
}

# These are known booleans that do not take a value, but we want to munge
Expand All @@ -170,12 +174,14 @@ def self.iptables_save(*args)
:rttl,
:socket,
:physdev_is_bridged,
:physdev_is_in,
:physdev_is_out,
:time_contiguous,
:kernel_timezone,
:queue_bypass,
]

# Properties that use "-m <ipt module name>" (with the potential to have multiple
# Properties that use "-m <ipt module name>" (with the potential to have multiple
# arguments against the same IPT module) must be in this hash. The keys in this
# hash are the IPT module names, with the values being an array of the respective
# supported arguments for this IPT module.
Expand All @@ -188,11 +194,12 @@ def self.iptables_save(*args)
# ones.
#
@module_to_argument_mapping = {
:physdev => [:physdev_in, :physdev_out, :physdev_is_bridged],
:physdev => [:physdev_in, :physdev_out, :physdev_is_bridged, :physdev_is_in, :physdev_is_out],
:addrtype => [:src_type, :dst_type],
:iprange => [:src_range, :dst_range],
:owner => [:uid, :gid],
:time => [:time_start, :time_stop, :month_days, :week_days, :date_start, :date_stop, :time_contiguous, :kernel_timezone]
:time => [:time_start, :time_stop, :month_days, :week_days, :date_start, :date_stop, :time_contiguous, :kernel_timezone],
:geoip => [:src_cc, :dst_cc]
}

# Create property methods dynamically
Expand Down Expand Up @@ -230,13 +237,15 @@ def self.iptables_save(*args)
# 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, :physdev_in,
:physdev_out, :physdev_is_bridged, :proto, :ishasmorefrags, :islastfrag, :isfirstfrag, :src_range, :dst_range,
:physdev_out, :physdev_is_bridged, :physdev_is_in, :physdev_is_out,
:proto, :ishasmorefrags, :islastfrag, :isfirstfrag, :src_range, :dst_range,
:tcp_flags, :uid, :gid, :mac_source, :sport, :dport, :port, :src_type,
:dst_type, :socket, :pkttype, :name, :ipsec_dir, :ipsec_policy, :state,
:ctstate, :icmp, :hop_limit, :limit, :burst, :length, :recent, :rseconds, :reap,
:rhitcount, :rttl, :rname, :mask, :rsource, :rdest, :ipset, :string, :string_algo,
:string_from, :string_to, :jump, :clamp_mss_to_pmtu, :gateway, :todest,
:tosource, :toports, :checksum_fill, :log_level, :log_prefix, :log_uid, :reject, :set_mss, :set_dscp, :set_dscp_class, :mss, :queue_num, :queue_bypass,
:set_mark, :match_mark, :connlimit_above, :connlimit_mask, :connmark, :time_start, :time_stop, :month_days, :week_days, :date_start, :date_stop, :time_contiguous, :kernel_timezone]
:set_mark, :match_mark, :connlimit_above, :connlimit_mask, :connmark, :time_start, :time_stop, :month_days, :week_days, :date_start, :date_stop, :time_contiguous, :kernel_timezone,
:src_cc, :dst_cc]

end
51 changes: 38 additions & 13 deletions lib/puppet/provider/firewall/iptables.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
has_feature :log_uid
has_feature :mark
has_feature :mss
has_feature :nflog_group
has_feature :nflog_prefix
has_feature :nflog_range
has_feature :nflog_threshold
has_feature :tcp_flags
has_feature :pkttype
has_feature :isfragment
Expand Down Expand Up @@ -88,6 +92,10 @@
:match_mark => "-m mark --mark",
:mss => '-m tcpmss --mss',
:name => "-m comment --comment",
:nflog_group => "--nflog-group",
:nflog_prefix => "--nflog-prefix",
:nflog_range => "--nflog-range",
:nflog_threshold => "--nflog-threshold",
:outiface => "-o",
:pkttype => "-m pkttype --pkt-type",
:port => '-m multiport --ports',
Expand Down Expand Up @@ -132,6 +140,8 @@
:physdev_in => "--physdev-in",
:physdev_out => "--physdev-out",
:physdev_is_bridged => "--physdev-is-bridged",
:physdev_is_in => "--physdev-is-in",
:physdev_is_out => "--physdev-is-out",
:date_start => "--datestart",
:date_stop => "--datestop",
:time_start => "--timestart",
Expand All @@ -146,6 +156,8 @@
:clusterip_total_nodes => "--total-nodes",
:clusterip_local_node => "--local-node",
:clusterip_hash_init => "--hash-init",
:src_cc => "--source-country",
:dst_cc => "--destination-country",
}

# These are known booleans that do not take a value, but we want to munge
Expand All @@ -162,6 +174,8 @@
:rttl,
:socket,
:physdev_is_bridged,
:physdev_is_in,
:physdev_is_out,
:time_contiguous,
:kernel_timezone,
:clusterip_new,
Expand All @@ -181,11 +195,12 @@
# ones.
#
@module_to_argument_mapping = {
:physdev => [:physdev_in, :physdev_out, :physdev_is_bridged],
:physdev => [:physdev_in, :physdev_out, :physdev_is_bridged, :physdev_is_in, :physdev_is_out],
:addrtype => [:src_type, :dst_type],
:iprange => [:src_range, :dst_range],
:owner => [:uid, :gid],
:time => [:time_start, :time_stop, :month_days, :week_days, :date_start, :date_stop, :time_contiguous, :kernel_timezone]
:time => [:time_start, :time_stop, :month_days, :week_days, :date_start, :date_stop, :time_contiguous, :kernel_timezone],
:geoip => [:src_cc, :dst_cc]
}

def self.munge_resource_map_from_existing_values(resource_map_original, compare)
Expand Down Expand Up @@ -263,18 +278,20 @@ def munge_resource_map_from_resource(resource_map_original, compare)
# 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
@resource_list = [
:table, :source, :destination, :iniface, :outiface, :physdev_in, :physdev_out, :physdev_is_bridged, :proto, :isfragment,
:stat_mode, :stat_every, :stat_packet, :stat_probability,
:table, :source, :destination, :iniface, :outiface,
:physdev_in, :physdev_out, :physdev_is_bridged, :physdev_is_in, :physdev_is_out,
:proto, :isfragment, :stat_mode, :stat_every, :stat_packet, :stat_probability,
:src_range, :dst_range, :tcp_flags, :uid, :gid, :mac_source, :sport, :dport, :port,
:src_type, :dst_type, :socket, :pkttype, :name, :ipsec_dir, :ipsec_policy,
:state, :ctstate, :icmp, :limit, :burst, :length, :recent, :rseconds, :reap,
:rhitcount, :rttl, :rname, :mask, :rsource, :rdest, :ipset, :string, :string_algo,
:string_from, :string_to, :jump, :goto, :clusterip_new, :clusterip_hashmode,
:clusterip_clustermac, :clusterip_total_nodes, :clusterip_local_node, :clusterip_hash_init, :queue_num, :queue_bypass,
:clamp_mss_to_pmtu, :gateway, :set_mss, :set_dscp, :set_dscp_class, :todest, :tosource, :toports, :to, :checksum_fill, :random, :log_prefix,
:nflog_group, :nflog_prefix, :nflog_range, :nflog_threshold, :clamp_mss_to_pmtu, :gateway,
:set_mss, :set_dscp, :set_dscp_class, :todest, :tosource, :toports, :to, :checksum_fill, :random, :log_prefix,
:log_level, :log_uid, :reject, :set_mark, :match_mark, :mss, :connlimit_above, :connlimit_mask, :connmark, :time_start, :time_stop,
:month_days, :week_days, :date_start, :date_stop, :time_contiguous, :kernel_timezone
]
:month_days, :week_days, :date_start, :date_stop, :time_contiguous, :kernel_timezone,
:src_cc, :dst_cc ]

def insert
debug 'Inserting rule %s' % resource[:name]
Expand Down Expand Up @@ -367,6 +384,8 @@ def self.rule_to_hash(line, table, counter)
end
# we do a similar thing for negated address masks (source and destination).
values = values.gsub(/(-\S+) (!)\s?(\S*)/,'\1 "\2 \3"')
# fix negated physdev rules
values = values.gsub(/-m physdev ! (--physdev-is-\S+)/, '-m physdev \1 "!"')
# the actual rule will have the ! mark before the option.
values = values.gsub(/(!)\s*(-\S+)\s*(\S*)/, '\2 "\1 \3"')
# The match extension for tcp & udp are optional and throws off the @resource_map.
Expand Down Expand Up @@ -400,7 +419,8 @@ def self.rule_to_hash(line, table, counter)
# distinguish between -f and the '-f' inside of --tcp-flags.
values = values.sub(/\s-f(?!l)(?=.*--comment)/, ' -f true')
else
values = values.sub(/#{resource_map[bool]}/, "#{resource_map[bool]} true")
# append `true` to booleans that are not already negated (followed by "!")
values = values.sub(/#{resource_map[bool]}(?! "!")/, "#{resource_map[bool]} true")
end
end

Expand Down Expand Up @@ -489,13 +509,10 @@ def self.rule_to_hash(line, table, counter)
end
end


# Convert booleans removing the previous cludge we did
@known_booleans.each do |bool|
if hash[bool] != nil then
if hash[bool] != "true" then
raise "Parser error: #{bool} was meant to be a boolean but received value: #{hash[bool]}."
end
unless [nil, 'true', '!'].include?(hash[bool])
raise "Parser error: #{bool} was meant to be a boolean but received value: #{hash[bool]}."
end
end

Expand All @@ -521,6 +538,9 @@ def self.rule_to_hash(line, table, counter)
:dst_range,
:dst_type,
:port,
:physdev_is_bridged,
:physdev_is_in,
:physdev_is_out,
:proto,
:source,
:sport,
Expand Down Expand Up @@ -636,6 +656,11 @@ def general_args
args << ['--wait']
end

#nflog options are not available on older OSes
[:nflog_group,:nflog_prefix,:nflog_threshold,:nflog_range].each do |nflog_feature|
fail "#{nflog_feature} is not available on iptables version #{iptables_version}" if resource[nflog_feature] && (iptables_version && iptables_version < '1.3.7')
end

resource_list.each do |res|
resource_value = nil
if (resource[res]) then
Expand Down
95 changes: 94 additions & 1 deletion lib/puppet/type/firewall.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
feature :ipsec_policy, "Match IPsec policy"
feature :ipsec_dir, "Match IPsec policy direction"
feature :mask, "Ability to match recent rules based on the ipv4 mask"
feature :nflog_group, "netlink group to subscribe to for logging"
feature :nflog_prefix, ""
feature :nflog_range, ""
feature :nflog_threshold, ""
feature :ipset, "Match against specified ipset list"
feature :clusterip, "Configure a simple cluster of nodes that share a certain IP and MAC address without an explicit load balancer in front of them."
feature :length, "Match the length of layer-3 payload"
Expand Down Expand Up @@ -448,6 +452,7 @@ def should_to_s(value)
* DNAT
* SNAT
* LOG
* NFLOG
* MASQUERADE
* REDIRECT
* MARK
Expand Down Expand Up @@ -618,6 +623,67 @@ def should_to_s(value)
newvalues(:true, :false)
end

newproperty(:nflog_group, :required_features => :nflog_group) do
desc <<-EOS
Used with the jump target NFLOG.
The netlink group (0 - 2^16-1) to which packets are (only applicable
for nfnetlink_log). Defaults to 0.
EOS

validate do |value|
if value.to_i > (2**16)-1 || value.to_i < 0
raise ArgumentError, "nflog_group must be between 0 and 2^16-1"
end
end

munge do |value|
if value.is_a?(String) and value =~ /^[-0-9]+$/
Integer(value)
else
value
end
end
end

newproperty(:nflog_prefix, :required_features => :nflog_prefix) do
desc <<-EOS
Used with the jump target NFLOG.
A prefix string to include in the log message, up to 64 characters long,
useful for distinguishing messages in the logs.
EOS

validate do |value|
if value.length > 64
raise ArgumentError, "nflog_prefix must be less than 64 characters."
end
end
end

newproperty(:nflog_range, :required_features => :nflog_range) do
desc <<-EOS
Used with the jump target NFLOG.
The number of bytes to be copied to userspace (only applicable for nfnetlink_log).
nfnetlink_log instances may specify their own range, this option overrides it.
EOS
end

newproperty(:nflog_threshold, :required_features => :nflog_threshold) do
desc <<-EOS
Used with the jump target NFLOG.
Number of packets to queue inside the kernel before sending them to userspace
(only applicable for nfnetlink_log). Higher values result in less overhead
per packet, but increase delay until the packets reach userspace. Defaults to 1.
EOS

munge do |value|
if value.is_a?(String) and value =~ /^[-0-9]+$/
Integer(value)
else
value
end
end
end

# ICMP matching property
newproperty(:icmp, :required_features => :icmp_match) do
desc <<-EOS
Expand Down Expand Up @@ -1253,6 +1319,20 @@ def should_to_s(value)
newvalues(:true, :false)
end

newproperty(:physdev_is_in, :required_features => :iptables) do
desc <<-EOS
Matches if the packet has entered through a bridge interface.
EOS
newvalues(:true, :false)
end

newproperty(:physdev_is_out, :required_features => :iptables) do
desc <<-EOS
Matches if the packet will leave through a bridge interface.
EOS
newvalues(:true, :false)
end

newproperty(:date_start, :required_features => :iptables) do
desc <<-EOS
Only match during the given time, which must be in ISO 8601 "T" notation.
Expand Down Expand Up @@ -1485,6 +1565,19 @@ def should_to_s(value)
newvalues(:true, :false)
end

newproperty(:src_cc) do
desc <<-EOS
src attribute for the module geoip
EOS
newvalues(/^[A-Z]{2}(,[A-Z]{2})*$/)
end

newproperty(:dst_cc) do
desc <<-EOS
dst attribute for the module geoip
EOS
newvalues(/^[A-Z]{2}(,[A-Z]{2})*$/)
end

autorequire(:firewallchain) do
reqs = []
Expand Down Expand Up @@ -1528,7 +1621,7 @@ def should_to_s(value)
end

# autobefore is only provided since puppet 4.0
if Puppet.version.to_f >= 4.0
if Puppet::Util::Package.versioncmp(Puppet.version, '4.0') >= 0
# On RHEL 7 this needs to be threaded correctly to manage SE Linux permissions after persisting the rules
autobefore(:file) do
[ '/etc/sysconfig/iptables', '/etc/sysconfig/ip6tables' ]
Expand Down
2 changes: 2 additions & 0 deletions manifests/init.pp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
$service_name = $::firewall::params::service_name,
$service_name_v6 = $::firewall::params::service_name_v6,
$package_name = $::firewall::params::package_name,
$ebtables_manage = false,
) inherits ::firewall::params {
case $ensure {
/^(running|stopped)$/: {
Expand All @@ -35,6 +36,7 @@
service_name => $service_name,
service_name_v6 => $service_name_v6,
package_name => $package_name,
ebtables_manage => $ebtables_manage,
}
contain "${title}::linux"
}
Expand Down
7 changes: 7 additions & 0 deletions manifests/linux.pp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
$service_name = $::firewall::params::service_name,
$service_name_v6 = $::firewall::params::service_name_v6,
$package_name = $::firewall::params::package_name,
$ebtables_manage = false,
) inherits ::firewall::params {
$enable = $ensure ? {
running => true,
Expand All @@ -27,6 +28,12 @@
ensure => $pkg_ensure,
}

if $ebtables_manage {
package { 'ebtables':
ensure => $pkg_ensure,
}
}

case $::operatingsystem {
'RedHat', 'CentOS', 'Fedora', 'Scientific', 'SL', 'SLC', 'Ascendos',
'CloudLinux', 'PSBM', 'OracleLinux', 'OVS', 'OEL', 'Amazon', 'XenServer',
Expand Down
35 changes: 21 additions & 14 deletions manifests/linux/redhat.pp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
service { 'firewalld':
ensure => stopped,
enable => false,
before => Package[$package_name],
before => [Package[$package_name], Service[$service_name]],
}
}

Expand Down Expand Up @@ -84,35 +84,42 @@
File["/etc/sysconfig/${service_name_v6}"] -> Service[$service_name_v6]
}

# Redhat 7 selinux user context for /etc/sysconfig/iptables is set to unconfined_u
# Redhat 7 selinux type context for /etc/sysconfig/iptables is set to etc_t
# Redhat 7 selinux user context for /etc/sysconfig/iptables is set to system_u
# Redhat 7 selinux type context for /etc/sysconfig/iptables is set to system_conf_t
case $::selinux {
#lint:ignore:quoted_booleans
'true',true: {
case $::operatingsystemrelease {
/^7\..*/: {
case $::operatingsystem {
'CentOS': {
case $::operatingsystem {
'CentOS': {
case $::operatingsystemrelease {
/^6\..*/: {
File["/etc/sysconfig/${service_name}"] { seluser => 'unconfined_u', seltype => 'system_conf_t' }
File["/etc/sysconfig/${service_name_v6}"] { seluser => 'unconfined_u', seltype => 'system_conf_t' }
}

/^7\..*/: {
File["/etc/sysconfig/${service_name}"] { seluser => 'system_u', seltype => 'system_conf_t' }
File["/etc/sysconfig/${service_name_v6}"] { seluser => 'system_u', seltype => 'system_conf_t' }
}

default : {
File["/etc/sysconfig/${service_name}"] { seluser => 'unconfined_u', seltype => 'etc_t' }
File["/etc/sysconfig/${service_name_v6}"] { seluser => 'unconfined_u', seltype => 'etc_t' }
}
}
}
/^6\..*/: {
File["/etc/sysconfig/${service_name}"] { seluser => 'unconfined_u', seltype => 'system_conf_t' }
File["/etc/sysconfig/${service_name_v6}"] { seluser => 'unconfined_u', seltype => 'system_conf_t' }
}
default: {

# Fedora uses the same SELinux context as Redhat
'Fedora': {
File["/etc/sysconfig/${service_name}"] { seluser => 'system_u', seltype => 'system_conf_t' }
File["/etc/sysconfig/${service_name_v6}"] { seluser => 'unconfined_u', seltype => 'system_conf_t' }
File["/etc/sysconfig/${service_name_v6}"] { seluser => 'system_u', seltype => 'system_conf_t' }
}

default: {}

}
}
default: {}
default: {}
#lint:endignore
}
}
6 changes: 2 additions & 4 deletions metadata.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "puppetlabs-firewall",
"version": "1.8.2",
"version": "1.9.0",
"author": "Puppet Labs",
"summary": "Manages Firewalls such as iptables",
"license": "Apache-2.0",
Expand Down Expand Up @@ -57,10 +57,8 @@
{
"operatingsystem": "Ubuntu",
"operatingsystemrelease": [
"10.04",
"12.04",
"14.04",
"16.04"
"16.04"
]
},
{
Expand Down
52 changes: 51 additions & 1 deletion spec/acceptance/firewall_bridging_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,56 @@ class { '::firewall': }
end
end
end

context 'physdev_is_in' do
it 'applies' do
pp = <<-EOS
class { '::firewall': }
firewall { '708 - test':
provider => 'ip6tables',
chain => 'FORWARD',
proto => tcp,
port => '708',
action => accept,
physdev_is_in => true,
}
EOS

apply_manifest(pp, :catch_failures => true)
apply_manifest(pp, :catch_changes => do_catch_changes)
end

it 'should contain the rule' do
shell('ip6tables-save') do |r|
expect(r.stdout).to match(/-A FORWARD -p tcp -m physdev\s+--physdev-is-in -m multiport --ports 708 -m comment --comment "708 - test" -j ACCEPT/)
end
end
end

context 'physdev_is_out' do
it 'applies' do
pp = <<-EOS
class { '::firewall': }
firewall { '709 - test':
provider => 'ip6tables',
chain => 'FORWARD',
proto => tcp,
port => '709',
action => accept,
physdev_is_out => true,
}
EOS

apply_manifest(pp, :catch_failures => true)
apply_manifest(pp, :catch_changes => do_catch_changes)
end

it 'should contain the rule' do
shell('ip6tables-save') do |r|
expect(r.stdout).to match(/-A FORWARD -p tcp -m physdev\s+--physdev-is-out -m multiport --ports 709 -m comment --comment "709 - test" -j ACCEPT/)
end
end
end
end
end
end
end
135 changes: 135 additions & 0 deletions spec/acceptance/nflog_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
require 'spec_helper_acceptance'

describe 'nflog on older OSes', :if => fact('iptables_version') < '1.3.7' do
let(:pp) { <<-EOS
class {'::firewall': }
firewall { '503 - test':
jump => 'NFLOG',
proto => 'all',
nflog_group => 3,
}
EOS
}
it 'should throw an error' do
apply_manifest(pp, :acceptable_error_codes => [0])
end
end

describe 'nflog', :unless => fact('iptables_version') < '1.3.7' do
describe 'nflog_group' do

let(:group) { 3 }

it 'applies' do
pp = <<-EOS
class {'::firewall': }
firewall { '503 - test':
jump => 'NFLOG',
proto => 'all',
nflog_group => #{group},
}
EOS
apply_manifest(pp, :catch_failures => true)
end

it 'contains the rule' do
shell('iptables-save') do |r|
expect(r.stdout).to match(/NFLOG --nflog-group #{group}/)
end
end
end

describe 'nflog_prefix' do

let(:prefix) { "TEST PREFIX" }

it 'applies' do
pp = <<-EOS
class {'::firewall': }
firewall { '503 - test':
jump => 'NFLOG',
proto => 'all',
nflog_prefix => '#{prefix}',
}
EOS
apply_manifest(pp, :catch_failures => true)
end

it 'contains the rule' do
shell('iptables-save') do |r|
expect(r.stdout).to match(/NFLOG --nflog-prefix +"#{prefix}"/)
end
end
end

describe 'nflog_range' do

let(:range) { 16 }

it 'applies' do
pp = <<-EOS
class {'::firewall': }
firewall { '503 - test':
jump => 'NFLOG',
proto => 'all',
nflog_range => #{range},
}
EOS
apply_manifest(pp, :catch_failures => true)
end

it 'contains the rule' do
shell('iptables-save') do |r|
expect(r.stdout).to match(/NFLOG --nflog-range #{range}/)
end
end
end

describe 'nflog_threshold' do

let(:threshold) { 2 }

it 'applies' do
pp = <<-EOS
class {'::firewall': }
firewall { '503 - test':
jump => 'NFLOG',
proto => 'all',
nflog_threshold => #{threshold},
}
EOS
apply_manifest(pp, :catch_failures => true)
end

it 'contains the rule' do
shell('iptables-save') do |r|
expect(r.stdout).to match(/NFLOG --nflog-threshold #{threshold}/)
end
end
end

describe 'multiple rules' do
let(:threshold) { 2 }
let(:group) { 3 }

it 'applies' do
pp = <<-EOS
class {'::firewall': }
firewall { '503 - test':
jump => 'NFLOG',
proto => 'all',
nflog_threshold => #{threshold},
nflog_group => #{group}
}
EOS
apply_manifest(pp, :catch_failures => true)
end

it 'contains the rules' do
shell('iptables-save') do |r|
expect(r.stdout).to match(/NFLOG --nflog-group #{group} --nflog-threshold #{threshold}/)
end
end

end
end
9 changes: 3 additions & 6 deletions spec/spec_helper_acceptance.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'beaker-rspec'
require 'beaker/puppet_install_helper'
require 'beaker/module_install_helper'

def iptables_flush_all_tables
['filter', 'nat', 'mangle', 'raw'].each do |t|
Expand All @@ -22,18 +23,14 @@ def do_catch_changes
end

run_puppet_install_helper
install_module_on(hosts)
install_module_dependencies_on(hosts)

RSpec.configure do |c|
# Project root
proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))

# Configure all nodes in nodeset
c.before :suite do
# Install module and dependencies
hosts.each do |host|
copy_module_to(host, :source => proj_root, :module_name => 'firewall')
on host, puppet('module install puppetlabs-stdlib --version 3.2.0')

# the ubuntu-14.04 docker image doesn't carry the iptables command
apply_manifest_on host, 'package { "iptables": ensure => installed }' if fact('osfamily') == 'Debian'
end
Expand Down
2 changes: 1 addition & 1 deletion spec/unit/classes/firewall_linux_redhat_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
it { should contain_service('firewalld').with(
:ensure => 'stopped',
:enable => false,
:before => 'Package[iptables-services]'
:before => ['Package[iptables-services]', 'Service[iptables]']
)}

it { should contain_package('iptables-services').with(
Expand Down
6 changes: 6 additions & 0 deletions spec/unit/classes/firewall_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,10 @@
let(:params) {{ :ensure => 'test' }}
it { expect { should contain_class('firewall::linux') }.to raise_error(Puppet::Error) }
end

context 'ebtables_manage => true' do
let(:facts) {{ :kernel => 'Linux' }}
let(:params) {{ :ebtables_manage => true }}
it { expect { should contain_package('ebtables') }.to raise_error(Puppet::Error) }
end
end
2 changes: 1 addition & 1 deletion spec/unit/puppet/provider/ip6tables_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env rspec

require 'spec_helper'
if Puppet.version < '3.4.0'
if Puppet::Util::Package.versioncmp(Puppet.version, '3.4.0') < 0
require 'puppet/provider/confine/exists'
else
require 'puppet/confine/exists'
Expand Down
4 changes: 2 additions & 2 deletions spec/unit/puppet/provider/iptables_chain_spec.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/usr/bin/env rspec

require 'spec_helper'
if Puppet.version < '3.4.0'
if Puppet::Util::Package.versioncmp(Puppet.version, '3.4.0') < 0
require 'puppet/provider/confine/exists'
else
require 'puppet/confine/exists'
end

describe 'iptables chain provider detection' do
if Puppet.version < '3.4.0'
if Puppet::Util::Package.versioncmp(Puppet.version, '3.4.0') < 0
let(:exists) {
Puppet::Provider::Confine::Exists
}
Expand Down
4 changes: 2 additions & 2 deletions spec/unit/puppet/provider/iptables_spec.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/usr/bin/env rspec

require 'spec_helper'
if Puppet.version < '3.4.0'
if Puppet::Util::Package.versioncmp(Puppet.version, '3.4.0') < 0
require 'puppet/provider/confine/exists'
else
require 'puppet/confine/exists'
end

describe 'iptables provider detection' do
if Puppet.version < '3.4.0'
if Puppet::Util::Package.versioncmp(Puppet.version, '3.4.0') < 0
let(:exists) {
Puppet::Provider::Confine::Exists
}
Expand Down
34 changes: 33 additions & 1 deletion spec/unit/puppet/type/firewall_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
expect(res.parameters[:jump]).to eql nil
end

['QUEUE', 'RETURN', 'DNAT', 'SNAT', 'LOG', 'MASQUERADE', 'REDIRECT', 'MARK'].each do |jump|
['QUEUE', 'RETURN', 'DNAT', 'SNAT', 'LOG', 'NFLOG', 'MASQUERADE', 'REDIRECT', 'MARK'].each do |jump|
it "should accept jump value #{jump}" do
@resource[:jump] = jump
expect(@resource[:jump]).to eql jump
Expand Down Expand Up @@ -263,6 +263,38 @@
end
end

describe 'NFLOG' do
describe ':nflog_group' do

[0,1,5,10].each do |v|
it {
@resource[:nflog_group] = v
expect(@resource[:nflog_group]).to eq v
}
end

[-3,999999].each do |v|
it {
expect(lambda { @resource[:nflog_group] = v }).to raise_error(Puppet::Error, /2\^16\-1/)
}
end
end

describe ':nflog_prefix' do
let(:valid_prefix) { "This is a valid prefix" }
let(:invalid_prefix) { "This is not a valid prefix. !t is longer than 64 char@cters for sure. How do I know? I c0unted." }

it {
@resource[:nflog_prefix] = valid_prefix
expect(@resource[:nflog_prefix]).to eq valid_prefix
}

it {
expect(lambda { @resource[:nflog_prefix] = invalid_prefix }).to raise_error(Puppet::Error, /64 characters/)
}
end
end

describe ':icmp' do
icmp_codes = {
:iptables => {
Expand Down
1 change: 1 addition & 0 deletions spec/unit/puppet/util/ipcidr_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'spec_helper'
require 'puppet/util/ipcidr'

describe 'Puppet::Util::IPCidr' do
describe 'ipv4 address' do
Expand Down