Skip to content

Commit

Permalink
Switch from voxpupuli/ferm to voxpupuli/nftables for firewalling
Browse files Browse the repository at this point in the history
  • Loading branch information
bastelfreak committed Dec 25, 2023
1 parent a69ae4a commit 368434c
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 41 deletions.
2 changes: 1 addition & 1 deletion .fixtures.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ fixtures:
repositories:
systemd: https://github.com/voxpupuli/puppet-systemd.git
stdlib: https://github.com/puppetlabs/puppetlabs-stdlib.git
ferm: https://github.com/voxpupuli/puppet-ferm.git
nftables: https://github.com/voxpupuli/puppet-nftables.git
concat: https://github.com/puppetlabs/puppetlabs-concat.git
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ Puppet module to configure wireguard through systemd-networkd configs

## Setup

The module can create firewall rules with [voxpupuli/ferm](https://github.com/voxpupuli/puppet-ferm#puppet-ferm).
The module can create firewall rules with [voxpupuli/nftables](https://github.com/voxpupuli/puppet-nftables?tab=readme-ov-file#nftables-puppet-module).
This is enabled by default but can be disabled by setting the `manage_firewall`
parameter to false in the `wireguard::interface` defined resource. You need to
have the `ferm` class in your catalog to use the feature.
have the `nftables` class in your catalog to use the feature (Version 3.6.0 or
newer).

This module can uses [systemd-networkd](https://www.freedesktop.org/software/systemd/man/systemd-networkd.html) or [wg-quick](https://manpages.debian.org/wg-quick) to
**Version 3 and older of the module use voxpupuli/ferm to manage firewall rules**

This module can use [systemd-networkd](https://www.freedesktop.org/software/systemd/man/systemd-networkd.html) or [wg-quick](https://manpages.debian.org/wg-quick) to
configure tunnels. For the former, you need to have a systemd-networkd
service resource in your catalog. We recommend [voxpupuli/systemd](https://github.com/voxpupuli/puppet-systemd#systemd)
with `manage_networkd` set to true. You do not need to configure your
Expand Down
4 changes: 2 additions & 2 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,9 @@ Default value: `$facts['networking']['primary']`

Data type: `Boolean`

if true, a ferm rule will be created
if true, a nftables rule will be created

Default value: `true`
Default value: `$facts['os']['family'] ? { 'Gentoo' => false, default => true`

##### <a name="-wireguard--interface--dport"></a>`dport`

Expand Down
76 changes: 65 additions & 11 deletions manifests/interface.pp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# @param interface the title of the defined resource, will be used for the wg interface
# @param ensure will ensure that the files for the provider will be present or absent
# @param input_interface ethernet interface where the wireguard packages will enter the system, used for firewall rules
# @param manage_firewall if true, a ferm rule will be created
# @param manage_firewall if true, a nftables rule will be created
# @param dport destination for firewall rules / where our wg instance will listen on. defaults to the last digits from the title
# @param firewall_mark netfilter firewall mark to set on outgoing packages from this wireguard interface
# @param source_addresses an array of ip addresses from where we receive wireguard connections
Expand Down Expand Up @@ -102,7 +102,7 @@
Integer[1024, 65000] $dport = Integer(regsubst($title, '^\D+(\d+)$', '\1')),
Optional[Integer[0, 4294967295]] $firewall_mark = undef,
String[1] $input_interface = $facts['networking']['primary'],
Boolean $manage_firewall = true,
Boolean $manage_firewall = $facts['os']['family'] ? { 'Gentoo' => false, default => true },
Array[Stdlib::IP::Address] $source_addresses = [],
Array[Hash[String,Variant[Stdlib::IP::Address::V4,Stdlib::IP::Address::V6]]] $addresses = [],
Optional[String[1]] $description = undef,
Expand All @@ -128,15 +128,69 @@
true => undef,
default => $destination_addresses,
}
ferm::rule { "allow_wg_${interface}":
action => 'ACCEPT',
chain => 'INPUT',
proto => 'udp',
dport => $dport,
interface => $input_interface,
saddr => $source_addresses,
daddr => $daddr,
notify => Service['systemd-networkd'],
# ToDo: It would be nice if this would be a parameter
if $endpoint =~ /:(\d+)$/ {
$endpoint_port = Integer($1)
} else {
$endpoint_port = undef
}
$source_addresses.each |$index1, $saddr| {
if $saddr =~ Stdlib::IP::Address::V4 {
$daddr.each |$index2, $_daddr| {
if $_daddr =~ Stdlib::IP::Address::V4 {
nftables::simplerule { "allow_in_wg_${interface}-${index1}${index2}":
action => 'accept',
comment => "Allow traffic from interface ${input_interface} from IP ${saddr} for wireguard tunnel ${interface}",
dport => $dport,
sport => $endpoint_port,
proto => 'udp',
daddr => $_daddr,
saddr => $saddr,
iifname => $input_interface,
notify => Service['systemd-networkd'],
}
nftables::simplerule { "allow_out_wg_${interface}-${index1}${index2}":
action => 'accept',
comment => "Allow traffic out interface ${input_interface} to IP ${saddr} for wireguard tunnel ${interface}",
dport => $endpoint_port,
sport => $dport,
proto => 'udp',
daddr => $_daddr,
saddr => $saddr,
oifname => $input_interface,
chain => 'default_out',
notify => Service['systemd-networkd'],
}
}
}
} else {
$daddr.each |$index2, $_daddr| {
if $_daddr =~ Stdlib::IP::Address::V6 {
nftables::simplerule { "allow_in_wg_${interface}-${index1}${index2}":
action => 'accept',
comment => "Allow traffic from interface ${input_interface} from IP ${saddr} for wireguard tunnel ${interface}",
dport => $dport,
proto => 'udp',
daddr => $_daddr,
saddr => $saddr,
iifname => $input_interface,
notify => Service['systemd-networkd'],
}
nftables::simplerule { "allow_out_wg_${interface}-${index1}${index2}":
action => 'accept',
comment => "Allow traffic out interface ${input_interface} to IP ${saddr} for wireguard tunnel ${interface}",
dport => $endpoint_port,
sport => $dport,
proto => 'udp',
daddr => $saddr,
saddr => $_daddr,
oifname => $input_interface,
chain => 'default_out',
notify => Service['systemd-networkd'],
}
}
}
}
}
}

Expand Down
56 changes: 32 additions & 24 deletions spec/defines/interface_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,30 @@
describe 'wireguard::interface', type: :define do
let(:title) { 'as1234' }

on_supported_os.each do |os, facts|
on_supported_os.each do |os, os_facts|
context "on #{os}" do
let :facts do
facts
os_facts
end

context 'with all defaults it wont work' do
context 'with only default values and manage_firewall=true it wont work' do
let :params do
{
manage_firewall: true
}
end

Check failure on line 19 in spec/defines/interface_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / Static validations

RSpec/EmptyLineAfterFinalLet: Add an empty line after the last `let`. (https://rspec.rubystyle.guide/#empty-line-after-let, https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterFinalLet)
it { is_expected.not_to compile }
end

context 'with only default values and manage_firewall=false it wont work' do
let :params do
{
manage_firewall: false
}
end

Check failure on line 28 in spec/defines/interface_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / Static validations

RSpec/EmptyLineAfterFinalLet: Add an empty line after the last `let`. (https://rspec.rubystyle.guide/#empty-line-after-let, https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterFinalLet)
it { is_expected.to compile.with_all_deps }
end

context 'with required params (public_key) and without firewall rules' do
let :params do
{
Expand Down Expand Up @@ -43,7 +57,7 @@
it { is_expected.to contain_file("/etc/systemd/network/#{title}.network").without_content(%r{Address}) }
it { is_expected.to contain_file("/etc/systemd/network/#{title}.network").without_content(%r{Description}) }
it { is_expected.to contain_file("/etc/systemd/network/#{title}.network").without_content(%r{MTUBytes}) }
it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
it { is_expected.to have_nftables__simplerule_resource_count(0) }
end

context 'with required params (public_key) and without firewall rules and with PersistentKeepalive=5' do
Expand Down Expand Up @@ -105,12 +119,8 @@
end

context 'with required params and with firewall rules' do
# we need to set configfile/configdirectory because the ferm module doesn't provide defaults for all OSes we test against
let :pre_condition do
'class{"ferm":
configfile => "/etc/ferm.conf",
configdirectory => "/etc/ferm.d/"
}
'
class {"systemd":
manage_networkd => true
}'
Expand All @@ -135,7 +145,7 @@ class {"systemd":
it { is_expected.to contain_systemd__network("#{title}.netdev") }
it { is_expected.to contain_systemd__network("#{title}.network") }
it { is_expected.to contain_file("/etc/systemd/network/#{title}.network").without_content(%r{Address}) }
it { is_expected.to contain_ferm__rule("allow_wg_#{title}") }
# it { is_expected.to contain_ferm__rule("allow_wg_#{title}") }
end

context 'with required params and without firewall rules and with configured addresses' do
Expand Down Expand Up @@ -163,15 +173,12 @@ class {"systemd":
it { is_expected.to contain_file("/etc/systemd/network/#{title}.network").with_content(%r{Address=192.168.218.87/32}) }
it { is_expected.to contain_file("/etc/systemd/network/#{title}.network").with_content(%r{Peer=172.20.53.97/32}) }
it { is_expected.to contain_file("/etc/systemd/network/#{title}.network").with_content(%r{Address=fe80::ade1/64}) }
it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
# it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
end

context 'with empty destintion_addresses' do
let :pre_condition do
'class{"ferm":
configfile => "/etc/ferm.conf",
configdirectory => "/etc/ferm.d/"
}
'
class {"systemd":
manage_networkd => true
}'
Expand All @@ -180,13 +187,14 @@ class {"systemd":
{
public_key: 'blabla==',
endpoint: 'wireguard.example.com:1234',
manage_firewall: true,
destination_addresses: [],
}
end

it { is_expected.to compile.with_all_deps }

Check failure on line 194 in spec/defines/interface_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / Static validations

RSpec/EmptyLineAfterExample: Add an empty line after `it`. (https://rspec.rubystyle.guide/#empty-lines-around-examples, https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyLineAfterExample)
it { is_expected.to contain_ferm__rule("allow_wg_#{title}").without_daddr }
context 'on non Gentoo', if: os_facts[:os]['family'] do

Check failure on line 195 in spec/defines/interface_spec.rb

View workflow job for this annotation

GitHub Actions / Puppet / Static validations

RSpec/EmptyExampleGroup: Empty example group detected. (https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/EmptyExampleGroup)
# it { is_expected.to contain_ferm__rule("allow_wg_#{title}").without_daddr }
end
end

context 'with description' do
Expand Down Expand Up @@ -297,7 +305,7 @@ class {"systemd":
it { is_expected.to contain_systemd__network("#{title}.network") }
it { is_expected.to contain_file("/etc/systemd/network/#{title}.netdev").with_content(expected_netdev_content) }
it { is_expected.to contain_file("/etc/systemd/network/#{title}.network").with_content(expected_network_content) }
it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
# it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
end

context 'with required params and defined private key and without firewall rules and with configured addresses' do
Expand Down Expand Up @@ -326,7 +334,7 @@ class {"systemd":
it { is_expected.to contain_file("/etc/systemd/network/#{title}.network").with_content(%r{Address=192.168.218.87/32}) }
it { is_expected.to contain_file("/etc/systemd/network/#{title}.network").with_content(%r{Peer=172.20.53.97/32}) }
it { is_expected.to contain_file("/etc/systemd/network/#{title}.network").with_content(%r{Address=fe80::ade1/64}) }
it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
# it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
end

context 'wgquick with required params (public_key) and without firewall rules' do
Expand All @@ -349,7 +357,7 @@ class {"systemd":
it { is_expected.to contain_file("/etc/wireguard/#{title}.pub") }
it { is_expected.to contain_file("/etc/wireguard/#{title}") }
it { is_expected.to contain_file("/etc/wireguard/#{title}.conf") }
it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
# it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
end

context 'with required params and defined private key and without firewall rules and with configured addresses with dns' do
Expand Down Expand Up @@ -379,7 +387,7 @@ class {"systemd":
it { is_expected.to contain_file("/etc/systemd/network/#{title}.network").with_content(%r{DNS=192.168.218.1}) }
it { is_expected.to contain_file("/etc/systemd/network/#{title}.network").with_content(%r{Peer=172.20.53.97/32}) }
it { is_expected.to contain_file("/etc/systemd/network/#{title}.network").with_content(%r{Address=fe80::ade1/64}) }
it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
# it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
end

context 'wgquick with required params (public_key) and an address entry with dns also without firewall rules' do
Expand All @@ -404,7 +412,7 @@ class {"systemd":
it { is_expected.to contain_file("/etc/wireguard/#{title}.conf").with_content(%r{[Interface]}) } # rubocop:disable Lint/DuplicateRegexpCharacterClassElement
it { is_expected.to contain_file("/etc/wireguard/#{title}.conf").with_content(%r{Address=192.168.218.87/32}) }
it { is_expected.to contain_file("/etc/wireguard/#{title}.conf").with_content(%r{DNS=192.168.218.1}) }
it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
# it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
end

context 'wgquick with postup and predown commands and without firewall' do
Expand Down Expand Up @@ -436,7 +444,7 @@ class {"systemd":
it { is_expected.to contain_file("/etc/wireguard/#{title}.conf").with_content(%r{Address=192.168.218.87/32}) }
it { is_expected.to contain_file("/etc/wireguard/#{title}.conf").with_content(%r{PostUp=resolvectl dns %i 10.34.3.1; resolvectl domain %i "~hello"}) }
it { is_expected.to contain_file("/etc/wireguard/#{title}.conf").with_content(%r{PreDown=resolvectl revert %i}) }
it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
# it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
end

context 'wgquick with mtu and without firewall' do
Expand All @@ -462,7 +470,7 @@ class {"systemd":
it { is_expected.to contain_file("/etc/wireguard/#{title}.conf").with_content(%r{[Interface]}) } # rubocop:disable Lint/DuplicateRegexpCharacterClassElement
it { is_expected.to contain_file("/etc/wireguard/#{title}.conf").with_content(%r{Address=192.168.218.87/32}) }
it { is_expected.to contain_file("/etc/wireguard/#{title}.conf").with_content(%r{MTU=1280}) }
it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
# it { is_expected.not_to contain_ferm__rule("allow_wg_#{title}") }
end

context 'with required params and firewall mark and without firewall rules' do
Expand Down

0 comments on commit 368434c

Please sign in to comment.