diff --git a/README.md b/README.md index a77aa8a7..8c7ae1f1 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,14 @@ Initially puppet deploys all configuration to If and only if successful the configuration will be copied to the real locations before the service is reloaded. +## Un-managed rules + +By default, rules added manually by the administrator to the in-memory +ruleset will be left untouched. However, +`nftables::purge_unmanaged_rules` can be set to `true` to revert this +behaviour and force a reload of the ruleset during the Puppet run if +non-managed changes are detected. + ## Basic types ### nftables::config diff --git a/REFERENCE.md b/REFERENCE.md index 1497e7ea..750dd976 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -162,6 +162,8 @@ The following parameters are available in the `nftables` class: * [`inet_filter`](#-nftables--inet_filter) * [`nat`](#-nftables--nat) * [`nat_table_name`](#-nftables--nat_table_name) +* [`purge_unmanaged_rules`](#-nftables--purge_unmanaged_rules) +* [`inmem_rules_hash_file`](#-nftables--inmem_rules_hash_file) * [`sets`](#-nftables--sets) * [`log_prefix`](#-nftables--log_prefix) * [`log_discarded`](#-nftables--log_discarded) @@ -270,6 +272,25 @@ The name of the 'nat' table. Default value: `'nat'` +##### `purge_unmanaged_rules` + +Data type: `Boolean` + +Prohibits in-memory rules that are not declared in Puppet +code. Setting this to true activates a check that reloads nftables +if the rules in memory have been modified without Puppet. + +Default value: `false` + +##### `inmem_rules_hash_file` + +Data type: `Stdlib::Unixpath` + +The name of the file where the hash of the in-memory rules +will be stored. + +Default value: `'/var/tmp/puppet-nft-memhash'` + ##### `sets` Data type: `Hash` diff --git a/manifests/init.pp b/manifests/init.pp index 1b9ea057..f1255127 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -46,6 +46,15 @@ # @param nat_table_name # The name of the 'nat' table. # +# @param purge_unmanaged_rules +# Prohibits in-memory rules that are not declared in Puppet +# code. Setting this to true activates a check that reloads nftables +# if the rules in memory have been modified without Puppet. +# +# @param inmem_rules_hash_file +# The name of the file where the hash of the in-memory rules +# will be stored. +# # @param sets # Allows sourcing set definitions directly from Hiera. # @@ -134,10 +143,12 @@ Boolean $fwd_drop_invalid = $fwd_conntrack, Boolean $inet_filter = true, Boolean $nat = true, + Boolean $purge_unmanaged_rules = false, Hash $rules = {}, Hash $sets = {}, String $log_prefix = '[nftables] %s %s', String[1] $nat_table_name = 'nat', + Stdlib::Unixpath $inmem_rules_hash_file = '/var/tmp/puppet-nft-memhash', Boolean $log_discarded = true, Variant[Boolean[false], String] $log_limit = '3/minute burst 5 packets', Variant[Boolean[false], Pattern[/icmp(v6|x)? type .+|tcp reset/]] $reject_with = 'icmpx type port-unreachable', @@ -221,6 +232,26 @@ restart => 'PATH=/usr/bin:/bin systemctl reload nftables', } + if $purge_unmanaged_rules { + # Reload nftables ruleset from disk if running state not match last service change hash, or is absent (-s required to ignore counters) + exec { 'nftables_memory_state_check': + command => ['echo', 'reloading_nftables'], + path => $facts['path'], + provider => shell, + unless => ["test -s ${inmem_rules_hash_file} -a \"$(nft -s list ruleset | sha1sum)\" = \"$(cat ${inmem_rules_hash_file})\""], + notify => Service['nftables'], + } + + # Generate nftables hash upon changes to the nftables service + exec { 'nftables_generate_hash': + command => ["nft -s list ruleset | sha1sum > ${inmem_rules_hash_file}"], + path => $facts['path'], + provider => shell, + subscribe => Service['nftables'], + refreshonly => true, + } + } + systemd::dropin_file { 'puppet_nft.conf': ensure => present, unit => 'nftables.service', diff --git a/spec/classes/nftables_spec.rb b/spec/classes/nftables_spec.rb index 36a68438..b4830e91 100644 --- a/spec/classes/nftables_spec.rb +++ b/spec/classes/nftables_spec.rb @@ -130,6 +130,18 @@ ) } + it { + expect(subject).not_to contain_exec('nftables_memory_state_check') + } + + it { + expect(subject).not_to contain_exec('nftables_generate_hash') + } + + it { + expect(subject).not_to contain_file('/var/tmp/puppet-nft-memhash') + } + it { expect(subject).to contain_exec('nft validate').with( refreshonly: true, @@ -298,6 +310,31 @@ it { is_expected.to have_nftables__set_resource_count(0) } end + context 'when purging unmanaged rules' do + let(:params) do + { + 'purge_unmanaged_rules' => true, + 'inmem_rules_hash_file' => '/foo/bar', + } + end + + it { + is_expected.to contain_exec('nftables_memory_state_check').with( + command: %w[echo reloading_nftables], + notify: 'Service[nftables]', + unless: ['test -s /foo/bar -a "$(nft -s list ruleset | sha1sum)" = "$(cat /foo/bar)"'] + ) + } + + it { + is_expected.to contain_exec('nftables_generate_hash').with( + command: ['nft -s list ruleset | sha1sum > /foo/bar'], + subscribe: 'Service[nftables]', + refreshonly: true + ) + } + end + %w[ip ip6 inet arp bridge netdev].each do |family| context "with noflush_tables parameter set to valid family #{family}" do let(:params) do