86 changes: 47 additions & 39 deletions README.markdown

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion lib/facter/iptables_persistent_version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
setcode do
# Throw away STDERR because dpkg >= 1.16.7 will make some noise if the
# package isn't currently installed.
cmd = "dpkg-query -Wf '${Version}' iptables-persistent 2>/dev/null"
os = Facter.value(:operatingsystem)
os_release = Facter.value(:operatingsystemrelease)
if (os == 'Debian' and (Puppet::Util::Package.versioncmp(os_release, '8.0') >= 0)) or
(os == 'Ubuntu' and (Puppet::Util::Package.versioncmp(os_release, '14.10') >= 0))
cmd = "dpkg-query -Wf '${Version}' netfilter-persistent 2>/dev/null"
else
cmd = "dpkg-query -Wf '${Version}' iptables-persistent 2>/dev/null"
end
version = Facter::Util::Resolution.exec(cmd)

if version.nil? or !version.match(/\d+\.\d+/)
Expand Down
58 changes: 49 additions & 9 deletions lib/puppet/provider/firewall/ip6tables.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
has_feature :ishasmorefrags
has_feature :islastfrag
has_feature :isfirstfrag
has_feature :socket
has_feature :address_type
has_feature :iprange
has_feature :ipsec_dir
has_feature :ipsec_policy
has_feature :mask
has_feature :ipset

optional_commands({
:ip6tables => 'ip6tables',
Expand All @@ -29,8 +36,17 @@

confine :kernel => :linux

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


def initialize(*args)
if Facter.fact('ip6tables_version').value.match /1\.3\.\d/
ip6tables_version = Facter.value('ip6tables_version')
if ip6tables_version and ip6tables_version.match /1\.3\.\d/
raise ArgumentError, 'The ip6tables provider is not supported on version 1.3 of iptables'
else
super
Expand All @@ -54,19 +70,26 @@ def self.iptables_save(*args)
:connmark => "-m connmark --mark",
:ctstate => "-m conntrack --ctstate",
:destination => "-d",
:dport => "-m multiport --dports",
:dport => ["-m multiport --dports", "--dport"],
:dst_range => '-m iprange --dst-range',
:dst_type => "-m addrtype --dst-type",
:gid => "-m owner --gid-owner",
:hop_limit => "-m hl --hl-eq",
:icmp => "-m icmp6 --icmpv6-type",
:iniface => "-i",
:ipsec_dir => "-m policy --dir",
:ipsec_policy => "--pol",
:ipset => "-m set --match-set",
:isfirstfrag => "-m frag --fragid 0 --fragfirst",
:ishasmorefrags => "-m frag --fragid 0 --fragmore",
:islastfrag => "-m frag --fragid 0 --fraglast",
:jump => "-j",
:limit => "-m limit --limit",
:log_level => "--log-level",
:log_prefix => "--log-prefix",
:mask => "--mask",
:name => "-m comment --comment",
:mac_source => ["-m mac --mac-source", "--mac-source"],
:outiface => "-o",
:pkttype => "-m pkttype --pkt-type",
:port => '-m multiport --ports',
Expand All @@ -80,8 +103,12 @@ def self.iptables_save(*args)
:rseconds => "--seconds",
:rsource => "--rsource",
:rttl => "--rttl",
:set_mark => mark_flag,
:socket => "-m socket",
:source => "-s",
:sport => "-m multiport --sports",
:sport => ["-m multiport --sports", "--sport"],
:src_range => '-m iprange --src-range',
:src_type => "-m addrtype --src-type",
:stat_every => '--every',
:stat_mode => "-m statistic --mode",
:stat_packet => '--packet',
Expand All @@ -93,11 +120,22 @@ def self.iptables_save(*args)
:toports => "--to-ports",
:tosource => "--to-source",
:uid => "-m owner --uid-owner",
:physdev_in => "-m physdev --physdev-in",
:physdev_out => "-m physdev --physdev-out",
}

# These are known booleans that do not take a value, but we want to munge
# to true if they exist.
@known_booleans = [:ishasmorefrags, :islastfrag, :isfirstfrag, :rsource, :rdest, :reap, :rttl]
@known_booleans = [
:ishasmorefrags,
:islastfrag,
:isfirstfrag,
:rsource,
:rdest,
:reap,
:rttl,
:socket
]

# Create property methods dynamically
(@resource_map.keys << :chain << :table << :action).each do |property|
Expand Down Expand Up @@ -133,11 +171,13 @@ def self.iptables_save(*args)
# (Note: on my CentOS 6.4 ip6tables-save returns -m frag on the place
# 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,
:proto, :ishasmorefrags, :islastfrag, :isfirstfrag, :tcp_flags, :gid, :uid, :sport, :dport,
:port, :pkttype, :name, :state, :ctstate, :icmp, :hop_limit, :limit, :burst,
:recent, :rseconds, :reap, :rhitcount, :rttl, :rname, :rsource, :rdest,
:jump, :todest, :tosource, :toports, :log_level, :log_prefix, :reject,
@resource_list = [:table, :source, :destination, :iniface, :outiface, :physdev_in,
:physdev_out, :proto, :ishasmorefrags, :islastfrag, :isfirstfrag, :src_range, :dst_range,
:tcp_flags, :gid, :uid, :mac_source, :sport, :dport, :port, :dst_type,
:src_type, :socket, :pkttype, :name, :ipsec_dir, :ipsec_policy, :state,
:ctstate, :icmp, :hop_limit, :limit, :burst, :recent, :rseconds, :reap,
:rhitcount, :rttl, :rname, :mask, :rsource, :rdest, :ipset, :jump, :todest,
:tosource, :toports, :log_level, :log_prefix, :reject, :set_mark,
:connlimit_above, :connlimit_mask, :connmark]

end
29 changes: 24 additions & 5 deletions lib/puppet/provider/firewall/iptables.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
has_feature :recent_limiting
has_feature :snat
has_feature :dnat
has_feature :netmap
has_feature :interface_match
has_feature :icmp_match
has_feature :owner
Expand Down Expand Up @@ -102,7 +103,10 @@
:todest => "--to-destination",
:toports => "--to-ports",
:tosource => "--to-source",
:to => "--to",
:uid => "-m owner --uid-owner",
:physdev_in => "-m physdev --physdev-in",
:physdev_out => "-m physdev --physdev-out",
}

