aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--REFERENCE.md139
-rw-r--r--manifests/chain.pp71
-rw-r--r--manifests/config.pp29
-rw-r--r--manifests/init.pp9
-rw-r--r--manifests/rule.pp76
-rw-r--r--spec/acceptance/ferm_spec.rb92
-rw-r--r--spec/classes/ferm_spec.rb61
-rw-r--r--spec/defines/chain_spec.rb30
-rw-r--r--spec/defines/rule_spec.rb119
-rw-r--r--spec/spec_helper.rb5
-rw-r--r--templates/ferm-table-chain-config-include.epp14
-rw-r--r--templates/ferm.conf.epp16
-rw-r--r--templates/ferm_chain_header.conf.epp8
-rw-r--r--templates/ferm_header.conf.epp2
-rw-r--r--types/actions.pp6
-rw-r--r--types/policies.pp4
-rw-r--r--types/tables.pp2
17 files changed, 579 insertions, 104 deletions
diff --git a/REFERENCE.md b/REFERENCE.md
index 39ba310..19ffae0 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -17,13 +17,15 @@ _Private Classes_
**Defined types**
-* [`ferm::chain`](#fermchain): defined resource which creates all rules for one chain
-* [`ferm::rule`](#fermrule): defined resource which creates a single rule in a specific chain
+* [`ferm::chain`](#fermchain): This defined resource manages ferm/iptables chains
+* [`ferm::rule`](#fermrule): This defined resource manages a single rule in a specific chain
**Data types**
-* [`Ferm::Policies`](#fermpolicies): a list of allowed default policies for a chain
+* [`Ferm::Actions`](#fermactions): a list of allowed actions for a rule
+* [`Ferm::Policies`](#fermpolicies): a list of allowed policies for a chain
* [`Ferm::Protocols`](#fermprotocols): a list of allowed protocolls to match
+* [`Ferm::Tables`](#fermtables): a list of available tables
## Classes
@@ -123,7 +125,7 @@ Data type: `Ferm::Policies`
Default policy for the FORWARD chain
Default value: DROP
-Allowed values: (ACCEPT|DROP|REJECT)
+Allowed values: (ACCEPT|DROP)
##### `output_policy`
@@ -131,7 +133,7 @@ Data type: `Ferm::Policies`
Default policy for the OUTPUT chain
Default value: ACCEPT
-Allowed values: (ACCEPT|DROP|REJECT)
+Allowed values: (ACCEPT|DROP)
##### `input_policy`
@@ -139,7 +141,7 @@ Data type: `Ferm::Policies`
Default policy for the INPUT chain
Default value: DROP
-Allowed values: (ACCEPT|DROP|REJECT)
+Allowed values: (ACCEPT|DROP)
##### `rules`
@@ -193,17 +195,23 @@ Example: {'nat' => ['PREROUTING', 'POSTROUTING']}
### ferm::chain
-defined resource which creates all rules for one chain
+This defined resource manages ferm/iptables chains
-#### Parameters
+#### Examples
-The following parameters are available in the `ferm::chain` defined type.
+##### create a custom chain, e.g. for all incoming SSH connections
-##### `policy`
+```puppet
+ferm::chain{'check-ssh':
+ chain => 'SSH',
+ disable_conntrack => true,
+ log_dropped_packets => true,
+}
+```
-Data type: `Ferm::Policies`
+#### Parameters
-Set the default policy for a CHAIN
+The following parameters are available in the `ferm::chain` defined type.
##### `disable_conntrack`
@@ -211,23 +219,70 @@ Data type: `Boolean`
Disable/Enable usage of conntrack
+##### `log_dropped_packets`
+
+Data type: `Boolean`
+
+Enable/Disable logging of packets to the kernel log, if no explicit chain matched
+
+##### `policy`
+
+Data type: `Optional[Ferm::Policies]`
+
+Set the default policy for CHAIN (works only for builtin chains)
+Default value: undef
+Allowed values: (ACCEPT|DROP) (see Ferm::Policies type)
+
+Default value: `undef`
+
##### `chain`
Data type: `String[1]`
Name of the chain that should be managed
+Default value: $name (resource name)
+Allowed values: String[1]
Default value: $name
-##### `log_dropped_packets`
+##### `table`
-Data type: `Boolean`
+Data type: `Ferm::Tables`
-Enable/Disable logging of packets to the kernel log, if no explicit chain matched
+Select the target table (filter/raw/mangle/nat)
+Default value: 'filter'
+Allowed values: (filter|raw|mangle|nat) (see Ferm::Tables type)
+
+Default value: 'filter'
### ferm::rule
-defined resource which creates a single rule in a specific chain
+This defined resource manages a single rule in a specific chain
+
+#### Examples
+
+##### Jump to the 'SSH' chain for all incoming SSH traffic (see chain.pp examples on how to create the chain)
+
+```puppet
+ferm::rule{'incoming-ssh':
+ chain => 'INPUT',
+ action => 'SSH',
+ proto => 'tcp',
+ dport => '22',
+}
+```
+
+##### Create a rule in the 'SSH' chain to allow connections from localhost
+
+```puppet
+ferm::rule{'allow-ssh-localhost':
+ chain => 'SSH',
+ action => 'ACCEPT',
+ proto => 'tcp',
+ dport => '22',
+ saddr => '127.0.0.1',
+}
+```
#### Parameters
@@ -239,12 +294,6 @@ Data type: `String[1]`
Configure the chain where we want to add the rule
-##### `policy`
-
-Data type: `Ferm::Policies`
-
-Configure what we want to do with the packet (drop, accept, log...)
-
##### `proto`
Data type: `Ferm::Protocols`
@@ -259,6 +308,26 @@ A comment that will be added to the ferm config and to ip{,6}tables
Default value: $name
+##### `action`
+
+Data type: `Optional[Ferm::Actions]`
+
+Configure what we want to do with the packet (drop/accept/reject, can also be a target chain name)
+Default value: undef
+Allowed values: (RETURN|ACCEPT|DROP|REJECT|NOTRACK|LOG|MARK|DNAT|SNAT|MASQUERADE|REDIRECT|String[1])
+
+Default value: `undef`
+
+##### `policy`
+
+Data type: `Optional[Ferm::Policies]`
+
+Configure what we want to do with the packet (drop/accept/reject, can also be a target chain name) [DEPRECATED]
+Default value: undef
+Allowed values: (RETURN|ACCEPT|DROP|REJECT|NOTRACK|LOG|MARK|DNAT|SNAT|MASQUERADE|REDIRECT|String[1])
+
+Default value: `undef`
+
##### `dport`
Data type: `Optional[Variant[Stdlib::Port,String[1]]]`
@@ -315,13 +384,29 @@ Set the rule to present or absent
Default value: 'present'
+##### `table`
+
+Data type: `Ferm::Tables`
+
+Select the target table (filter/raw/mangle/nat)
+Default value: filter
+Allowed values: (filter|raw|mangle|nat) (see Ferm::Tables type)
+
+Default value: 'filter'
+
## Data types
+### Ferm::Actions
+
+As you can also *jump* to other chains, each chain-name is also a valid action/target
+
+Alias of `Variant[Enum['RETURN', 'ACCEPT', 'DROP', 'REJECT', 'NOTRACK', 'LOG', 'MARK', 'DNAT', 'SNAT', 'MASQUERADE', 'REDIRECT'], String[1]]`
+
### Ferm::Policies
-a list of allowed default policies for a chain
+a list of allowed policies for a chain
-Alias of `Enum['ACCEPT', 'DROP', 'REJECT']`
+Alias of `Enum['ACCEPT', 'DROP']`
### Ferm::Protocols
@@ -329,3 +414,9 @@ a list of allowed protocolls to match
Alias of `Enum['icmp', 'tcp', 'udp', 'udplite', 'icmpv6', 'esp', 'ah', 'sctp', 'mh', 'all']`
+### Ferm::Tables
+
+a list of available tables
+
+Alias of `Enum['raw', 'mangle', 'nat', 'filter']`
+
diff --git a/manifests/chain.pp b/manifests/chain.pp
index 1198f62..a01b9b4 100644
--- a/manifests/chain.pp
+++ b/manifests/chain.pp
@@ -1,23 +1,57 @@
-# defined resource which creates all rules for one chain
-# @param policy Set the default policy for a CHAIN
+# @summary This defined resource manages ferm/iptables chains
+#
+# @example create a custom chain, e.g. for all incoming SSH connections
+# ferm::chain{'check-ssh':
+# chain => 'SSH',
+# disable_conntrack => true,
+# log_dropped_packets => true,
+# }
+#
# @param disable_conntrack Disable/Enable usage of conntrack
-# @param chain Name of the chain that should be managed
# @param log_dropped_packets Enable/Disable logging of packets to the kernel log, if no explicit chain matched
+# @param policy Set the default policy for CHAIN (works only for builtin chains)
+# Default value: undef
+# Allowed values: (ACCEPT|DROP) (see Ferm::Policies type)
+# @param chain Name of the chain that should be managed
+# Default value: $name (resource name)
+# Allowed values: String[1]
+# @param table Select the target table (filter/raw/mangle/nat)
+# Default value: 'filter'
+# Allowed values: (filter|raw|mangle|nat) (see Ferm::Tables type)
define ferm::chain (
- Ferm::Policies $policy,
Boolean $disable_conntrack,
Boolean $log_dropped_packets,
- String[1] $chain = $name,
+ String[1] $chain = $name,
+ Optional[Ferm::Policies] $policy = undef,
+ Ferm::Tables $table = 'filter',
) {
+ # prevent unmanaged files due to new naming schema
+ # keep the default "filter" chains in the original location
+ # only prefix chains in other tables with the table name
+ if $table == 'filter' and $chain in ['INPUT', 'FORWARD', 'OUTPUT'] {
+ $filename = "${ferm::configdirectory}/chains/${chain}.conf"
+ } else {
+ $filename = "${ferm::configdirectory}/chains/${table}-${chain}.conf"
+ }
+
+ $builtin_chains = {
+ 'raw' => ['PREROUTING', 'OUTPUT'],
+ 'nat' => ['PREROUTING', 'INPUT', 'OUTPUT', 'POSTROUTING'],
+ 'mangle' => ['PREROUTING', 'INPUT', 'FORWARD', 'OUTPUT', 'POSTROUTING'],
+ 'filter' => ['INPUT', 'FORWARD', 'OUTPUT'],
+ }
+
+ if $policy and ! ($chain in $builtin_chains[$table]) {
+ fail("Can only set a default policy for builtin chains. '${chain}' is not a builtin chain.")
+ }
# concat resource for the chain
- $filename = downcase($chain)
- concat{"${ferm::configdirectory}/chains/${chain}.conf":
+ concat{$filename:
ensure => 'present',
}
- concat::fragment{"${chain}-policy":
- target => "${ferm::configdirectory}/chains/${chain}.conf",
+ concat::fragment{"${table}-${chain}-policy":
+ target => $filename,
content => epp(
"${module_name}/ferm_chain_header.conf.epp", {
'policy' => $policy,
@@ -28,10 +62,25 @@ define ferm::chain (
}
if $log_dropped_packets {
- concat::fragment{"${chain}-footer":
- target => "${ferm::configdirectory}/chains/${chain}.conf",
+ concat::fragment{"${table}-${chain}-footer":
+ target => $filename,
content => epp("${module_name}/ferm_chain_footer.conf.epp", { 'chain' => $chain }),
order => 'zzzzzzzzzzzzzzzzzzzzz',
}
}
+
+ # make sure the generated snippet is actually included
+ concat::fragment{"${table}-${chain}-config-include":
+ target => $ferm::configfile,
+ content => epp(
+ "${module_name}/ferm-table-chain-config-include.epp", {
+ 'ip' => join($ferm::ip_versions, ' '),
+ 'table' => $table,
+ 'chain' => $chain,
+ 'filename' => $filename,
+ }
+ ),
+ order => "${table}-${chain}",
+ require => Concat[$filename],
+ }
}
diff --git a/manifests/config.pp b/manifests/config.pp
index 25607ad..efabe2b 100644
--- a/manifests/config.pp
+++ b/manifests/config.pp
@@ -57,4 +57,33 @@ class ferm::config {
disable_conntrack => $ferm::disable_conntrack,
log_dropped_packets => $ferm::output_log_dropped_packets,
}
+
+ # initialize default tables and chains
+ ['PREROUTING', 'OUTPUT'].each |$raw_chain| {
+ ferm::chain{"raw-${raw_chain}":
+ chain => $raw_chain,
+ policy => 'ACCEPT',
+ disable_conntrack => true,
+ log_dropped_packets => false,
+ table => 'raw',
+ }
+ }
+ ['PREROUTING', 'INPUT', 'OUTPUT', 'POSTROUTING'].each |$nat_chain| {
+ ferm::chain{"nat-${nat_chain}":
+ chain => $nat_chain,
+ policy => 'ACCEPT',
+ disable_conntrack => true,
+ log_dropped_packets => false,
+ table => 'nat',
+ }
+ }
+ ['PREROUTING', 'INPUT', 'FORWARD', 'OUTPUT', 'POSTROUTING'].each |$mangle_chain| {
+ ferm::chain{"mangle-${mangle_chain}":
+ chain => $mangle_chain,
+ policy => 'ACCEPT',
+ disable_conntrack => true,
+ log_dropped_packets => false,
+ table => 'mangle',
+ }
+ }
}
diff --git a/manifests/init.pp b/manifests/init.pp
index 221e148..d2251c9 100644
--- a/manifests/init.pp
+++ b/manifests/init.pp
@@ -45,13 +45,13 @@
# Allowed values: (true|false)
# @param forward_policy Default policy for the FORWARD chain
# Default value: DROP
-# Allowed values: (ACCEPT|DROP|REJECT)
+# Allowed values: (ACCEPT|DROP)
# @param output_policy Default policy for the OUTPUT chain
# Default value: ACCEPT
-# Allowed values: (ACCEPT|DROP|REJECT)
+# Allowed values: (ACCEPT|DROP)
# @param input_policy Default policy for the INPUT chain
# Default value: DROP
-# Allowed values: (ACCEPT|DROP|REJECT)
+# Allowed values: (ACCEPT|DROP)
# @param rules A hash that holds all data for ferm::rule
# Default value: Empty Hash
# Allowed value: Any Hash
@@ -95,6 +95,9 @@ class ferm (
-> Class['ferm::config']
~> Class['ferm::service']
+ Ferm::Chain <| |>
+ ~> Class['ferm::service']
+
$rules.each |$rulename, $attributes| {
ferm::rule{$rulename:
* => $attributes,
diff --git a/manifests/rule.pp b/manifests/rule.pp
index 68e88a2..4f2c985 100644
--- a/manifests/rule.pp
+++ b/manifests/rule.pp
@@ -1,8 +1,31 @@
-# defined resource which creates a single rule in a specific chain
+# @summary This defined resource manages a single rule in a specific chain
+#
+# @example Jump to the 'SSH' chain for all incoming SSH traffic (see chain.pp examples on how to create the chain)
+# ferm::rule{'incoming-ssh':
+# chain => 'INPUT',
+# action => 'SSH',
+# proto => 'tcp',
+# dport => '22',
+# }
+#
+# @example Create a rule in the 'SSH' chain to allow connections from localhost
+# ferm::rule{'allow-ssh-localhost':
+# chain => 'SSH',
+# action => 'ACCEPT',
+# proto => 'tcp',
+# dport => '22',
+# saddr => '127.0.0.1',
+# }
+#
# @param chain Configure the chain where we want to add the rule
-# @param policy Configure what we want to do with the packet (drop, accept, log...)
# @param proto Which protocol do we want to match, typically UDP or TCP
# @param comment A comment that will be added to the ferm config and to ip{,6}tables
+# @param action Configure what we want to do with the packet (drop/accept/reject, can also be a target chain name)
+# Default value: undef
+# Allowed values: (RETURN|ACCEPT|DROP|REJECT|NOTRACK|LOG|MARK|DNAT|SNAT|MASQUERADE|REDIRECT|String[1])
+# @param policy Configure what we want to do with the packet (drop/accept/reject, can also be a target chain name) [DEPRECATED]
+# Default value: undef
+# Allowed values: (RETURN|ACCEPT|DROP|REJECT|NOTRACK|LOG|MARK|DNAT|SNAT|MASQUERADE|REDIRECT|String[1])
# @param dport The destination port, can be a range as string or a single port number as integer
# @param sport The source port, can be a range as string or a single port number as integer
# @param saddr The source address we want to match
@@ -10,11 +33,15 @@
# @param proto_options Optional parameters that will be passed to the protocol (for example to match specific ICMP types)
# @param interface an Optional interface where this rule should be applied
# @param ensure Set the rule to present or absent
+# @param table Select the target table (filter/raw/mangle/nat)
+# Default value: filter
+# Allowed values: (filter|raw|mangle|nat) (see Ferm::Tables type)
define ferm::rule (
String[1] $chain,
- Ferm::Policies $policy,
Ferm::Protocols $proto,
String $comment = $name,
+ Optional[Ferm::Actions] $action = undef,
+ Optional[Ferm::Policies] $policy = undef,
Optional[Variant[Stdlib::Port,String[1]]] $dport = undef,
Optional[Variant[Stdlib::Port,String[1]]] $sport = undef,
Optional[Variant[Array, String[1]]] $saddr = undef,
@@ -22,7 +49,31 @@ define ferm::rule (
Optional[String[1]] $proto_options = undef,
Optional[String[1]] $interface = undef,
Enum['absent','present'] $ensure = 'present',
+ Ferm::Tables $table = 'filter',
){
+
+ if $policy and $action {
+ fail('Cannot specify both policy and action. Do not provide policy when using the new action param.')
+ } elsif $policy and ! $action {
+ warning('The param "policy" is deprecated (superseded by "action") and will be dropped in a future release.')
+ $action_temp = $policy
+ } elsif $action and ! $policy {
+ $action_temp = $action
+ } else {
+ fail('Exactly one of "action" or the deprecated "policy" param is required.')
+ }
+
+ if $action_temp in ['RETURN', 'ACCEPT', 'DROP', 'REJECT', 'NOTRACK', 'LOG',
+ 'MARK', 'DNAT', 'SNAT', 'MASQUERADE', 'REDIRECT'] {
+ $action_real = $action_temp
+ } else {
+ # assume the action contains a target chain, so prefix it with the "jump" statement
+ $action_real = "jump ${action_temp}"
+ # make sure the target chain is created before we try to add rules to it
+ Ferm::Chain <| chain == $action_temp and table == $table |> -> Ferm::Rule[$name]
+ }
+
+
$proto_real = "proto ${proto}"
$dport_real = $dport ? {
@@ -63,33 +114,42 @@ define ferm::rule (
}
$comment_real = "mod comment comment '${comment}'"
- $rule = squeeze("${comment_real} ${proto_real} ${proto_options_real} ${dport_real} ${sport_real} ${daddr_real} ${saddr_real} ${policy};", ' ')
+ # prevent unmanaged files due to new naming schema
+ # keep the default "filter" chains in the original location
+ # only prefix chains in other tables with the table name
+ if $table == 'filter' and $chain in ['INPUT', 'FORWARD', 'OUTPUT'] {
+ $filename = "${ferm::configdirectory}/chains/${chain}.conf"
+ } else {
+ $filename = "${ferm::configdirectory}/chains/${table}-${chain}.conf"
+ }
+
+ $rule = squeeze("${comment_real} ${proto_real} ${proto_options_real} ${dport_real} ${sport_real} ${daddr_real} ${saddr_real} ${action_real};", ' ')
if $ensure == 'present' {
if $interface {
unless defined(Concat::Fragment["${chain}-${interface}-aaa"]) {
concat::fragment{"${chain}-${interface}-aaa":
- target => "${ferm::configdirectory}/chains/${chain}.conf",
+ target => $filename,
content => "interface ${interface} {\n",
order => $interface,
}
}
concat::fragment{"${chain}-${interface}-${name}":
- target => "${ferm::configdirectory}/chains/${chain}.conf",
+ target => $filename,
content => " ${rule}\n",
order => $interface,
}
unless defined(Concat::Fragment["${chain}-${interface}-zzz"]) {
concat::fragment{"${chain}-${interface}-zzz":
- target => "${ferm::configdirectory}/chains/${chain}.conf",
+ target => $filename,
content => "}\n",
order => $interface,
}
}
} else {
concat::fragment{"${chain}-${name}":
- target => "${ferm::configdirectory}/chains/${chain}.conf",
+ target => $filename,
content => "${rule}\n",
}
}
diff --git a/spec/acceptance/ferm_spec.rb b/spec/acceptance/ferm_spec.rb
index 1b0f794..b0c41a5 100644
--- a/spec/acceptance/ferm_spec.rb
+++ b/spec/acceptance/ferm_spec.rb
@@ -12,27 +12,29 @@ manage_initfile = case sut_os
false
end
+basic_manifest = %(
+ class { 'ferm':
+ manage_service => true,
+ manage_configfile => true,
+ manage_initfile => #{manage_initfile}, # CentOS-6 does not provide init script
+ forward_policy => 'DROP',
+ output_policy => 'DROP',
+ input_policy => 'DROP',
+ rules => {
+ 'allow_acceptance_tests' => {
+ chain => 'INPUT',
+ action => 'ACCEPT',
+ proto => tcp,
+ dport => 22,
+ },
+ },
+ ip_versions => ['ip'], #only ipv4 available with CI
+ }
+)
+
describe 'ferm' do
context 'with basics settings' do
- pp = %(
- class { 'ferm':
- manage_service => true,
- manage_configfile => true,
- manage_initfile => #{manage_initfile}, # CentOS-6 does not provide init script
- forward_policy => 'DROP',
- output_policy => 'DROP',
- input_policy => 'DROP',
- rules => {
- 'allow acceptance_tests' => {
- chain => 'INPUT',
- policy => 'ACCEPT',
- proto => tcp,
- dport => 22,
- },
- },
- ip_versions => ['ip'], #only ipv4 available with CI
- }
- )
+ pp = basic_manifest
it 'works with no error' do
apply_manifest(pp, catch_failures: true)
@@ -54,7 +56,57 @@ describe 'ferm' do
end
describe iptables do
- it { is_expected.to have_rule('-A INPUT -p tcp -m comment --comment "allow acceptance_tests" -m tcp --dport 22 -j ACCEPT').with_table('filter').with_chain('INPUT') }
+ it do
+ is_expected.to have_rule('-A INPUT -p tcp -m comment --comment ["]*allow_acceptance_tests["]* -m tcp --dport 22 -j ACCEPT'). \
+ with_table('filter'). \
+ with_chain('INPUT')
+ end
+ end
+
+ context 'with custom chains' do
+ advanced_manifest = %(
+ ferm::chain { 'check-http':
+ chain => 'HTTP',
+ disable_conntrack => true,
+ log_dropped_packets => false,
+ }
+ ferm::rule { 'jump_http':
+ chain => 'INPUT',
+ action => 'HTTP',
+ proto => 'tcp',
+ dport => '80',
+ require => Ferm::Chain['check-http'],
+ }
+ ferm::rule { 'allow_http_localhost':
+ chain => 'HTTP',
+ action => 'ACCEPT',
+ proto => 'tcp',
+ dport => '80',
+ saddr => '127.0.0.1',
+ require => Ferm::Chain['check-http'],
+ }
+ )
+ pp = [basic_manifest, advanced_manifest].join("\n")
+
+ it 'works with no error' do
+ apply_manifest(pp, catch_failures: true)
+ end
+ it 'works idempotently' do
+ apply_manifest(pp, catch_changes: true)
+ end
+
+ describe iptables do
+ it do
+ is_expected.to have_rule('-A INPUT -p tcp -m comment --comment ["]*jump_http["]* -m tcp --dport 80 -j HTTP'). \
+ with_table('filter'). \
+ with_chain('INPUT')
+ end
+ it do
+ is_expected.to have_rule('-A HTTP -s 127.0.0.1/32 -p tcp -m comment --comment ["]*allow_http_localhost["]* -m tcp --dport 80 -j ACCEPT'). \
+ with_table('filter'). \
+ with_chain('HTTP')
+ end
+ end
end
end
end
diff --git a/spec/classes/ferm_spec.rb b/spec/classes/ferm_spec.rb
index e5669b8..225577b 100644
--- a/spec/classes/ferm_spec.rb
+++ b/spec/classes/ferm_spec.rb
@@ -64,6 +64,17 @@ describe 'ferm' do
is_expected.to contain_concat__fragment('ferm.conf'). \
without_content(%r{@preserve;})
end
+ it { is_expected.to contain_concat__fragment('raw-PREROUTING-config-include') }
+ it { is_expected.to contain_concat__fragment('raw-OUTPUT-config-include') }
+ it { is_expected.to contain_concat__fragment('nat-PREROUTING-config-include') }
+ it { is_expected.to contain_concat__fragment('nat-INPUT-config-include') }
+ it { is_expected.to contain_concat__fragment('nat-OUTPUT-config-include') }
+ it { is_expected.to contain_concat__fragment('nat-POSTROUTING-config-include') }
+ it { is_expected.to contain_concat__fragment('mangle-PREROUTING-config-include') }
+ it { is_expected.to contain_concat__fragment('mangle-INPUT-config-include') }
+ it { is_expected.to contain_concat__fragment('mangle-FORWARD-config-include') }
+ it { is_expected.to contain_concat__fragment('mangle-OUTPUT-config-include') }
+ it { is_expected.to contain_concat__fragment('mangle-POSTROUTING-config-include') }
end
context 'with managed initfile' do
let :params do
@@ -77,18 +88,62 @@ describe 'ferm' do
end
end
context 'it creates chains' do
- it { is_expected.to contain_concat__fragment('FORWARD-policy') }
- it { is_expected.to contain_concat__fragment('INPUT-policy') }
- it { is_expected.to contain_concat__fragment('OUTPUT-policy') }
+ it { is_expected.to contain_concat__fragment('raw-PREROUTING-policy') }
+ it { is_expected.to contain_concat__fragment('raw-OUTPUT-policy') }
+ it { is_expected.to contain_concat__fragment('nat-PREROUTING-policy') }
+ it { is_expected.to contain_concat__fragment('nat-INPUT-policy') }
+ it { is_expected.to contain_concat__fragment('nat-OUTPUT-policy') }
+ it { is_expected.to contain_concat__fragment('nat-POSTROUTING-policy') }
+ it { is_expected.to contain_concat__fragment('mangle-PREROUTING-policy') }
+ it { is_expected.to contain_concat__fragment('mangle-INPUT-policy') }
+ it { is_expected.to contain_concat__fragment('mangle-FORWARD-policy') }
+ it { is_expected.to contain_concat__fragment('mangle-OUTPUT-policy') }
+ it { is_expected.to contain_concat__fragment('mangle-POSTROUTING-policy') }
+ it { is_expected.to contain_concat__fragment('filter-INPUT-policy') }
+ it { is_expected.to contain_concat__fragment('filter-FORWARD-policy') }
+ it { is_expected.to contain_concat__fragment('filter-OUTPUT-policy') }
if facts[:os]['release']['major'].to_i == 10
+ it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/raw-PREROUTING.conf') }
+ it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/raw-OUTPUT.conf') }
+ it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/nat-PREROUTING.conf') }
+ it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/nat-INPUT.conf') }
+ it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/nat-OUTPUT.conf') }
+ it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/nat-POSTROUTING.conf') }
+ it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/mangle-PREROUTING.conf') }
+ it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/mangle-INPUT.conf') }
+ it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/mangle-FORWARD.conf') }
+ it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/mangle-OUTPUT.conf') }
+ it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/mangle-POSTROUTING.conf') }
it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/FORWARD.conf') }
it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/INPUT.conf') }
it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/OUTPUT.conf') }
else
+ it { is_expected.to contain_concat('/etc/ferm.d/chains/raw-PREROUTING.conf') }
+ it { is_expected.to contain_concat('/etc/ferm.d/chains/raw-OUTPUT.conf') }
+ it { is_expected.to contain_concat('/etc/ferm.d/chains/nat-PREROUTING.conf') }
+ it { is_expected.to contain_concat('/etc/ferm.d/chains/nat-INPUT.conf') }
+ it { is_expected.to contain_concat('/etc/ferm.d/chains/nat-OUTPUT.conf') }
+ it { is_expected.to contain_concat('/etc/ferm.d/chains/nat-POSTROUTING.conf') }
+ it { is_expected.to contain_concat('/etc/ferm.d/chains/mangle-PREROUTING.conf') }
+ it { is_expected.to contain_concat('/etc/ferm.d/chains/mangle-INPUT.conf') }
+ it { is_expected.to contain_concat('/etc/ferm.d/chains/mangle-FORWARD.conf') }
+ it { is_expected.to contain_concat('/etc/ferm.d/chains/mangle-OUTPUT.conf') }
+ it { is_expected.to contain_concat('/etc/ferm.d/chains/mangle-POSTROUTING.conf') }
it { is_expected.to contain_concat('/etc/ferm.d/chains/FORWARD.conf') }
it { is_expected.to contain_concat('/etc/ferm.d/chains/INPUT.conf') }
it { is_expected.to contain_concat('/etc/ferm.d/chains/OUTPUT.conf') }
end
+ it { is_expected.to contain_ferm__chain('raw-PREROUTING') }
+ it { is_expected.to contain_ferm__chain('raw-OUTPUT') }
+ it { is_expected.to contain_ferm__chain('nat-PREROUTING') }
+ it { is_expected.to contain_ferm__chain('nat-INPUT') }
+ it { is_expected.to contain_ferm__chain('nat-OUTPUT') }
+ it { is_expected.to contain_ferm__chain('nat-POSTROUTING') }
+ it { is_expected.to contain_ferm__chain('mangle-PREROUTING') }
+ it { is_expected.to contain_ferm__chain('mangle-INPUT') }
+ it { is_expected.to contain_ferm__chain('mangle-FORWARD') }
+ it { is_expected.to contain_ferm__chain('mangle-OUTPUT') }
+ it { is_expected.to contain_ferm__chain('mangle-POSTROUTING') }
it { is_expected.to contain_ferm__chain('FORWARD') }
it { is_expected.to contain_ferm__chain('OUTPUT') }
it { is_expected.to contain_ferm__chain('INPUT') }
diff --git a/spec/defines/chain_spec.rb b/spec/defines/chain_spec.rb
index 9425821..4a598b3 100644
--- a/spec/defines/chain_spec.rb
+++ b/spec/defines/chain_spec.rb
@@ -15,25 +15,25 @@ describe 'ferm::chain', type: :define do
context 'default params creates INPUT2 chain' do
let :params do
{
- policy: 'DROP',
disable_conntrack: false,
log_dropped_packets: true
}
end
it { is_expected.to compile.with_all_deps }
+ it { is_expected.to contain_concat__fragment('filter-INPUT2-config-include') }
it do
- is_expected.to contain_concat__fragment('INPUT2-policy'). \
+ is_expected.to contain_concat__fragment('filter-INPUT2-policy'). \
with_content(%r{ESTABLISHED RELATED})
end
it do
- is_expected.to contain_concat__fragment('INPUT2-footer'). \
+ is_expected.to contain_concat__fragment('filter-INPUT2-footer'). \
with_content(%r{LOG log-prefix 'INPUT2: ';})
end
if facts[:os]['release']['major'].to_i == 10
- it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/INPUT2.conf') }
+ it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/filter-INPUT2.conf') }
else
- it { is_expected.to contain_concat('/etc/ferm.d/chains/INPUT2.conf') }
+ it { is_expected.to contain_concat('/etc/ferm.d/chains/filter-INPUT2.conf') }
end
it { is_expected.to contain_ferm__chain('INPUT2') }
end
@@ -41,7 +41,6 @@ describe 'ferm::chain', type: :define do
context 'without conntrack' do
let :params do
{
- policy: 'DROP',
disable_conntrack: true,
log_dropped_packets: false
}
@@ -49,15 +48,28 @@ describe 'ferm::chain', type: :define do
it { is_expected.to compile.with_all_deps }
it do
- is_expected.to contain_concat__fragment('INPUT2-policy')
- is_expected.not_to contain_concat__fragment('INPUT2-policy'). \
+ is_expected.to contain_concat__fragment('filter-INPUT2-policy')
+ is_expected.not_to contain_concat__fragment('filter-INPUT2-policy'). \
with_content(%r{ESTABLISHED RELATED})
end
it do
- is_expected.not_to contain_concat__fragment('INPUT2-footer'). \
+ is_expected.not_to contain_concat__fragment('filter-INPUT2-footer'). \
with_content(%r{LOG log-prefix 'INPUT2: ';})
end
end
+
+ context 'with policy setting for custom chain' do
+ let :params do
+ {
+ chain: 'INPUT2',
+ policy: 'DROP',
+ disable_conntrack: true,
+ log_dropped_packets: false
+ }
+ end
+
+ it { is_expected.to compile.and_raise_error(%r{Can only set a default policy for builtin chains}) }
+ end
end
end
end
diff --git a/spec/defines/rule_spec.rb b/spec/defines/rule_spec.rb
index 1bec758..ef20e17 100644
--- a/spec/defines/rule_spec.rb
+++ b/spec/defines/rule_spec.rb
@@ -11,7 +11,37 @@ describe 'ferm::rule', type: :define do
'include ferm'
end
- context 'without a specific interface' do
+ context 'without policy or action' do
+ let(:title) { 'filter-ssh' }
+ let :params do
+ {
+ chain: 'INPUT',
+ proto: 'tcp',
+ dport: '22',
+ saddr: '127.0.0.1'
+ }
+ end
+
+ it { is_expected.to compile.and_raise_error(%r{Exactly one of "action" or the deprecated "policy" param is required}) }
+ end
+
+ context 'with both policy and action' do
+ let(:title) { 'filter-ssh' }
+ let :params do
+ {
+ chain: 'INPUT',
+ policy: 'ACCEPT',
+ action: 'ACCEPT',
+ proto: 'tcp',
+ dport: '22',
+ saddr: '127.0.0.1'
+ }
+ end
+
+ it { is_expected.to compile.and_raise_error(%r{Cannot specify both policy and action}) }
+ end
+
+ context 'without a specific interface using legacy policy param' do
let(:title) { 'filter-ssh' }
let :params do
{
@@ -26,12 +56,32 @@ describe 'ferm::rule', type: :define do
it { is_expected.to compile.with_all_deps }
it { is_expected.to contain_concat__fragment('INPUT-filter-ssh').with_content("mod comment comment 'filter-ssh' proto tcp dport 22 saddr @ipfilter((127.0.0.1)) ACCEPT;\n") }
end
+
+ context 'without a specific interface' do
+ let(:title) { 'filter-ssh' }
+ let :params do
+ {
+ chain: 'INPUT',
+ action: 'ACCEPT',
+ proto: 'tcp',
+ dport: '22',
+ saddr: '127.0.0.1'
+ }
+ end
+
+ it { is_expected.to compile.with_all_deps }
+ it { is_expected.to contain_concat__fragment('INPUT-filter-ssh').with_content("mod comment comment 'filter-ssh' proto tcp dport 22 saddr @ipfilter((127.0.0.1)) ACCEPT;\n") }
+ it { is_expected.to contain_concat__fragment('filter-INPUT-config-include') }
+ it { is_expected.to contain_concat__fragment('filter-FORWARD-config-include') }
+ it { is_expected.to contain_concat__fragment('filter-OUTPUT-config-include') }
+ end
+
context 'with a specific interface' do
let(:title) { 'filter-ssh' }
let :params do
{
chain: 'INPUT',
- policy: 'ACCEPT',
+ action: 'ACCEPT',
proto: 'tcp',
dport: '22',
saddr: '127.0.0.1',
@@ -44,12 +94,13 @@ describe 'ferm::rule', type: :define do
it { is_expected.to contain_concat__fragment('INPUT-eth0-aaa').with_content("interface eth0 {\n") }
it { is_expected.to contain_concat__fragment('INPUT-eth0-zzz').with_content("}\n") }
end
+
context 'with a specific interface using array for daddr' do
let(:title) { 'filter-ssh' }
let :params do
{
chain: 'INPUT',
- policy: 'ACCEPT',
+ action: 'ACCEPT',
proto: 'tcp',
dport: '22',
daddr: ['127.0.0.1', '123.123.123.123', ['10.0.0.1', '10.0.0.2']],
@@ -62,6 +113,68 @@ describe 'ferm::rule', type: :define do
it { is_expected.to contain_concat__fragment('INPUT-eth0-aaa').with_content("interface eth0 {\n") }
it { is_expected.to contain_concat__fragment('INPUT-eth0-zzz').with_content("}\n") }
end
+
+ context 'with jumping to custom chains' do
+ # create custom chain
+ let(:pre_condition) do
+ 'include ferm ;
+ ferm::chain{"check-ssh":
+ chain => "SSH",
+ disable_conntrack => true,
+ log_dropped_packets => false,
+ }'
+ end
+ let(:title) { 'filter-ssh' }
+ let :params do
+ {
+ chain: 'INPUT',
+ action: 'SSH',
+ proto: 'tcp',
+ dport: '22'
+ }
+ end
+
+ it { is_expected.to compile.with_all_deps }
+ it { is_expected.to contain_concat__fragment('filter-SSH-policy') }
+ it do
+ is_expected.to contain_concat__fragment('INPUT-filter-ssh').\
+ with_content("mod comment comment 'filter-ssh' proto tcp dport 22 jump SSH;\n"). \
+ that_requires('Ferm::Chain[check-ssh]')
+ end
+ it { is_expected.to contain_concat__fragment('filter-INPUT-config-include') }
+ if facts[:os]['release']['major'].to_i == 10
+ it { is_expected.to contain_concat('/etc/ferm/ferm.d/chains/filter-SSH.conf') }
+ else
+ it { is_expected.to contain_concat('/etc/ferm.d/chains/filter-SSH.conf') }
+ end
+ end
+
+ context 'definining rules in custom chains' do
+ # create custom chain
+ let(:pre_condition) do
+ 'include ferm ;
+ ferm::chain{"check-ssh":
+ chain => "SSH",
+ disable_conntrack => true,
+ log_dropped_packets => false,
+ }'
+ end
+ let(:title) { 'allow-ssh-localhost' }
+ let :params do
+ {
+ chain: 'SSH',
+ action: 'ACCEPT',
+ proto: 'tcp',
+ dport: '22',
+ saddr: '127.0.0.1'
+ }
+ end
+
+ it { is_expected.to compile.with_all_deps }
+ it { is_expected.to contain_concat__fragment('SSH-allow-ssh-localhost').with_content("mod comment comment 'allow-ssh-localhost' proto tcp dport 22 saddr @ipfilter((127.0.0.1)) ACCEPT;\n") }
+ it { is_expected.to contain_concat__fragment('filter-INPUT-config-include') }
+ it { is_expected.to contain_concat__fragment('filter-SSH-config-include') }
+ end
end
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index f16fb15..96f14d5 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -10,6 +10,11 @@ require 'rspec-puppet-facts'
require 'bundler'
include RspecPuppetFacts
+if ENV['DEBUG']
+ Puppet::Util::Log.level = :debug
+ Puppet::Util::Log.newdestination(:console)
+end
+
if File.exist?(File.join(__dir__, 'default_module_facts.yml'))
facts = YAML.load(File.read(File.join(__dir__, 'default_module_facts.yml')))
if facts
diff --git a/templates/ferm-table-chain-config-include.epp b/templates/ferm-table-chain-config-include.epp
new file mode 100644
index 0000000..722d3e7
--- /dev/null
+++ b/templates/ferm-table-chain-config-include.epp
@@ -0,0 +1,14 @@
+<%- | String[1] $ip,
+Ferm::Tables $table,
+String[1] $chain,
+Stdlib::Absolutepath $filename,
+| -%>
+
+domain (<%= $ip %>) table <%= $table %> {
+ chain <%= $chain %> {
+ <%- if $table == 'filter' and $chain == 'INPUT' { -%>
+ interface lo ACCEPT;
+ <%- } -%>
+ @include '<%= $filename %>';
+ }
+}
diff --git a/templates/ferm.conf.epp b/templates/ferm.conf.epp
index 0245a70..3b1a211 100644
--- a/templates/ferm.conf.epp
+++ b/templates/ferm.conf.epp
@@ -2,7 +2,6 @@
Stdlib::Absolutepath $configdirectory,
Hash[String[1], Array[String[1]]] $preserve_chains_in_tables,
| -%>
-# End custom section
<%- $preserve_chains_in_tables.each |$table, $chains| { -%>
domain (<%= $ip %>) table <%= $table %> {
@@ -11,18 +10,3 @@ domain (<%= $ip %>) table <%= $table %> {
<%- } -%>
}
<%- } -%>
-
-domain (<%= $ip %>) table filter {
- chain INPUT {
- interface lo ACCEPT;
- @include '<%= $configdirectory %>/chains/INPUT.conf';
- }
-
- chain OUTPUT {
- @include '<%= $configdirectory %>/chains/OUTPUT.conf';
- }
-
- chain FORWARD {
- @include '<%= $configdirectory %>/chains/FORWARD.conf';
- }
-}
diff --git a/templates/ferm_chain_header.conf.epp b/templates/ferm_chain_header.conf.epp
index f94b18d..938958b 100644
--- a/templates/ferm_chain_header.conf.epp
+++ b/templates/ferm_chain_header.conf.epp
@@ -1,12 +1,14 @@
-<%- | Ferm::Policies $policy,
+<%- | Optional[Ferm::Policies] $policy,
Boolean $disable_conntrack,
| -%>
# THIS FILE IS MANAGED BY PUPPET
+<%- if $policy { -%>
# Default policy for this chain
policy <%= $policy %>;
+<%- } -%>
<% unless $disable_conntrack { -%>
# connection tracking
-mod state state INVALID DROP;
-mod state state (ESTABLISHED RELATED) ACCEPT;
+mod conntrack ctstate (ESTABLISHED RELATED) ACCEPT;
+mod conntrack ctstate INVALID DROP;
<% } -%>
diff --git a/templates/ferm_header.conf.epp b/templates/ferm_header.conf.epp
index e1a1f1a..a29106c 100644
--- a/templates/ferm_header.conf.epp
+++ b/templates/ferm_header.conf.epp
@@ -5,5 +5,3 @@
# get all ip definitions
@include '<%= $configdirectory %>/definitions/';
-
-# Begin custom section
diff --git a/types/actions.pp b/types/actions.pp
new file mode 100644
index 0000000..49bfd2c
--- /dev/null
+++ b/types/actions.pp
@@ -0,0 +1,6 @@
+# @summary a list of allowed actions for a rule
+# As you can also *jump* to other chains, each chain-name is also a valid action/target
+type Ferm::Actions = Variant[
+ Enum['RETURN', 'ACCEPT', 'DROP', 'REJECT', 'NOTRACK', 'LOG', 'MARK', 'DNAT', 'SNAT', 'MASQUERADE', 'REDIRECT'],
+ String[1],
+]
diff --git a/types/policies.pp b/types/policies.pp
index 03be6ce..0963095 100644
--- a/types/policies.pp
+++ b/types/policies.pp
@@ -1,2 +1,2 @@
-# @summary a list of allowed default policies for a chain
-type Ferm::Policies = Enum['ACCEPT','DROP', 'REJECT']
+# @summary a list of allowed policies for a chain
+type Ferm::Policies = Enum['ACCEPT','DROP']
diff --git a/types/tables.pp b/types/tables.pp
new file mode 100644
index 0000000..89edde7
--- /dev/null
+++ b/types/tables.pp
@@ -0,0 +1,2 @@
+# @summary a list of available tables
+type Ferm::Tables = Enum['raw', 'mangle', 'nat', 'filter']