diff --git a/hieradata/common.yaml b/hieradata/common.yaml index 02a6d8b..e29de34 100644 --- a/hieradata/common.yaml +++ b/hieradata/common.yaml @@ -178,6 +178,8 @@ lookup_options: convert_to: Sensitive stalwart::fallback_admin_password: convert_to: Sensitive + wireguard::interfaces: + convert_to: Sensitive facts_path: '/opt/puppetlabs/facter/facts.d' diff --git a/modules/wireguard/manifests/init.pp b/modules/wireguard/manifests/init.pp new file mode 100644 index 0000000..a4301b6 --- /dev/null +++ b/modules/wireguard/manifests/init.pp @@ -0,0 +1,44 @@ +# manage wireguard interfaces via wg-quick +class wireguard ( + Boolean $manage_package = true, + String $package_name = 'wireguard-tools', + Variant[Hash, Sensitive[Hash]] $interfaces = {}, +) { + + if $manage_package { + package { $package_name: + ensure => installed, + before => File['/etc/wireguard'], + } + } + + file { '/etc/wireguard': + ensure => directory, + owner => 'root', + group => 'root', + mode => '0700', + } + + # hiera hands eyaml secrets over as plain strings inside the (Sensitive) hash; re-wrap them per resource + $raw = $interfaces ? { + Sensitive => $interfaces.unwrap, + default => $interfaces, + } + + $raw.each |String $iface, Hash $data| { + $peers = $data.get('peers', []).map |Hash $peer| { + $peer['preshared_key'] =~ String ? { + true => $peer + { 'preshared_key' => Sensitive($peer['preshared_key']) }, + default => $peer, + } + } + $private_key = $data['private_key'] =~ String ? { + true => Sensitive($data['private_key']), + default => $data['private_key'], + } + + wireguard::interface { $iface: + * => $data + { 'peers' => $peers, 'private_key' => $private_key }, + } + } +} diff --git a/modules/wireguard/manifests/interface.pp b/modules/wireguard/manifests/interface.pp new file mode 100644 index 0000000..7428941 --- /dev/null +++ b/modules/wireguard/manifests/interface.pp @@ -0,0 +1,63 @@ +# manage one wg-quick interface; without private_key, /etc/wireguard/.key is generated once and loaded via PostUp +define wireguard::interface ( + Array[Stdlib::IP::Address] $addresses, + Optional[Stdlib::Port] $listen_port = undef, + Optional[Integer[1280, 9000]] $mtu = undef, + Optional[Sensitive[String[1]]] $private_key = undef, + Array[Struct[{ + public_key => String[1], + allowed_ips => Variant[String[1], Array[String[1], 1]], + preshared_key => Optional[Sensitive[String[1]]], + endpoint => Optional[String[1]], + persistent_keepalive => Optional[Integer[0, 65535]], + }]] $peers = [], +) { + + $conf = "/etc/wireguard/${name}.conf" + $key = $private_key.then |$k| { $k.unwrap } + + if $private_key =~ Undef { + $keyfile = "/etc/wireguard/${name}.key" + + exec { "wireguard_genkey_${name}": + command => "/bin/sh -c 'umask 077; wg genkey > ${keyfile}'", + creates => $keyfile, + path => ['/usr/bin', '/usr/sbin', '/bin', '/sbin'], + require => File['/etc/wireguard'], + } + + file { $keyfile: + ensure => file, + owner => 'root', + group => 'root', + mode => '0600', + require => Exec["wireguard_genkey_${name}"], + before => [File[$conf], Service["wg-quick@${name}"]], + } + } + + file { $conf: + ensure => file, + owner => 'root', + group => 'root', + mode => '0600', + content => Sensitive(template('wireguard/wg.conf.erb')), + show_diff => false, + notify => Exec["wireguard_syncconf_${name}"], + } + + service { "wg-quick@${name}": + ensure => running, + enable => true, + require => File[$conf], + } + + # syncconf applies peer/key changes without bouncing the tunnel; address/mtu changes need a manual restart + exec { "wireguard_syncconf_${name}": + command => "/bin/bash -c 'wg syncconf ${name} <(wg-quick strip ${name})'", + onlyif => "/usr/sbin/ip link show ${name}", + path => ['/usr/bin', '/usr/sbin', '/bin', '/sbin'], + refreshonly => true, + require => Service["wg-quick@${name}"], + } +} diff --git a/modules/wireguard/templates/wg.conf.erb b/modules/wireguard/templates/wg.conf.erb new file mode 100644 index 0000000..01b45bd --- /dev/null +++ b/modules/wireguard/templates/wg.conf.erb @@ -0,0 +1,31 @@ +# THIS FILE IS MANAGED BY PUPPET +[Interface] +<% @addresses.each do |addr| -%> +Address = <%= addr %> +<% end -%> +<% if @listen_port -%> +ListenPort = <%= @listen_port %> +<% end -%> +<% if @mtu -%> +MTU = <%= @mtu %> +<% end -%> +<% if @key -%> +PrivateKey = <%= @key %> +<% else -%> +PostUp = wg set %i private-key /etc/wireguard/%i.key +<% end -%> +<% @peers.each do |peer| -%> + +[Peer] +PublicKey = <%= peer['public_key'] %> +<% if peer['preshared_key'] -%> +PresharedKey = <%= peer['preshared_key'].unwrap %> +<% end -%> +AllowedIPs = <%= Array(peer['allowed_ips']).join(', ') %> +<% if peer['endpoint'] -%> +Endpoint = <%= peer['endpoint'] %> +<% end -%> +<% if peer['persistent_keepalive'] -%> +PersistentKeepalive = <%= peer['persistent_keepalive'] %> +<% end -%> +<% end -%>