# These are known booleans that do not take a value, but we want to munge
Expand Down Expand Up @@ -150,13 +154,13 @@
# 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, :proto, :isfragment,
:table, :source, :destination, :iniface, :outiface, :physdev_in, :physdev_out, :proto, :isfragment,
:stat_mode, :stat_every, :stat_packet, :stat_probability,
:src_range, :dst_range, :tcp_flags, :gid, :uid, :mac_source, :sport, :dport, :port,
:dst_type, :src_type, :socket, :pkttype, :name, :ipsec_dir, :ipsec_policy,
:state, :ctstate, :icmp, :limit, :burst, :recent, :rseconds, :reap,
:rhitcount, :rttl, :rname, :mask, :rsource, :rdest, :ipset, :jump, :todest,
:tosource, :toports, :random, :log_prefix, :log_level, :reject, :set_mark,
:tosource, :toports, :to, :random, :log_prefix, :log_level, :reject, :set_mark,
:connlimit_above, :connlimit_mask, :connmark
]

Expand Down Expand Up @@ -231,7 +235,7 @@ def self.rule_to_hash(line, table, counter)
# 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.
values = values.gsub(/-m (tcp|udp) (--(s|d)port|-m multiport)/, '\2')
values = values.gsub(/(?!-m tcp --tcp-flags)-m (tcp|udp) /, '')
# '--pol ipsec' takes many optional arguments; we cheat again by adding " around them
values = values.sub(/
--pol\sipsec
Expand Down Expand Up @@ -259,13 +263,21 @@ def self.rule_to_hash(line, table, counter)
end
end

# Handle resource_map values depending on whether physdev-in, physdev-out, or both are specified
if values.include? "--physdev-in" and values.include? "--physdev-out" then
#values = values.sub("--physdev-out","-m physdev --physdev-out")
@resource_map[:physdev_out] = "--physdev-out"
else
@resource_map[:physdev_out] = "-m physdev --physdev-out"
end

############
# Populate parser_list with used value, in the correct order
############
map_index={}
@resource_map.each_pair do |map_k,map_v|
[map_v].flatten.each do |v|
ind=values.index(/\s#{v}/)
ind=values.index(/\s#{v}\s/)
next unless ind
map_index[map_k]=ind
end
Expand Down Expand Up @@ -426,7 +438,7 @@ def update_args

def delete_args
# Split into arguments
line = properties[:line].gsub(/\-A/, '-D').split(/\s(?=(?:[^"]|"[^"]*")*$)/).map{|v| v.gsub(/"/, '')}
line = properties[:line].gsub(/\-A /, '-D ').split(/\s(?=(?:[^"]|"[^"]*")*$)/).map{|v| v.gsub(/"/, '')}
line.unshift("-t", properties[:table])
end

Expand All @@ -440,6 +452,13 @@ def general_args
resource_map = self.class.instance_variable_get('@resource_map')
known_booleans = self.class.instance_variable_get('@known_booleans')

# Handle physdev args depending on whether physdev-in, physdev-out, or both are specified
if (resource[:physdev_in])
resource_map[:physdev_out] = "--physdev-out"
else
resource_map[:physdev_out] = "-m physdev --physdev-out"
end

resource_list.each do |res|
resource_value = nil
if (resource[res]) then
Expand Down
101 changes: 96 additions & 5 deletions lib/puppet/type/firewall.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
feature :recent_limiting, "The netfilter recent module"
feature :snat, "Source NATing"
feature :dnat, "Destination NATing"
feature :netmap, "NET MAPping"
feature :interface_match, "Interface matching"
feature :icmp_match, "Matching ICMP types"
feature :owner, "Matching owners"
Expand Down Expand Up @@ -136,10 +137,25 @@
src_range => '192.168.1.1-192.168.1.10'
The source IP range is must in 'IP1-IP2' format.
The source IP range must be in 'IP1-IP2' format.
EOS

newvalues(/^((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)-((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)/)
validate do |value|
if matches = /^([^\-\/]+)-([^\-\/]+)$/.match(value)
start_addr = matches[1]
end_addr = matches[2]

[start_addr, end_addr].each do |addr|
begin
@resource.host_to_ip(addr)
rescue Exception
self.fail("Invalid IP address \"#{addr}\" in range \"#{value}\"")
end
end
else
raise(ArgumentError, "The source IP range must be in 'IP1-IP2' format.")
end
end
end

newproperty(:destination) do
Expand Down Expand Up @@ -171,10 +187,25 @@
dst_range => '192.168.1.1-192.168.1.10'
The destination IP range is must in 'IP1-IP2' format.
The destination IP range must be in 'IP1-IP2' format.
EOS

newvalues(/^((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)-((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)/)
validate do |value|
if matches = /^([^\-\/]+)-([^\-\/]+)$/.match(value)
start_addr = matches[1]
end_addr = matches[2]

[start_addr, end_addr].each do |addr|
begin
@resource.host_to_ip(addr)
rescue Exception
self.fail("Invalid IP address \"#{addr}\" in range \"#{value}\"")
end
end
else
raise(ArgumentError, "The destination IP range must be in 'IP1-IP2' format.")
end
end
end

newproperty(:sport, :array_matching => :all) do
Expand Down Expand Up @@ -469,6 +500,12 @@ def should_to_s(value)
EOS
end

newproperty(:to, :required_features => :netmap) do
desc <<-EOS
For NETMAP this will replace the destination IP
EOS
end

newproperty(:random, :required_features => :dnat) do
desc <<-EOS
When using a jump value of "MASQUERADE", "DNAT", "REDIRECT", or "SNAT"
Expand Down Expand Up @@ -693,6 +730,46 @@ def should_to_s(value)
only, as iptables does not accept multiple uid in a single
statement.
EOS
def insync?(is)
require 'etc'

# The following code allow us to take into consideration unix mappings
# between string usernames and UIDs (integers). We also need to ignore
# spaces as they are irrelevant with respect to rule sync.

# Remove whitespace
is = is.gsub(/\s+/,'')
should = @should.first.to_s.gsub(/\s+/,'')

# Keep track of negation, but remove the '!'
is_negate = ''
should_negate = ''
if is.start_with?('!')
is = is.gsub(/^!/,'')
is_negate = '!'
end
if should.start_with?('!')
should = should.gsub(/^!/,'')
should_negate = '!'
end

# If 'should' contains anything other than digits,
# we assume that we have to do a lookup to convert
# to UID
unless should[/[0-9]+/] == should
should = Etc.getpwnam(should).uid
end

# If 'is' contains anything other than digits,
# we assume that we have to do a lookup to convert
# to UID
unless is[/[0-9]+/] == is
is = Etc.getpwnam(is).uid
end

return "#{is_negate}#{is}" == "#{should_negate}#{should}"
end

end

newproperty(:gid, :required_features => :owner) do
Expand Down Expand Up @@ -996,6 +1073,20 @@ def should_to_s(value)
newvalues(/^([0-9a-f]{2}[:]){5}([0-9a-f]{2})$/i)
end

newproperty(:physdev_in, :required_features => :iptables) do
desc <<-EOS
Match if the packet is entering a bridge from the given interface.
EOS
newvalues(/^[a-zA-Z0-9\-\._\+]+$/)
end

newproperty(:physdev_out, :required_features => :iptables) do
desc <<-EOS
Match if the packet is leaving a bridge via the given interface.
EOS
newvalues(/^[a-zA-Z0-9\-\._\+]+$/)
end

autorequire(:firewallchain) do
reqs = []
protocol = nil
Expand All @@ -1022,7 +1113,7 @@ def should_to_s(value)
autorequire(:package) do
case value(:provider)
when :iptables, :ip6tables
%w{iptables iptables-persistent iptables-services}
%w{iptables iptables-persistent netfilter-persistent iptables-services}
else
[]
end
Expand Down
4 changes: 2 additions & 2 deletions lib/puppet/util/firewall.rb
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def persist_iptables(proto)
end

# RHEL 7 and newer also use systemd to persist iptable rules
if os_key == 'RedHat' && ['RedHat','CentOS','Scientific','SL','SLC','Ascendos','CloudLinux','PSBM','OracleLinux','OVS','OEL','Amazon','XenServer'].include?(Facter.value(:operatingsystem)) && Facter.value(:operatingsystemrelease).to_i >= 7
if os_key == 'RedHat' && ['RedHat','CentOS','Scientific','SL','SLC','Ascendos','CloudLinux','PSBM','OracleLinux','OVS','OEL','XenServer'].include?(Facter.value(:operatingsystem)) && Facter.value(:operatingsystemrelease).to_i >= 7
os_key = 'Fedora'
end

Expand All @@ -191,7 +191,7 @@ def persist_iptables(proto)
when :Debian
case proto.to_sym
when :IPv4, :IPv6
if Puppet::Util::Package.versioncmp(persist_ver, '1.0') > 0
if (persist_ver and Puppet::Util::Package.versioncmp(persist_ver, '1.0') > 0)
%w{/usr/sbin/service netfilter-persistent save}
else
%w{/usr/sbin/service iptables-persistent save}
Expand Down
5 changes: 3 additions & 2 deletions manifests/linux/redhat.pp
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
# RHEL 7 and later and Fedora 15 and later require the iptables-services
# package, which provides the /usr/libexec/iptables/iptables.init used by
# lib/puppet/util/firewall.rb.
if ($::operatingsystem != 'Fedora' and versioncmp($::operatingsystemrelease, '7.0') >= 0)
or ($::operatingsystem == 'Fedora' and versioncmp($::operatingsystemrelease, '15') >= 0) {
if ($::operatingsystem != 'Amazon')
and (($::operatingsystem != 'Fedora' and versioncmp($::operatingsystemrelease, '7.0') >= 0)
or ($::operatingsystem == 'Fedora' and versioncmp($::operatingsystemrelease, '15') >= 0)) {
service { 'firewalld':
ensure => stopped,
enable => false,
Expand Down
37 changes: 30 additions & 7 deletions manifests/params.pp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
case $::osfamily {
'RedHat': {
case $::operatingsystem {
'Amazon': {
$service_name = 'iptables'
$package_name = undef
}
'Archlinux': {
$service_name = ['iptables','ip6tables']
$package_name = undef
Expand All @@ -18,19 +22,38 @@
if versioncmp($::operatingsystemrelease, '7.0') >= 0 {
$package_name = 'iptables-services'
} else {
$package_name = undef
$package_name = 'iptables-ipv6'
}
$service_name = 'iptables'
}
}
}
'Debian': {
if $::operatingsystem == 'Debian' and versioncmp($::operatingsystemrelease, '8.0') >= 0 {
$service_name = 'netfilter-persistent'
$package_name = 'netfilter-persistent'
} else {
$service_name = 'iptables-persistent'
$package_name = 'iptables-persistent'
case $::operatingsystem {
'Debian': {
if versioncmp($::operatingsystemrelease, '8.0') >= 0 {
$service_name = 'netfilter-persistent'
$package_name = 'netfilter-persistent'
} else {
$service_name = 'iptables-persistent'
$package_name = 'iptables-persistent'
}

}
'Ubuntu': {
if versioncmp($::operatingsystemrelease, '14.10') >= 0 {
$service_name = 'netfilter-persistent'
$package_name = 'netfilter-persistent'
} else {
$service_name = 'iptables-persistent'
$package_name = 'iptables-persistent'
}

}
default: {
$service_name = 'iptables-persistent'
$package_name = 'iptables-persistent'
}
}
}
default: {
Expand Down
2 changes: 1 addition & 1 deletion metadata.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "puppetlabs-firewall",
"version": "1.3.0",
"version": "1.4.0",
"author": "Puppet Labs",
"summary": "Manages Firewalls such as iptable",
"license": "Apache-2.0",
Expand Down
182 changes: 182 additions & 0 deletions spec/acceptance/firewall_bridging_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
require 'spec_helper_acceptance'

describe 'firewall type', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) do

describe 'reset' do
it 'deletes all iptables rules' do
shell('iptables --flush; iptables -t nat --flush; iptables -t mangle --flush')
end
it 'deletes all ip6tables rules' do
shell('ip6tables --flush; ip6tables -t nat --flush; ip6tables -t mangle --flush')
end
end

describe 'iptables physdev tests' do
context 'physdev_in eth0' do
it 'applies' do
pp = <<-EOS
class { '::firewall': }
firewall { '701 - test':
chain => 'FORWARD',
proto => tcp,
port => '701',
action => accept,
physdev_in => 'eth0',
}
EOS

apply_manifest(pp, :catch_failures => true)
unless fact('selinux') == 'true'
apply_manifest(pp, :catch_changes => true)
end
end

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

context 'physdev_out eth1' do
it 'applies' do
pp = <<-EOS
class { '::firewall': }
firewall { '702 - test':
chain => 'FORWARD',
proto => tcp,
port => '702',
action => accept,
physdev_out => 'eth1',
}
EOS

apply_manifest(pp, :catch_failures => true)
unless fact('selinux') == 'true'
apply_manifest(pp, :catch_changes => true)
end
end

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

context 'physdev_in eth0 and physdev_out eth1' do
it 'applies' do
pp = <<-EOS
class { '::firewall': }
firewall { '703 - test':
chain => 'FORWARD',
proto => tcp,
port => '703',
action => accept,
physdev_in => 'eth0',
physdev_out => 'eth1',
}
EOS

apply_manifest(pp, :catch_failures => true)
unless fact('selinux') == 'true'
apply_manifest(pp, :catch_changes => true)
end
end

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

#iptables version 1.3.5 is not suppored by the ip6tables provider
if default['platform'] !~ /el-5/
describe 'ip6tables physdev tests' do
context 'physdev_in eth0' do
it 'applies' do
pp = <<-EOS
class { '::firewall': }
firewall { '701 - test':
provider => 'ip6tables',
chain => 'FORWARD',
proto => tcp,
port => '701',
action => accept,
physdev_in => 'eth0',
}
EOS

apply_manifest(pp, :catch_failures => true)
unless fact('selinux') == 'true'
apply_manifest(pp, :catch_changes => true)
end
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-in eth0 -m multiport --ports 701 -m comment --comment "701 - test" -j ACCEPT/)
end
end
end

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

apply_manifest(pp, :catch_failures => true)
unless fact('selinux') == 'true'
apply_manifest(pp, :catch_changes => true)
end
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-out eth1 -m multiport --ports 702 -m comment --comment "702 - test" -j ACCEPT/)
end
end
end

context 'physdev_in eth0 and physdev_out eth1' do
it 'applies' do
pp = <<-EOS
class { '::firewall': }
firewall { '703 - test':
provider => 'ip6tables',
chain => 'FORWARD',
proto => tcp,
port => '703',
action => accept,
physdev_in => 'eth0',
physdev_out => 'eth1',
}
EOS

apply_manifest(pp, :catch_failures => true)
unless fact('selinux') == 'true'
apply_manifest(pp, :catch_changes => true)
end
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-in eth0 --physdev-out eth1 -m multiport --ports 703 -m comment --comment "703 - test" -j ACCEPT/)
end
end
end
end
end

end
569 changes: 567 additions & 2 deletions spec/acceptance/firewall_spec.rb

Large diffs are not rendered by default.

117 changes: 117 additions & 0 deletions spec/acceptance/firewall_uid_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
require 'spec_helper_acceptance'

describe 'firewall type', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) do

describe 'reset' do
it 'deletes all rules' do
shell('iptables --flush; iptables -t nat --flush; iptables -t mangle --flush')
end
it 'deletes all ip6tables rules' do
shell('ip6tables --flush; ip6tables -t nat --flush; ip6tables -t mangle --flush')
end
end

describe "uid tests" do
context 'uid set to root' do
it 'applies' do
pp = <<-EOS
class { '::firewall': }
firewall { '801 - test':
chain => 'OUTPUT',
action => accept,
uid => 'root',
proto => 'all',
}
EOS

apply_manifest(pp, :catch_failures => true)
unless fact('selinux') == 'true'
apply_manifest(pp, :catch_changes => true)
end
end

it 'should contain the rule' do
shell('iptables-save') do |r|
expect(r.stdout).to match(/-A OUTPUT -m owner --uid-owner (0|root) -m comment --comment "801 - test" -j ACCEPT/)
end
end
end

context 'uid set to !root' do
it 'applies' do
pp = <<-EOS
class { '::firewall': }
firewall { '802 - test':
chain => 'OUTPUT',
action => accept,
uid => '!root',
proto => 'all',
}
EOS

apply_manifest(pp, :catch_failures => true)
unless fact('selinux') == 'true'
apply_manifest(pp, :catch_changes => true)
end
end

it 'should contain the rule' do
shell('iptables-save') do |r|
expect(r.stdout).to match(/-A OUTPUT -m owner ! --uid-owner (0|root) -m comment --comment "802 - test" -j ACCEPT/)
end
end
end

context 'uid set to 0' do
it 'applies' do
pp = <<-EOS
class { '::firewall': }
firewall { '803 - test':
chain => 'OUTPUT',
action => accept,
uid => '0',
proto => 'all',
}
EOS

apply_manifest(pp, :catch_failures => true)
unless fact('selinux') == 'true'
apply_manifest(pp, :catch_changes => true)
end
end

it 'should contain the rule' do
shell('iptables-save') do |r|
expect(r.stdout).to match(/-A OUTPUT -m owner --uid-owner (0|root) -m comment --comment "803 - test" -j ACCEPT/)
end
end
end

context 'uid set to !0' do
it 'applies' do
pp = <<-EOS
class { '::firewall': }
firewall { '804 - test':
chain => 'OUTPUT',
action => accept,
uid => '!0',
proto => 'all',
}
EOS

apply_manifest(pp, :catch_failures => true)
unless fact('selinux') == 'true'
apply_manifest(pp, :catch_changes => true)
end
end

it 'should contain the rule' do
shell('iptables-save') do |r|
expect(r.stdout).to match(/-A OUTPUT -m owner ! --uid-owner (0|root) -m comment --comment "804 - test" -j ACCEPT/)
end
end
end

end

end
58 changes: 51 additions & 7 deletions spec/acceptance/resource_cmd_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@
# existing ruleset scenarios. This will give the parsing capabilities of the
# code a good work out.
describe 'puppet resource firewall command:', :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) do
before(:all) do
# In order to properly check stderr for anomalies we need to fix the deprecation warnings from puppet.conf.
config = shell('puppet config print config').stdout
shell("sed -i -e \'s/^templatedir.*$//\' #{config}")
end

context 'make sure it returns no errors when executed on a clean machine' do
it do
shell('puppet resource firewall') do |r|
r.exit_code.should be_zero
# don't check stdout, some boxes come with rules, that is normal
# don't check stderr, puppet throws deprecation warnings
r.stderr.should be_empty
end
end
end
Expand Down Expand Up @@ -38,7 +44,7 @@
shell('puppet resource firewall') do |r|
r.exit_code.should be_zero
# don't check stdout, testing preexisting rules, output is normal
# don't check stderr, puppet throws deprecation warnings
r.stderr.should be_empty
end
end
end
Expand All @@ -53,7 +59,7 @@
shell('puppet resource firewall') do |r|
r.exit_code.should be_zero
# don't check stdout, testing preexisting rules, output is normal
# don't check stderr, puppet throws deprecation warnings
r.stderr.should be_empty
end
end
end
Expand All @@ -70,7 +76,7 @@
shell('puppet resource firewall') do |r|
r.exit_code.should be_zero
# don't check stdout, testing preexisting rules, output is normal
# don't check stderr, puppet throws deprecation warnings
r.stderr.should be_empty
end
end
end
Expand All @@ -85,7 +91,7 @@
shell('puppet resource firewall') do |r|
r.exit_code.should be_zero
# don't check stdout, testing preexisting rules, output is normal
# don't check stderr, puppet throws deprecation warnings
r.stderr.should be_empty
end
end
end
Expand All @@ -103,7 +109,7 @@
shell('puppet resource firewall') do |r|
r.exit_code.should be_zero
# don't check stdout, testing preexisting rules, output is normal
# don't check stderr, puppet throws deprecation warnings
r.stderr.should be_empty
end
end
end
Expand All @@ -122,7 +128,45 @@
shell('puppet resource firewall') do |r|
r.exit_code.should be_zero
# don't check stdout, testing preexisting rules, output is normal
# don't check stderr, puppet throws deprecation warnings
r.stderr.should be_empty
end
end
end

context 'accepts rules with -m (tcp|udp) without dport/sport' do
before :all do
iptables_flush_all_tables
shell('iptables -A INPUT -s 10.0.0.0/8 -p udp -m udp -j ACCEPT')
end

it do
shell('puppet resource firewall') do |r|
r.exit_code.should be_zero
# don't check stdout, testing preexisting rules, output is normal
r.stderr.should be_empty
end
end
end

# version of iptables that ships with el5 doesn't work with the
# ip6tables provider
if default['platform'] !~ /el-5/
context 'dport/sport with ip6tables' do
before :all do
if fact('osfamily') == 'Debian'
shell('echo "iptables-persistent iptables-persistent/autosave_v4 boolean false" | debconf-set-selections')
shell('echo "iptables-persistent iptables-persistent/autosave_v6 boolean false" | debconf-set-selections')
shell('apt-get install iptables-persistent -y')
end
ip6tables_flush_all_tables
shell('ip6tables -A INPUT -d fe80::/64 -p tcp -m tcp --dport 546 --sport 547 -j ACCEPT -m comment --comment 000-foobar')
end
it do
shell('puppet resource firewall \'000-foobar\' provider=ip6tables') do |r|
r.exit_code.should be_zero
# don't check stdout, testing preexisting rules, output is normal
r.stderr.should be_empty
end
end
end
end
Expand Down
10 changes: 10 additions & 0 deletions spec/fixtures/ip6tables/conversion_hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@
:destination => '2001:db8:4321::/48',
},
},
'udp_source_port_and_destination_port' => {
:line => '-A ufw6-before-input -s fe80::/10 -d fe80::/10 -p udp -m udp --sport 547 --dport 546 -j ACCEPT',
:table => 'filter',
:provider => 'ip6tables',
:params => {
:proto => 'udp',
:sport => ['547'],
:dport => ['546'],
},
}
}

# This hash is for testing converting a hash to an argument line.
Expand Down
111 changes: 88 additions & 23 deletions spec/unit/facter/iptables_persistent_version_spec.rb
Original file line number Diff line number Diff line change
@@ -1,35 +1,100 @@
require 'spec_helper'

describe "Facter::Util::Fact iptables_persistent_version" do
before { Facter.clear }
let(:dpkg_cmd) { "dpkg-query -Wf '${Version}' iptables-persistent 2>/dev/null" }

{
"Debian" => "0.0.20090701",
"Ubuntu" => "0.5.3ubuntu2",
}.each do |os, ver|
describe "#{os} package installed" do


context "iptables-persistent applicable" do
before { Facter.clear }

let(:dpkg_cmd) { "dpkg-query -Wf '${Version}' iptables-persistent 2>/dev/null" }

{
"Debian" => "0.0.20090701",
"Ubuntu" => "0.5.3ubuntu2",
}.each do |os, ver|

if os == "Debian"
os_release = "7.0"
elsif os == "Ubuntu"
os_release = "14.04"
end

describe "#{os} package installed" do
before {
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return(os)
allow(Facter.fact(:operatingsystemrelease)).to receive(:value).and_return(os_release)
allow(Facter::Util::Resolution).to receive(:exec).with(dpkg_cmd).
and_return(ver)
}
it { Facter.fact(:iptables_persistent_version).value.should == ver }
end
end

describe 'Ubuntu package not installed' do
before {
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return(os)
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('Ubuntu')
allow(Facter.fact(:operatingsystemrelease)).to receive(:value).and_return('14.04')
allow(Facter::Util::Resolution).to receive(:exec).with(dpkg_cmd).
and_return(ver)
and_return(nil)
}
it { Facter.fact(:iptables_persistent_version).value.should == ver }
it { Facter.fact(:iptables_persistent_version).value.should be_nil }
end

describe 'CentOS not supported' do
before { allow(Facter.fact(:operatingsystem)).to receive(:value).
and_return("CentOS") }
it { Facter.fact(:iptables_persistent_version).value.should be_nil }
end
end

describe 'Ubuntu package not installed' do
before {
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('Ubuntu')
allow(Facter::Util::Resolution).to receive(:exec).with(dpkg_cmd).
and_return(nil)
}
it { Facter.fact(:iptables_persistent_version).value.should be_nil }
end

describe 'CentOS not supported' do
before { allow(Facter.fact(:operatingsystem)).to receive(:value).
and_return("CentOS") }
it { Facter.fact(:iptables_persistent_version).value.should be_nil }
context "netfilter-persistent applicable" do
before { Facter.clear }

let(:dpkg_cmd) { "dpkg-query -Wf '${Version}' netfilter-persistent 2>/dev/null" }

{
"Debian" => "0.0.20090701",
"Ubuntu" => "0.5.3ubuntu2",
}.each do |os, ver|

if os == "Debian"
os_release = "8.0"
elsif os == "Ubuntu"
os_release = "14.10"
end

describe "#{os} package installed" do
before {
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return(os)
allow(Facter.fact(:operatingsystemrelease)).to receive(:value).and_return(os_release)
allow(Facter::Util::Resolution).to receive(:exec).with(dpkg_cmd).
and_return(ver)
}
it { Facter.fact(:iptables_persistent_version).value.should == ver }
end
end

describe 'Ubuntu package not installed' do
os_release = "14.10"
before {
allow(Facter.fact(:operatingsystem)).to receive(:value).and_return('Ubuntu')
allow(Facter.fact(:operatingsystemrelease)).to receive(:value).and_return(os_release)
allow(Facter::Util::Resolution).to receive(:exec).with(dpkg_cmd).
and_return(nil)
}
it { Facter.fact(:iptables_persistent_version).value.should be_nil }
end

describe 'CentOS not supported' do
before { allow(Facter.fact(:operatingsystem)).to receive(:value).
and_return("CentOS") }
it { Facter.fact(:iptables_persistent_version).value.should be_nil }
end

end




end
61 changes: 61 additions & 0 deletions spec/unit/puppet/provider/ip6tables_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env rspec

require 'spec_helper'
if Puppet.version < '3.4.0'
require 'puppet/provider/confine/exists'
else
require 'puppet/confine/exists'
end
provider_class = Puppet::Type.type(:firewall).provider(:ip6tables)
describe 'ip6tables' do
let(:params) { {:name => '000 test foo', :action => 'accept'} }
let(:provider) { provider_class }
let(:resource) { Puppet::Type.type(:firewall) }
let(:ip6tables_version) { '1.4.0' }

before :each do

end

def stub_iptables
allow(Puppet::Type::Firewall).to receive(:defaultprovider).and_return provider
# Stub confine facts
allow(provider).to receive(:command).with(:iptables_save).and_return "/sbin/iptables-save"

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)
allow(Puppet::Util::Execution).to receive(:execute).and_return ""
allow(Puppet::Util).to receive(:which).with("iptables-save").
and_return "/sbin/iptables-save"
end

shared_examples 'raise error' do
it {
stub_iptables
expect {
provider.new(resource.new(params))
}.to raise_error(Puppet::DevError, error_message)
}
end
shared_examples 'run' do
it {
stub_iptables
provider.new(resource.new(params))
}
end
context 'iptables 1.3' do
let(:params) { {:name => '000 test foo', :action => 'accept'} }
let(:error_message) { /The ip6tables provider is not supported on version 1\.3 of iptables/ }
let(:ip6tables_version) { '1.3.10' }
it_should_behave_like 'raise error'
end
context 'ip6tables nil' do
let(:params) { {:name => '000 test foo', :action => 'accept'} }
let(:error_message) { /The ip6tables provider is not supported on version 1\.3 of iptables/ }
let(:ip6tables_version) { nil }
it_should_behave_like 'run'
end


end
2 changes: 1 addition & 1 deletion spec/unit/puppet/type/firewall_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@
end
end

[:tosource, :todest].each do |addr|
[:tosource, :todest, :to].each do |addr|
describe addr do
it "should accept #{addr} value as a string" do
@resource[addr] = '127.0.0.1'
Expand Down
7 changes: 5 additions & 2 deletions spec/unit/puppet/type/firewallchain_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@

describe 'purge iptables rules' do
before(:each) do
allow(Puppet::Type.type(:firewall).provider(:iptables)).to receive(:iptables_save).and_return(<<EOS
stub_return = <<EOS
# Completed on Sun Jan 5 19:30:21 2014
# Generated by iptables-save v1.4.12 on Sun Jan 5 19:30:21 2014
*filter
Expand All @@ -162,16 +162,19 @@
COMMIT
# Completed on Sun Jan 5 19:30:21 2014
EOS
)
allow(Puppet::Type.type(:firewall).provider(:iptables)).to receive(:iptables_save).and_return(stub_return)
allow(Puppet::Type.type(:firewall).provider(:ip6tables)).to receive(:ip6tables_save).and_return(stub_return)
end

it 'should generate iptables resources' do
allow(Facter.fact(:ip6tables_version)).to receive(:value).and_return("1.4.21")
resource = Puppet::Type::Firewallchain.new(:name => 'INPUT:filter:IPv4', :purge => true)

expect(resource.generate.size).to eq(3)
end

it 'should not generate ignored iptables rules' do
allow(Facter.fact(:ip6tables_version)).to receive(:value).and_return("1.4.21")
resource = Puppet::Type::Firewallchain.new(:name => 'INPUT:filter:IPv4', :purge => true, :ignore => ['-j fail2ban-ssh'])

expect(resource.generate.size).to eq(2)
Expand Down