diff --git a/README.md b/README.md index 27bde5d8..15cac2c3 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,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/files/systemd/nft-hash-ruleset.sh b/files/systemd/nft-hash-ruleset.sh new file mode 100644 index 00000000..a41d51b9 --- /dev/null +++ b/files/systemd/nft-hash-ruleset.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# This file is Puppet managed + +umask 0377 +/sbin/nft -s list ruleset | /usr/bin/sha1sum > $1 diff --git a/files/systemd/puppet_nft.conf b/files/systemd/puppet_nft.conf deleted file mode 100644 index e77d8381..00000000 --- a/files/systemd/puppet_nft.conf +++ /dev/null @@ -1,7 +0,0 @@ -# Puppet Deployed -[Service] -ExecStart= -ExecStart=/sbin/nft -I /etc/nftables/puppet -f /etc/sysconfig/nftables.conf -ExecReload= -ExecReload=/sbin/nft -I /etc/nftables/puppet -f /etc/sysconfig/nftables.conf - diff --git a/manifests/init.pp b/manifests/init.pp index 19a21129..4fbe1842 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -43,9 +43,18 @@ # @param nat # Add default tables and chains to process NAT traffic. # +# @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 outwith Puppet. +# # @param nat_table_name # The name of the 'nat' table. # +# @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. # @@ -99,10 +108,12 @@ Boolean $fwd_conntrack = false, 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 = '/run/puppet-nft-memhash', 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', Variant[Boolean[false], Enum['mask']] $firewalld_enable = 'mask', @@ -164,10 +175,33 @@ restart => '/usr/bin/systemctl reload nftables', } + if $purge_unmanaged_rules { + exec { 'Reload nftables if there are un-managed rules': + command => '/usr/bin/systemctl reload nftables', + refreshonly => false, + unless => "/usr/bin/test -s ${inmem_rules_hash_file} -a \"$(nft -s list ruleset | sha1sum)\" = \"$(cat ${inmem_rules_hash_file})\"", + require => Service['nftables'], + } + + file { '/usr/local/sbin/nft-hash-ruleset.sh' : + ensure => file, + mode => '0755', + content => file('nftables/systemd/nft-hash-ruleset.sh'), + before => Systemd::Dropin_file['puppet_nft.conf'], + } + } else { + file { $inmem_rules_hash_file: + ensure => absent, + } + } + systemd::dropin_file { 'puppet_nft.conf': ensure => present, unit => 'nftables.service', - content => file('nftables/systemd/puppet_nft.conf'), + content => epp('nftables/systemd/puppet_nft.conf.epp', { + 'purge_unmanaged' => $purge_unmanaged_rules, + 'hash_file' => $inmem_rules_hash_file, + }), notify => Service['nftables'], } diff --git a/spec/classes/nftables_spec.rb b/spec/classes/nftables_spec.rb index 4b1edd02..7b3038f1 100644 --- a/spec/classes/nftables_spec.rb +++ b/spec/classes/nftables_spec.rb @@ -77,6 +77,12 @@ expect(subject).to contain_systemd__dropin_file('puppet_nft.conf').with( content: %r{^ExecReload=/sbin/nft -I /etc/nftables/puppet -f /etc/sysconfig/nftables.conf$} ) + is_expected.not_to contain_systemd__dropin_file('puppet_nft.conf').with( + content: %r{^ExecReload=.*nft-hash-ruleset\.sh.*$} + ) + is_expected.not_to contain_systemd__dropin_file('puppet_nft.conf').with( + content: %r{^ExecStartPost.*$} + ) } it { @@ -94,6 +100,9 @@ it { is_expected.to contain_class('nftables::rules::out::chrony') } it { is_expected.not_to contain_class('nftables::rules::out::all') } it { is_expected.not_to contain_nftables__rule('default_out-all') } + it { is_expected.not_to contain_exec('Reload nftables if there are un-managed rules') } + it { is_expected.to contain_file('/run/puppet-nft-memhash') } + it { is_expected.not_to contain_file('/usr/local/sbin/nft-hash-ruleset.sh') } context 'with out_all set true' do let(:params) do @@ -207,6 +216,35 @@ it { is_expected.to have_nftables__set_resource_count(0) } end + context 'with not allowing un-managed changes' do + let(:params) do + { + 'purge_unmanaged_rules' => true, + 'inmem_rules_hash_file' => '/foo/bar', + } + end + + it { is_expected.not_to contain_file('/foo/bar') } + it { is_expected.to contain_file('/usr/local/sbin/nft-hash-ruleset.sh') } + it { + is_expected.to contain_exec('Reload nftables if there are un-managed rules').with( + command: '/usr/bin/systemctl reload nftables', + refreshonly: false, + unless: '/usr/bin/test -s /foo/bar -a "$(nft -s list ruleset | sha1sum)" = "$(cat /foo/bar)"' + ) + } + it { + is_expected.to contain_systemd__dropin_file('puppet_nft.conf').with( + content: %r{^ExecReload=/bin/bash /usr/local/sbin/nft-hash-ruleset\.sh /foo/bar$} + ) + } + it { + is_expected.to contain_systemd__dropin_file('puppet_nft.conf').with( + content: %r{^ExecStartPost=/bin/bash /usr/local/sbin/nft-hash-ruleset\.sh /foo/bar$} + ) + } + end + context 'with with noflush_tables parameter' do let(:params) do { diff --git a/templates/systemd/puppet_nft.conf.epp b/templates/systemd/puppet_nft.conf.epp new file mode 100644 index 00000000..a69b5bc6 --- /dev/null +++ b/templates/systemd/puppet_nft.conf.epp @@ -0,0 +1,16 @@ +<%- | + Boolean $purge_unmanaged, + Stdlib::Unixpath $hash_file, +|-%> +# Puppet Deployed +[Service] +ExecStart= +ExecStart=/sbin/nft -I /etc/nftables/puppet -f /etc/sysconfig/nftables.conf +<% if $purge_unmanaged { -%> +ExecStartPost=/bin/bash /usr/local/sbin/nft-hash-ruleset.sh <%= $hash_file %> +<% } -%> +ExecReload= +ExecReload=/sbin/nft -I /etc/nftables/puppet -f /etc/sysconfig/nftables.conf +<% if $purge_unmanaged { -%> +ExecReload=/bin/bash /usr/local/sbin/nft-hash-ruleset.sh <%= $hash_file -%> +<% } -%>