1,209 changes: 489 additions & 720 deletions CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ group :development do
gem "github_changelog_generator", '= 1.15.2', require: false
end
group :system_tests do
gem "puppet_litmus", '< 1.0.0', require: false, platforms: [:ruby, :x64_mingw]
gem "puppet_litmus", '~> 1.0', require: false, platforms: [:ruby, :x64_mingw]
gem "serverspec", '~> 2.41', require: false
end

Expand Down
15 changes: 4 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,10 @@ resources { 'firewallchain':
}
```

> **Note:** If there are unmanaged rules in unmanaged chains, it will take a second Puppet run for the firewall chain to be purged.
> **Note:** If you need more fine-grained control about which unmananged rules get removed, investigate the `purge` and `ignore_foreign` parameters available in `firewallchain`.
> **Note:** `ignore_foreign` of `firewallchain` does not work as expected with a resources purge of `firewall`.
### Upgrading

Use these steps if you already have a version of the firewall module installed.
Expand Down Expand Up @@ -394,16 +394,9 @@ firewall {'666 for NFLOG':

It is possible for an unmanaged rule to exist on the target system that has the same comment as the rule specified in the manifest. This configuration is not supported by the firewall module.

In the event of a duplicate rule, the module will by default display a warning message notifying the user that it has found a duplicate but will continue to update the resource.

This behaviour is configurable via the `onduplicaterulebehaviour` parameter. Users can choose from the following behaviours:

* `ignore` - The duplicate rule is ignored and any updates to the resource will continue unaffected.
* `warn` - The duplicate rule is logged as a warning and any updates to the resource will continue unaffected.
* `error` - The duplicate rule is logged as an error and any updates to the resource will be skipped.
In the event of a duplicate rule, the module will throw an error message notifying the user that it has found a duplicate and halt in it's update.

With either the `ignore` or `warn` (default) behaviour, Puppet may create another duplicate rule.
To prevent this behavior and report the resource as failing during the Puppet run, specify the `error` behaviour.
This behaviour was previously configurable via the `onduplicaterulebehaviour` parameter. However the implementation of this resulted in a massive slowdown of the module and so this has been removed in favour of a simple error being thrown whenever a duplicate is detected.

### Additional information

Expand Down
19 changes: 0 additions & 19 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1382,7 +1382,6 @@ The following parameters are available in the `firewall` type.

* [`line`](#-firewall--line)
* [`name`](#-firewall--name)
* [`onduplicaterulebehaviour`](#-firewall--onduplicaterulebehaviour)
* [`provider`](#-firewall--provider)

##### <a name="-firewall--line"></a>`line`
Expand All @@ -1404,24 +1403,6 @@ so make sure you prefix the rule with a number:
Depending on the provider, the name of the rule can be stored using
the comment feature of the underlying firewall subsystem.

##### <a name="-firewall--onduplicaterulebehaviour"></a>`onduplicaterulebehaviour`

Valid values: `ignore`, `warn`, `error`

In certain situations it is possible for an unmanaged rule to exist
on the target system that has the same comment as the rule
specified in the manifest.

This setting determines what happens when such a duplicate is found.

It offers three options:

* ignore - The duplicate rule is ignored and any updates to the resource will continue unaffected.
* warn - The duplicate rule is logged as a warning and any updates to the resource will continue unaffected.
* error - The duplicate rule is logged as an error and any updates to the resource will be skipped.

Default value: `warn`

##### <a name="-firewall--provider"></a>`provider`

The specific backend to use for this `firewall` resource. You will seldom need to specify this --- Puppet will usually
Expand Down
19 changes: 10 additions & 9 deletions lib/puppet/provider/firewall/ip6tables.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
has_feature :nflog_prefix
has_feature :nflog_range
has_feature :nflog_threshold
has_feature :tcp_option
has_feature :tcp_flags
has_feature :pkttype
has_feature :ishasmorefrags
Expand All @@ -52,31 +53,30 @@

confine kernel: :linux

ip6tables_version = Facter.value('ip6tables_version')
mark_flag = if ip6tables_version && Puppet::Util::Package.versioncmp(ip6tables_version, '1.4.1') < 0
const_set(:Ip6tables_version, Facter.value('ip6tables_version'))
mark_flag = if const_get(:Ip6tables_version) && Puppet::Util::Package.versioncmp(const_get(:Ip6tables_version), '1.4.1') < 0
'--set-mark'
else
'--set-xmark'
end

kernelversion = Facter.value('kernelversion')
if (kernelversion && Puppet::Util::Package.versioncmp(kernelversion, '3.13') >= 0) &&
(ip6tables_version && Puppet::Util::Package.versioncmp(ip6tables_version, '1.6.2') >= 0)
(const_get(:Ip6tables_version) && Puppet::Util::Package.versioncmp(const_get(:Ip6tables_version), '1.6.2') >= 0)
has_feature :random_fully
end

if (kernelversion && Puppet::Util::Package.versioncmp(kernelversion, '3.3') >= 0) &&
(ip6tables_version && Puppet::Util::Package.versioncmp(ip6tables_version, '1.4.13') >= 0)
(const_get(:Ip6tables_version) && Puppet::Util::Package.versioncmp(const_get(:Ip6tables_version), '1.4.13') >= 0)
has_feature :rpfilter
end

if ip6tables_version && Puppet::Util::Package.versioncmp(ip6tables_version, '1.6.1') >= 0
if const_get(:Ip6tables_version) && Puppet::Util::Package.versioncmp(const_get(:Ip6tables_version), '1.6.1') >= 0
has_feature :nflog_size
end

def initialize(*args)
ip6tables_version = Facter.value('ip6tables_version')
raise ArgumentError, 'The ip6tables provider is not supported on version 1.3 of iptables' if ip6tables_version&.match(%r{1\.3\.\d})
raise ArgumentError, 'The ip6tables provider is not supported on version 1.3 of iptables' if Puppet::Type::Firewall::ProviderIp6tables::Ip6tables_version&.match(%r{1\.3\.\d})
super
end

Expand Down Expand Up @@ -183,7 +183,8 @@ def self.iptables_save(*args)
string_from: '--from',
string_to: '--to',
table: '-t',
tcp_flags: '-m tcp --tcp-flags',
tcp_option: '--tcp-option',
tcp_flags: '--tcp-flags',
todest: '--to-destination',
toports: '--to-ports',
tosource: '--to-source',
Expand Down Expand Up @@ -312,7 +313,7 @@ def self.iptables_save(*args)
@resource_list = [:table, :source, :destination, :iniface, :outiface, :physdev_in,
: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,
:tcp_option, :tcp_flags, :uid, :gid, :mac_source, :sport, :dport, :port, :src_type,
:dst_type, :socket, :pkttype, :ipsec_dir, :ipsec_policy, :state,
:ctstate, :ctproto, :ctorigsrc, :ctorigdst, :ctreplsrc, :ctrepldst,
:ctorigsrcport, :ctorigdstport, :ctreplsrcport, :ctrepldstport, :ctstatus, :ctexpire, :ctdir,
Expand Down
46 changes: 21 additions & 25 deletions lib/puppet/provider/firewall/iptables.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
has_feature :nflog_prefix
has_feature :nflog_range
has_feature :nflog_threshold
has_feature :tcp_option
has_feature :tcp_flags
has_feature :pkttype
has_feature :isfragment
Expand Down Expand Up @@ -173,7 +174,8 @@
string_from: '--from',
string_to: '--to',
table: '-t',
tcp_flags: ['-m tcp --tcp-flags', '--tcp-flags'],
tcp_option: '--tcp-option',
tcp_flags: '--tcp-flags',
todest: '--to-destination',
toports: '--to-ports',
tosource: '--to-source',
Expand Down Expand Up @@ -296,7 +298,13 @@ def self.munge_resource_map_from_existing_values(resource_map_original, compare)

def munge_resource_map_from_resource(resource_map_original, compare)
resource_map_new = resource_map_original.clone
module_to_argument_mapping = self.class.instance_variable_get('@module_to_argument_mapping')
# We ignore the 'tcp' match module on rule parse, but it needs to be included to generate
# arguments, since both '--tcp-option' and '--tcp-flags' should be prefixed with the match
# module spec. '--dport' and '--sport' are ignored because we only produce multiport-style
# portspecs, which do not require '-m tcp', and for which iptables-save does not include
# '-m tcp' in its output.
tcp_module_arguments = { tcp: [:tcp_option, :tcp_flags] }
module_to_argument_mapping = self.class.instance_variable_get('@module_to_argument_mapping').merge(tcp_module_arguments)

module_to_argument_mapping.each do |ipt_module, arg_array|
arg_array.each do |argument|
Expand Down Expand Up @@ -348,7 +356,7 @@ def munge_resource_map_from_resource(resource_map_original, compare)
: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_range, :dst_range, :tcp_option, :tcp_flags, :uid, :gid, :mac_source, :sport, :dport, :port,
:src_type, :dst_type, :socket, :pkttype, :ipsec_dir, :ipsec_policy,
:state, :ctstate, :ctproto, :ctorigsrc, :ctorigdst, :ctreplsrc, :ctrepldst,
:ctorigsrcport, :ctorigdstport, :ctreplsrcport, :ctrepldstport, :ctstatus, :ctexpire, :ctdir,
Expand Down Expand Up @@ -410,29 +418,9 @@ def delete
end

def exists?
if duplicate_rule?(@property_hash[:name])
core_message = "Duplicate rule found for #{@property_hash[:name]}."
case @resource.parameters[:onduplicaterulebehaviour].value
when :error
raise "#{core_message} Skipping update."
when :warn
warning "#{core_message}. This may add an additional rule to the system."
when :ignore
debug "#{core_message}. This may add an additional rule to the system."
end
end

properties[:ensure] != :absent
end

def duplicate_rule?(rule)
rules = self.class.instances
system_rule_count = Hash.new(0)
rules.each { |r| system_rule_count[r.name] += 1 }
duplicate_rules = rules.select { |r| system_rule_count[r.name] > 1 }
duplicate_rules.select { |r| r.name == rule }.any?
end

# Flush the property hash once done.
def flush
debug('[flush]')
Expand All @@ -448,6 +436,8 @@ def self.instances
debug '[instances]'
table = nil
rules = []
# Used for detecting duplicate rules
duplicate_rules = []
counter = 1

# String#lines would be nice, but we need to support Ruby 1.8.5
Expand All @@ -459,6 +449,8 @@ def self.instances
else
hash = rule_to_hash(line, table, counter)
if hash
raise "Duplicate rule found for #{hash[:name]}. Skipping update." if duplicate_rules.include?(hash[:name])
duplicate_rules << hash[:name]
rules << new(hash)
counter += 1
end
Expand Down Expand Up @@ -523,8 +515,12 @@ def self.rule_to_hash(line, table, counter)
values = values.gsub(%r{(?<=\s)(-\S+) (!)\s?(\S*)}, '\1 "\2 \3"')
# fix negated physdev rules
values = values.gsub(%r{-m physdev ! (--physdev-is-\S+)}, '-m physdev \1 "!"')
# The match extension for tcp & udp are optional and throws off the @resource_map.
values = values.gsub(%r{(?!-m tcp --tcp-flags)-m (tcp|udp) }, '')
# The match extensions for tcp & udp are implied by the protocol, cannot be
# given unless the protocol matches the extension name, are never required,
# and if multiport matches are used, may not even be in the iptables-save
# output in predictable locations. They need to be removed to preserve
# parse sanity.
values = values.gsub(%r{-m (tcp|udp) }, '')
# There is a bug in EL5 which puts 2 spaces before physdev, so we fix it
values = values.gsub(%r{\s{2}--physdev}, ' --physdev')
# '--pol ipsec' takes many optional arguments; we cheat again by adding " around them
Expand Down
6 changes: 4 additions & 2 deletions lib/puppet/provider/firewallchain/iptables_chain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,14 @@ def create
end

def destroy
allvalidchains do |t, chain, table|
allvalidchains do |t, chain, table, protocol|
if INTERNAL_CHAINS.match?(chain)
# can't delete internal chains
warning "Attempting to destroy internal chain #{@resource[:name]}"
else
debug "Deleting chain #{chain} on table #{table}"
debug "Flush chain #{chain} on table #{table} (#{protocol})"
t.call ['-t', table, '-F', chain]
debug "Deleting chain #{chain} on table #{table} (#{protocol})"
t.call ['-t', table, '-X', chain]
end
end
Expand Down
41 changes: 22 additions & 19 deletions lib/puppet/type/firewall.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@
* string_matching: The ability to match a given string by using some pattern matching strategy.
* tcp_option: The ability to match on particular TCP options.
* tcp_flags: The ability to match on particular TCP flag settings.
* netmap: The ability to map entire subnets via source or destination nat rules.
Expand Down Expand Up @@ -171,6 +173,7 @@
feature :log_ip_options, 'Add IP/IPv6 packet header to log messages'
feature :mark, 'Match or Set the netfilter mark value associated with the packet'
feature :mss, 'Match a given TCP MSS value or range.'
feature :tcp_option, 'The ability to match on particular TCP options'
feature :tcp_flags, 'The ability to match on particular TCP flag settings'
feature :pkttype, 'Match a packet type'
feature :rpfilter, 'Perform reverse-path filtering'
Expand Down Expand Up @@ -236,24 +239,6 @@
newvalues(%r{^\d+[[:graph:][:space:]]+$})
end

newparam(:onduplicaterulebehaviour) do
desc <<-PUPPETCODE
In certain situations it is possible for an unmanaged rule to exist
on the target system that has the same comment as the rule
specified in the manifest.
This setting determines what happens when such a duplicate is found.
It offers three options:
* ignore - The duplicate rule is ignored and any updates to the resource will continue unaffected.
* warn - The duplicate rule is logged as a warning and any updates to the resource will continue unaffected.
* error - The duplicate rule is logged as an error and any updates to the resource will be skipped.
PUPPETCODE
newvalues(:ignore, :warn, :error)
defaultto :warn
end

newproperty(:action) do
desc <<-PUPPETCODE
This is the action to perform on a match. Can be one of:
Expand Down Expand Up @@ -587,6 +572,24 @@ def should_to_s(value)
PUPPETCODE
end

# tcp-specific
newproperty(:tcp_option, required_features: :tcp_option) do
desc <<-PUPPETCODE
Match when the TCP option is present or absent.
Given as a single TCP option, optionally prefixed with '! ' to match
on absence instead. Only one TCP option can be matched in a given rule.
TCP option numbers are an eight-bit field, so valid option numbers range
from 0-255.
PUPPETCODE

validate do |value|
unless value.to_i.bit_length < 8 && value.to_i >= 0
raise ArgumentError, "TCP Options fall in the range 0-255, #{value} is not a valid TCP Option number"
end
end
munge { |value| value.to_s }
end

# tcp-specific
newproperty(:tcp_flags, required_features: :tcp_flags) do
desc <<-PUPPETCODE
Expand Down Expand Up @@ -2359,7 +2362,7 @@ def should_to_s(value)
['/etc/sysconfig/iptables', '/etc/sysconfig/ip6tables']
end

validate do
validate do # rubocop:disable Metrics/BlockLength
debug('[validate]')

# TODO: this is put here to skip validation if ensure is not set. This
Expand Down
2 changes: 1 addition & 1 deletion manifests/linux/debian.pp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
Enum[present, latest, 'present', 'latest'] $package_ensure = $firewall::params::package_ensure,
) inherits firewall::params {
if $package_name {
ensure_packages([$package_name], {
stdlib::ensure_packages([$package_name], {
ensure => $package_ensure
})
}
Expand Down
12 changes: 11 additions & 1 deletion manifests/linux/redhat.pp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
}

if $package_name {
ensure_packages($package_name, {
stdlib::ensure_packages($package_name, {
'ensure' => $package_ensure,
'before' => Service[$service_name] }
)
Expand Down Expand Up @@ -151,6 +151,16 @@
$seltype = 'system_conf_t'
}

/^8\..*/: {
$seluser = 'system_u'
$seltype = 'etc_t'
}

/^9\..*/: {
$seluser = 'system_u'
$seltype = 'etc_t'
}

default : {
$seluser = 'unconfined_u'
$seltype = 'etc_t'
Expand Down
2 changes: 1 addition & 1 deletion manifests/params.pp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
$package_name = ['iptables-services', 'nftables', 'iptables-nft-services']
$iptables_name = 'iptables-nft'
$sysconfig_manage = false
$firewalld_manage = false
$firewalld_manage = true
} elsif versioncmp($facts['os']['release']['full'], '8.0') >= 0 {
$service_name = ['iptables', 'nftables']
$service_name_v6 = 'ip6tables'
Expand Down
4 changes: 2 additions & 2 deletions metadata.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "puppetlabs-firewall",
"version": "5.0.0",
"version": "6.0.0",
"author": "puppetlabs",
"summary": "Manages Firewalls such as iptables",
"license": "Apache-2.0",
Expand All @@ -10,7 +10,7 @@
"dependencies": [
{
"name": "puppetlabs/stdlib",
"version_requirement": ">= 4.0.0 < 9.0.0"
"version_requirement": ">= 9.0.0 < 10.0.0"
}
],
"operatingsystem_support": [
Expand Down
65 changes: 15 additions & 50 deletions spec/acceptance/firewall_duplicate_comment_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,19 @@

require 'spec_helper_acceptance'

def make_manifest(behaviour)
pp = <<-PUPPETCODE
describe 'firewall - duplicate comments' do
before(:all) do
if os[:family] == 'ubuntu' || os[:family] == 'debian'
update_profile_file
end
end

after(:each) do
iptables_flush_all_tables
end

context 'when a duplicate comment is found' do
pp = <<-PUPPETCODE
class { 'firewall': }
resources { 'firewall':
purge => true,
Expand All @@ -14,61 +25,15 @@ class { 'firewall': }
dport => '550',
action => accept,
destination => '192.168.2.0/24',
onduplicaterulebehaviour => #{behaviour}
}
PUPPETCODE

pp
end

describe 'firewall - duplicate comments' do
before(:all) do
if os[:family] == 'ubuntu' || os[:family] == 'debian'
update_profile_file
end
end

before(:each) do
run_shell('iptables -I INPUT -m state --state NEW -m tcp -p tcp --dport 551 -j ACCEPT -m comment --comment "550 destination"')
end

after(:each) do
iptables_flush_all_tables
end

context 'when onduplicateerrorhevent is set to error' do
it 'raises an error' do
run_shell('iptables -I INPUT -m state --state NEW -m tcp -p tcp --dport 551 -j ACCEPT -m comment --comment "550 destination"')
pp = make_manifest('error')

apply_manifest(pp) do |r|
expect(r.stderr).to include('Error: /Stage[main]/Main/Firewall[550 destination]: Could not evaluate: Duplicate rule found for 550 destination. Skipping update.')
end
end
end

context 'when onduplicateerrorhevent is set to warn' do
run_shell('iptables -I INPUT -m state --state NEW -m tcp -p tcp --dport 551 -j ACCEPT -m comment --comment "550 destination"')

it 'warns and continues' do
run_shell('iptables -I INPUT -m state --state NEW -m tcp -p tcp --dport 551 -j ACCEPT -m comment --comment "550 destination"')
pp = make_manifest('warn')

apply_manifest(pp) do |r|
expect(r.stderr).to include('Warning: Firewall[550 destination](provider=iptables): Duplicate rule found for 550 destination.. This may add an additional rule to the system.')
end
end
end

context 'when onduplicateerrorhevent is set to ignore' do
run_shell('iptables -I INPUT -m state --state NEW -m tcp -p tcp --dport 551 -j ACCEPT -m comment --comment "550 destination"')

it 'continues silently' do
run_shell('iptables -I INPUT -m state --state NEW -m tcp -p tcp --dport 551 -j ACCEPT -m comment --comment "550 destination"')
pp = make_manifest('ignore')
run_shell('iptables -I INPUT -m state --state NEW -m tcp -p tcp --dport 552 -j ACCEPT -m comment --comment "550 destination"')

apply_manifest(pp) do |r|
expect(r.stderr).to be_empty
expect(r.stderr).to include('Duplicate rule found for 550 destination. Skipping update.')
end
end
end
Expand Down
7 changes: 4 additions & 3 deletions spec/default_facts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
#
# Facts specified here will override the values provided by rspec-puppet-facts.
---
ipaddress: "172.16.254.254"
ipaddress6: "FE80:0000:0000:0000:AAAA:AAAA:AAAA"
networking:
ip: "172.16.254.254"
ip6: "FE80:0000:0000:0000:AAAA:AAAA:AAAA"
mac: "AA:AA:AA:AA:AA:AA"
is_pe: false
macaddress: "AA:AA:AA:AA:AA:AA"
140 changes: 139 additions & 1 deletion spec/fixtures/ip6tables/conversion_hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,94 @@
params: {
random_fully: 'true',
}
}
},
'tcp_flags_1' => {
line: '-A INPUT -p tcp -m tcp --tcp-flags SYN,RST,ACK,FIN SYN -m comment --comment "000 initiation"',
compare_all: true,
table: 'filter',
chain: 'INPUT',
proto: 'tcp',
params: {
name: '000 initiation',
tcp_flags: 'SYN,RST,ACK,FIN SYN',
proto: 'tcp',
chain: 'INPUT',
line: '-A INPUT -p tcp -m tcp --tcp-flags SYN,RST,ACK,FIN SYN -m comment --comment "000 initiation"',
provider: 'ip6tables',
table: 'filter',
ensure: :present,
},
},
'tcp_option_1' => {
line: '-A INPUT -p tcp -m tcp --tcp-option 8 -m comment --comment "001 tcp_option works alone"',
compare_all: true,
table: 'filter',
chain: 'INPUT',
proto: 'tcp',
params: {
chain: 'INPUT',
ensure: :present,
line: '-A INPUT -p tcp -m tcp --tcp-option 8 -m comment --comment "001 tcp_option works alone"',
name: '001 tcp_option works alone',
proto: 'tcp',
provider: 'ip6tables',
table: 'filter',
tcp_option: '8',
},
},
'tcp_option_2' => {
line: '-A INPUT -p tcp -m tcp ! --tcp-option 8 -m comment --comment "002 tcp_option works alone, negated"',
compare_all: true,
table: 'filter',
chain: 'INPUT',
proto: 'tcp',
params: {
chain: 'INPUT',
ensure: :present,
line: '-A INPUT -p tcp -m tcp ! --tcp-option 8 -m comment --comment "002 tcp_option works alone, negated"',
name: '002 tcp_option works alone, negated',
proto: 'tcp',
provider: 'ip6tables',
table: 'filter',
tcp_option: '! 8',
},
},
'tcp_option_with_tcp_flags_1' => {
line: '-A INPUT -p tcp -m tcp --tcp-option 8 --tcp-flags FIN,SYN,RST,ACK SYN -m comment --comment "000 initiation"',
table: 'filter',
compare_all: true,
chain: 'INPUT',
proto: 'tcp',
params: {
chain: 'INPUT',
ensure: :present,
line: '-A INPUT -p tcp -m tcp --tcp-option 8 --tcp-flags FIN,SYN,RST,ACK SYN -m comment --comment "000 initiation"',
name: '000 initiation',
proto: 'tcp',
provider: 'ip6tables',
table: 'filter',
tcp_flags: 'FIN,SYN,RST,ACK SYN',
tcp_option: '8',
},
},
'tcp_option_with_tcp_flags_2' => {
line: '-A INPUT -p tcp -m tcp ! --tcp-option 8 --tcp-flags FIN,SYN,RST,ACK SYN -m comment --comment "000 initiation"',
table: 'filter',
compare_all: true,
chain: 'INPUT',
proto: 'tcp',
params: {
chain: 'INPUT',
ensure: :present,
line: '-A INPUT -p tcp -m tcp ! --tcp-option 8 --tcp-flags FIN,SYN,RST,ACK SYN -m comment --comment "000 initiation"',
name: '000 initiation',
proto: 'tcp',
provider: 'ip6tables',
table: 'filter',
tcp_flags: 'FIN,SYN,RST,ACK SYN',
tcp_option: '! 8',
},
},
}.freeze

# This hash is for testing converting a hash to an argument line.
Expand Down Expand Up @@ -141,4 +228,55 @@
},
args: ['-t', :filter, '-p', :tcp, '-j', 'NFLOG', '--nflog-group', 1, '--nflog-prefix', 'myprefix', '-m', 'comment', '--comment', '100 nflog'],
},
'tcp_flags_1' => {
params: {
name: '000 initiation',
tcp_flags: 'SYN,RST,ACK,FIN SYN',
table: 'filter',
},

args: ['-t', :filter, '-p', :tcp, '-m', 'tcp', '--tcp-flags', 'SYN,RST,ACK,FIN', 'SYN', '-m', 'comment', '--comment', '000 initiation'],
},
'tcp_option_1' => {
params: {
name: '000 initiation',
table: 'filter',
chain: 'INPUT',
proto: 'tcp',
tcp_option: '8',
},
args: ['-t', :filter, '-p', :tcp, '-m', 'tcp', '--tcp-option', '8', '-m', 'comment', '--comment', '000 initiation'],
},
'tcp_option_2' => {
params: {
name: '000 initiation',
table: 'filter',
chain: 'INPUT',
proto: 'tcp',
tcp_option: '! 8',
},
args: ['-t', :filter, '-p', :tcp, '-m', 'tcp', '!', '--tcp-option', '8', '-m', 'comment', '--comment', '000 initiation'],
},
'tcp_option_with_tcp_flags_1' => {
params: {
name: '000 initiation',
table: 'filter',
chain: 'INPUT',
proto: 'tcp',
tcp_flags: 'FIN,SYN,RST,ACK SYN',
tcp_option: '8',
},
args: ['-t', :filter, '-p', :tcp, '-m', 'tcp', '--tcp-option', '8', '--tcp-flags', 'FIN,SYN,RST,ACK', 'SYN', '-m', 'comment', '--comment', '000 initiation'],
},
'tcp_option_with_tcp_flags_2' => {
params: {
name: '000 initiation',
table: 'filter',
chain: 'INPUT',
proto: 'tcp',
tcp_flags: 'FIN,SYN,RST,ACK SYN',
tcp_option: '! 8',
},
args: ['-t', :filter, '-p', :tcp, '-m', 'tcp', '!', '--tcp-option', '8', '--tcp-flags', 'FIN,SYN,RST,ACK', 'SYN', '-m', 'comment', '--comment', '000 initiation'],
},
}.freeze
110 changes: 110 additions & 0 deletions spec/fixtures/iptables/conversion_hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,74 @@
tcp_flags: 'SYN,RST,ACK,FIN SYN',
},
},
'tcp_option_1' => {
line: '-A INPUT -p tcp -m tcp --tcp-option 8 -m comment --comment "001 tcp_option works alone"',
table: 'filter',
chain: 'INPUT',
proto: 'tcp',
params: {
chain: 'INPUT',
ensure: :present,
line: '-A INPUT -p tcp -m tcp --tcp-option 8 -m comment --comment "001 tcp_option works alone"',
name: '001 tcp_option works alone',
proto: 'tcp',
provider: 'iptables',
table: 'filter',
tcp_option: '8',
},
},
'tcp_option_2' => {
line: '-A INPUT -p tcp -m tcp ! --tcp-option 8 -m comment --comment "002 tcp_option works alone, negated"',
table: 'filter',
chain: 'INPUT',
proto: 'tcp',
params: {
chain: 'INPUT',
ensure: :present,
line: '-A INPUT -p tcp -m tcp ! --tcp-option 8 -m comment --comment "002 tcp_option works alone, negated"',
name: '002 tcp_option works alone, negated',
proto: 'tcp',
provider: 'iptables',
table: 'filter',
tcp_option: '! 8',
},
},
'tcp_option_with_tcp_flags_1' => {
line: '-A INPUT -p tcp -m tcp --tcp-option 8 --tcp-flags FIN,SYN,RST,ACK SYN -m comment --comment "000 initiation"',
table: 'filter',
compare_all: true,
chain: 'INPUT',
proto: 'tcp',
params: {
chain: 'INPUT',
ensure: :present,
line: '-A INPUT -p tcp -m tcp --tcp-option 8 --tcp-flags FIN,SYN,RST,ACK SYN -m comment --comment "000 initiation"',
name: '000 initiation',
proto: 'tcp',
provider: 'iptables',
table: 'filter',
tcp_flags: 'FIN,SYN,RST,ACK SYN',
tcp_option: '8',
},
},
'tcp_option_with_tcp_flags_2' => {
line: '-A INPUT -p tcp -m tcp ! --tcp-option 8 --tcp-flags FIN,SYN,RST,ACK SYN -m comment --comment "000 initiation"',
table: 'filter',
compare_all: true,
chain: 'INPUT',
proto: 'tcp',
params: {
chain: 'INPUT',
ensure: :present,
line: '-A INPUT -p tcp -m tcp ! --tcp-option 8 --tcp-flags FIN,SYN,RST,ACK SYN -m comment --comment "000 initiation"',
name: '000 initiation',
proto: 'tcp',
provider: 'iptables',
table: 'filter',
tcp_flags: 'FIN,SYN,RST,ACK SYN',
tcp_option: '! 8',
},
},
'state_returns_sorted_values' => {
line: '-A INPUT -m state --state INVALID,RELATED,ESTABLISHED',
table: 'filter',
Expand Down Expand Up @@ -1003,6 +1071,48 @@

args: ['-t', :filter, '-p', :tcp, '-m', 'tcp', '--tcp-flags', 'SYN,RST,ACK,FIN', 'SYN', '-m', 'comment', '--comment', '000 initiation'],
},
'tcp_option_1' => {
params: {
name: '000 initiation',
table: 'filter',
chain: 'INPUT',
proto: 'tcp',
tcp_option: '8',
},
args: ['-t', :filter, '-p', :tcp, '-m', 'tcp', '--tcp-option', '8', '-m', 'comment', '--comment', '000 initiation'],
},
'tcp_option_2' => {
params: {
name: '000 initiation',
table: 'filter',
chain: 'INPUT',
proto: 'tcp',
tcp_option: '! 8',
},
args: ['-t', :filter, '-p', :tcp, '-m', 'tcp', '!', '--tcp-option', '8', '-m', 'comment', '--comment', '000 initiation'],
},
'tcp_option_with_tcp_flags_1' => {
params: {
name: '000 initiation',
table: 'filter',
chain: 'INPUT',
proto: 'tcp',
tcp_flags: 'FIN,SYN,RST,ACK SYN',
tcp_option: '8',
},
args: ['-t', :filter, '-p', :tcp, '-m', 'tcp', '--tcp-option', '8', '--tcp-flags', 'FIN,SYN,RST,ACK', 'SYN', '-m', 'comment', '--comment', '000 initiation'],
},
'tcp_option_with_tcp_flags_2' => {
params: {
name: '000 initiation',
table: 'filter',
chain: 'INPUT',
proto: 'tcp',
tcp_flags: 'FIN,SYN,RST,ACK SYN',
tcp_option: '! 8',
},
args: ['-t', :filter, '-p', :tcp, '-m', 'tcp', '!', '--tcp-option', '8', '--tcp-flags', 'FIN,SYN,RST,ACK', 'SYN', '-m', 'comment', '--comment', '000 initiation'],
},
'states_set_from_array' => {
params: {
name: '100 states_set_from_array',
Expand Down
103 changes: 101 additions & 2 deletions spec/unit/puppet/provider/ip6tables_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
require 'puppet/confine/exists'

provider_class = Puppet::Type.type(:firewall).provider(:ip6tables)
describe 'ip6tables' do
describe 'ip6tables' do # rubocop:disable RSpec/MultipleDescribes
let(:params) { { name: '000 test foo', action: 'accept' } }
let(:provider) { provider_class }
let(:resource) { Puppet::Type.type(:firewall) }
Expand All @@ -21,7 +21,7 @@ def stub_iptables

allow(Facter.fact(:kernel)).to receive(:value).and_return('Linux')
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('Debian')
allow(Facter.fact('ip6tables_version')).to receive(:value).and_return(ip6tables_version)
stub_const('Puppet::Type::Firewall::ProviderIp6tables::Ip6tables_version', ip6tables_version)
allow(Puppet::Util::Execution).to receive(:execute).and_return ''
allow(Puppet::Util).to receive(:which).with('iptables-save')
.and_return '/sbin/iptables-save'
Expand Down Expand Up @@ -56,3 +56,102 @@ def stub_iptables
it_behaves_like 'run'
end
end

describe 'ip6tables provider' do
let(:provider6) { Puppet::Type.type(:firewall).provider(:ip6tables) }
let(:resource) do
Puppet::Type.type(:firewall).new(name: '000 test foo',
action: 'accept',
provider: 'ip6tables')
end

before :each do
allow(Puppet::Type::Firewall).to receive(:ip6tables).and_return provider6
allow(provider6).to receive(:command).with(:ip6tables_save).and_return '/sbin/ip6tables-save'

# Stub iptables version
allow(Facter.fact(:ip6tables_version)).to receive(:value).and_return '1.4.7'

allow(Puppet::Util::Execution).to receive(:execute).and_return ''
allow(Puppet::Util).to receive(:which).with('ip6tables-save')
.and_return '/sbin/ip6tables-save'
end

it 'is expected to be able to get a list of existing rules' do
provider6.instances.each do |rule|
expect(rule).to be_instance_of(provider6)
expect(rule.properties[:provider6].to_s).to eql provider6.name.to_s
end
end

it 'is expected to ignore lines with fatal errors' do
allow(Puppet::Util::Execution).to receive(:execute).with(['/sbin/ip6tables-save'])
.and_return('FATAL: Could not load /lib/modules/2.6.18-028stab095.1/modules.dep: No such file or directory')
expect(provider6.instances.length).to eq 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]
it 'the parameter hash keys should be the same as returned by rules_to_hash' do
expect(resource.keys).to match_array(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}' should match #{param_value.inspect}" do
if param_value == true
expect(resource[param_name]).to be_truthy
else
expect(resource[param_name]).to eq(data[:params][param_name])
end
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
data[:args].unshift('--wait') if instance.general_args.flatten.include? '--wait'
expect(instance.general_args.flatten).to eql data[:args]
end
end
end
end

describe 'when deleting ipv6 resources' do
let(:sample_rule) do
'-A INPUT -i lo -m comment --comment "001 accept all to lo interface v6" -j ACCEPT'
end

let(:bare_sample_rule) do
'-A INPUT -i lo -m comment --comment 001 accept all to lo interface v6 -j ACCEPT'
end

let(:resource) { provider6.rule_to_hash(sample_rule, 'filter', 0) }
let(:instance) { provider6.new(resource) }

it 'resource[:line] looks like the original rule' do
resource[:line] == sample_rule
end

it 'delete_args is an array' do
expect(instance.delete_args.class).to eq(Array)
end
end
end
95 changes: 0 additions & 95 deletions spec/unit/puppet/provider/iptables_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -353,98 +353,3 @@
end
end
end

describe 'ip6tables provider' do
let(:provider6) { Puppet::Type.type(:firewall).provider(:ip6tables) }
let(:resource) do
Puppet::Type.type(:firewall).new(name: '000 test foo',
action: 'accept',
provider: 'ip6tables')
end

before :each do
allow(Puppet::Type::Firewall).to receive(:ip6tables).and_return provider6
allow(provider6).to receive(:command).with(:ip6tables_save).and_return '/sbin/ip6tables-save'

# Stub iptables version
allow(Facter.fact(:ip6tables_version)).to receive(:value).and_return '1.4.7'

allow(Puppet::Util::Execution).to receive(:execute).and_return ''
allow(Puppet::Util).to receive(:which).with('ip6tables-save')
.and_return '/sbin/ip6tables-save'
end

it 'is expected to be able to get a list of existing rules' do
provider6.instances.each do |rule|
expect(rule).to be_instance_of(provider6)
expect(rule.properties[:provider6].to_s).to eql provider6.name.to_s
end
end

it 'is expected to ignore lines with fatal errors' do
allow(Puppet::Util::Execution).to receive(:execute).with(['/sbin/ip6tables-save'])
.and_return('FATAL: Could not load /lib/modules/2.6.18-028stab095.1/modules.dep: No such file or directory')
expect(provider6.instances.length).to eq 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]
it 'the parameter hash keys should be the same as returned by rules_to_hash' do
expect(resource.keys).to match 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}' should match #{param_value.inspect}" do
expect(resource[param_name]).to eql 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
data[:args].unshift('--wait') if instance.general_args.flatten.include? '--wait'
expect(instance.general_args.flatten).to eql data[:args]
end
end
end
end

describe 'when deleting ipv6 resources' do
let(:sample_rule) do
'-A INPUT -i lo -m comment --comment "001 accept all to lo interface v6" -j ACCEPT'
end

let(:bare_sample_rule) do
'-A INPUT -i lo -m comment --comment 001 accept all to lo interface v6 -j ACCEPT'
end

let(:resource) { provider6.rule_to_hash(sample_rule, 'filter', 0) }
let(:instance) { provider6.new(resource) }

it 'resource[:line] looks like the original rule' do
resource[:line] == sample_rule
end

it 'delete_args is an array' do
expect(instance.delete_args.class).to eq(Array)
end
end
end