From d4b8909eab6194da389b121e46137da7618eb45c Mon Sep 17 00:00:00 2001 From: Tim Meusel Date: Mon, 30 Sep 2019 14:51:12 +0200 Subject: implement ipset support --- README.md | 18 ++++++++-- REFERENCE.md | 76 ++++++++++++++++++++++++++++++++++++++++++ manifests/chain.pp | 6 +++- manifests/ipset.pp | 62 ++++++++++++++++++++++++++++++++++ spec/defines/ipset_spec.rb | 29 ++++++++++++++++ templates/ferm-chain-ipset.epp | 13 ++++++++ 6 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 manifests/ipset.pp create mode 100644 spec/defines/ipset_spec.rb create mode 100644 templates/ferm-chain-ipset.epp diff --git a/README.md b/README.md index 93edb62..01217bd 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,8 @@ You can collect them like this: Ferm::Rule <<| tag == 'allow_kafka_server2server' |>> ``` -You can also define rules in Hiera. Make sure to use `alias()` as interpolation function, because `hiera()` will always return a string. +You can also define rules in Hiera. Make sure to use `alias()` as interpolation +function, because `hiera()` will always return a string. ```yaml --- @@ -94,6 +95,20 @@ defined hashes and hand them over to the class. The main class will create rules for all of them. It also collects all exported resources that are tagged with the FQDN of a box. +It's also possible to match against [ipsets](http://ipset.netfilter.org/). This +allows to easily match against a huge amount of IP addresses or network ranges. +You can use this as follows: + +```puppet +ferm::ipset { 'INPUT': + sets => { + 'office' => 'ACCPET', + 'internet' => 'DROP', + } +} +``` + +please see the [references](#reference) section for more examples. ## Examples @@ -131,7 +146,6 @@ The second rule will disable connection tracking for all other traffic coming in This will prevent your conntrack table from overflowing, tracking only the relevant connections and allowing you to use a stateful ruleset. - ## Reference All parameters are documented within the classes. We generate markdown diff --git a/REFERENCE.md b/REFERENCE.md index 019c5a4..7e7d518 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -18,6 +18,7 @@ _Private Classes_ **Defined types** * [`ferm::chain`](#fermchain): This defined resource manages ferm/iptables chains +* [`ferm::ipset`](#fermipset): a defined resource that can match for ipsets at the top of a chain. This is a per-chain resource. You cannot mix IPv4 and IPv6 sets. * [`ferm::rule`](#fermrule): This defined resource manages a single rule in a specific chain **Data types** @@ -273,6 +274,81 @@ Default value: $ferm::ip_versions Default value: $ferm::ip_versions +### ferm::ipset + +a defined resource that can match for ipsets at the top of a chain. This is a per-chain resource. You cannot mix IPv4 and IPv6 sets. + +* **See also** +http://ferm.foo-projects.org/download/2.1/ferm.html#set + +#### Examples + +##### + +```puppet +ferm::ipset { 'CONSUL': + sets => { + 'internet' => 'ACCEPT' + }, +} +``` + +##### create to matches for IPv6, both at the end of the `INPUT` chain. Explicitly mention the `filter` table. + +```puppet +ferm::ipset { 'INPUT': + prepend_to_chain => false, + table => 'filter', + ip_version => 'ip6', + sets => { + 'testset01' => 'ACCEPT', + 'anothertestset' => 'DROP' + }, +} +``` + +#### Parameters + +The following parameters are available in the `ferm::ipset` defined type. + +##### `chain` + +Data type: `String[1]` + +name of the chain we want to apply those rules to. The name of the defined resource will be used as default value for this. + +Default value: $name + +##### `table` + +Data type: `Ferm::Tables` + +name of the table where we want to apply this. Defaults to `filter` because that's the most common usecase. + +Default value: 'filter' + +##### `ip_version` + +Data type: `Enum['ip','ip6']` + +sadly, ip sets are version specific. You cannot mix IPv4 and IPv6 addresses. Because of this you need to provide the version. + +Default value: 'ip' + +##### `sets` + +Data type: `Hash[String[1], Ferm::Actions]` + +A hash with multiple sets. For each hash you can provide an action like `DROP` or `ACCEPT`. + +##### `prepend_to_chain` + +Data type: `Boolean` + + + +Default value: `true` + ### ferm::rule This defined resource manages a single rule in a specific chain diff --git a/manifests/chain.pp b/manifests/chain.pp index 10cc9c1..1be7e83 100644 --- a/manifests/chain.pp +++ b/manifests/chain.pp @@ -73,6 +73,10 @@ define ferm::chain ( } # make sure the generated snippet is actually included + # the ordering here is hacked. We might end up with multiple blocks for the same filter+chain. + # This happens if we add ipset matches. We suffix this ordering with `bbb`. This allows us to + # insert ipset matches before other rules by adding `-aaa` or + # insert them at the end by ordering them with `-ccc`. concat::fragment{"${table}-${chain}-config-include": target => $ferm::configfile, content => epp( @@ -83,7 +87,7 @@ define ferm::chain ( 'filename' => $filename, } ), - order => "${table}-${chain}", + order => "${table}-${chain}-bbb", require => Concat[$filename], } } diff --git a/manifests/ipset.pp b/manifests/ipset.pp new file mode 100644 index 0000000..fab7894 --- /dev/null +++ b/manifests/ipset.pp @@ -0,0 +1,62 @@ +# +# @summary a defined resource that can match for ipsets at the top of a chain. This is a per-chain resource. You cannot mix IPv4 and IPv6 sets. +# +# @see http://ferm.foo-projects.org/download/2.1/ferm.html#set +# +# @example +# ferm::ipset { 'CONSUL': +# sets => { +# 'internet' => 'ACCEPT' +# }, +# } +# +# @example create to matches for IPv6, both at the end of the `INPUT` chain. Explicitly mention the `filter` table. +# ferm::ipset { 'INPUT': +# prepend_to_chain => false, +# table => 'filter', +# ip_version => 'ip6', +# sets => { +# 'testset01' => 'ACCEPT', +# 'anothertestset' => 'DROP' +# }, +# } +# +# @param chain +# name of the chain we want to apply those rules to. The name of the defined resource will be used as default value for this. +# +# @param table +# name of the table where we want to apply this. Defaults to `filter` because that's the most common usecase. +# +# @param ip_version +# sadly, ip sets are version specific. You cannot mix IPv4 and IPv6 addresses. Because of this you need to provide the version. +# +# @param sets +# A hash with multiple sets. For each hash you can provide an action like `DROP` or `ACCEPT`. +# +define ferm::ipset ( + Hash[String[1], Ferm::Actions] $sets, + String[1] $chain = $name, + Ferm::Tables $table = 'filter', + Enum['ip','ip6'] $ip_version = 'ip', + Boolean $prepend_to_chain = true, +) { + + $suffix = $prepend_to_chain ? { + true => 'aaa', + false => 'ccc', + } + + # make sure the generated snippet is actually included + concat::fragment{"${table}-${chain}-ipset": + target => $ferm::configfile, + content => epp( + "${module_name}/ferm-chain-ipset.epp", { + 'ip' => $ip_version, + 'table' => $table, + 'chain' => $chain, + 'sets' => $sets, + } + ), + order => "${table}-${chain}-${suffix}", + } +} diff --git a/spec/defines/ipset_spec.rb b/spec/defines/ipset_spec.rb new file mode 100644 index 0000000..050e5ef --- /dev/null +++ b/spec/defines/ipset_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe 'ferm::ipset', type: :define do + on_supported_os.each do |os, facts| + context "on #{os} " do + let :facts do + facts + end + let(:title) { 'INPUT' } + + let :pre_condition do + 'include ferm' + end + + context 'default params creates INPUT2 chain' do + let :params do + { + sets: { + office: 'ACCEPT', + internet: 'DROP' + } + } + end + + it { is_expected.to compile.with_all_deps } + end + end + end +end diff --git a/templates/ferm-chain-ipset.epp b/templates/ferm-chain-ipset.epp new file mode 100644 index 0000000..79aeb5c --- /dev/null +++ b/templates/ferm-chain-ipset.epp @@ -0,0 +1,13 @@ +<%- | String[1] $ip, +Ferm::Tables $table, +String[1] $chain, +Hash[String[1], Ferm::Actions] $sets, +| -%> + +domain (<%= $ip %>) table <%= $table %> { + chain <%= $chain %> { + <%- $sets.each |$ipset, $action| { -%> + mod set set <%= $ipset %> src <%= $action %>; + <%- } -%> + } +} -- cgit v1.2.3