Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New parameter purge_unmanaged_rules to reload nftables if configuration does not match reality #253

Merged
merged 12 commits into from
Aug 4, 2024
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -270,6 +272,25 @@ The name of the 'nat' table.

Default value: `'nat'`

##### <a name="-nftables--purge_unmanaged_rules"></a>`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`

##### <a name="-nftables--inmem_rules_hash_file"></a>`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'`

##### <a name="-nftables--sets"></a>`sets`

Data type: `Hash`
Expand Down
31 changes: 31 additions & 0 deletions manifests/init.pp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand Down Expand Up @@ -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] %<chain>s %<comment>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',
Expand Down Expand Up @@ -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',
Expand Down
37 changes: 37 additions & 0 deletions spec/classes/nftables_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Loading