diff options
| author | Tim Meusel <tim@bastelfreak.de> | 2019-09-11 13:29:12 +0200 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-09-11 13:29:12 +0200 | 
| commit | 6d96e030be0db4a916dd6a9bd0b25570d359e634 (patch) | |
| tree | 523f7a96c5b2640dbf2dd45cd89d931e12ceff9d | |
| parent | 81748ba786c6a55c4575a400c08de99716da8fbb (diff) | |
| parent | 882a45498ddefdfc83ff5b19da723fd0be3acdec (diff) | |
| download | puppet-ferm-6d96e030be0db4a916dd6a9bd0b25570d359e634.tar.gz puppet-ferm-6d96e030be0db4a916dd6a9bd0b25570d359e634.tar.bz2  | |
Merge pull request #58 from voxpupuli/multi-table-support
add ability to configure rules in tables other than the default "filter" table
| -rw-r--r-- | REFERENCE.md | 139 | ||||
| -rw-r--r-- | manifests/chain.pp | 71 | ||||
| -rw-r--r-- | manifests/config.pp | 29 | ||||
| -rw-r--r-- | manifests/init.pp | 9 | ||||
| -rw-r--r-- | manifests/rule.pp | 76 | ||||
| -rw-r--r-- | spec/acceptance/ferm_spec.rb | 92 | ||||
| -rw-r--r-- | spec/classes/ferm_spec.rb | 61 | ||||
| -rw-r--r-- | spec/defines/chain_spec.rb | 30 | ||||
| -rw-r--r-- | spec/defines/rule_spec.rb | 119 | ||||
| -rw-r--r-- | spec/spec_helper.rb | 5 | ||||
| -rw-r--r-- | templates/ferm-table-chain-config-include.epp | 14 | ||||
| -rw-r--r-- | templates/ferm.conf.epp | 16 | ||||
| -rw-r--r-- | templates/ferm_chain_header.conf.epp | 8 | ||||
| -rw-r--r-- | templates/ferm_header.conf.epp | 2 | ||||
| -rw-r--r-- | types/actions.pp | 6 | ||||
| -rw-r--r-- | types/policies.pp | 4 | ||||
| -rw-r--r-- | types/tables.pp | 2 | 
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']  | 
