From 1ce7180d601787f070986d1e3d199330de6492ff Mon Sep 17 00:00:00 2001 From: AveryanAlex Date: Fri, 13 Sep 2024 12:17:34 +0300 Subject: [PATCH] nixos/wg-quick: add AmneziaWG support Co-authored-by: azahi --- .../modules/services/networking/wg-quick.nix | 63 +++++++-- nixos/tests/wireguard/amneziawg-quick.nix | 125 ++++++++++++++++++ nixos/tests/wireguard/default.nix | 1 + 3 files changed, 178 insertions(+), 11 deletions(-) create mode 100644 nixos/tests/wireguard/amneziawg-quick.nix diff --git a/nixos/modules/services/networking/wg-quick.nix b/nixos/modules/services/networking/wg-quick.nix index 369c5a939765..c829e8eb0fc1 100644 --- a/nixos/modules/services/networking/wg-quick.nix +++ b/nixos/modules/services/networking/wg-quick.nix @@ -11,6 +11,15 @@ let interfaceOpts = { ... }: { options = { + type = mkOption { + example = "amneziawg"; + default = "wireguard"; + type = types.enum ["wireguard" "amneziawg"]; + description = '' + The type of the interface. Currently only "wireguard" and "amneziawg" are supported. + ''; + }; + configFile = mkOption { example = "/secret/wg0.conf"; default = null; @@ -151,6 +160,22 @@ let description = "Peers linked to the interface."; type = with types; listOf (submodule peerOpts); }; + + extraOptions = mkOption { + type = with types; attrsOf (oneOf [ str int ]); + default = { }; + example = { + Jc = 5; + Jmin = 10; + Jmax = 42; + S1 = 60; + S2 = 90; + H4 = 12345; + }; + description = '' + Extra options to append to the interface section. Can be used to define AmneziaWG-specific options. + ''; + }; }; }; @@ -227,7 +252,7 @@ let writeScriptFile = name: text: ((pkgs.writeShellScriptBin name text) + "/bin/${name}"); - generatePrivateKeyScript = privateKeyFile: '' + generatePrivateKeyScript = privateKeyFile: wgBin: '' set -e # If the parent dir does not already exist, create it. @@ -236,7 +261,7 @@ let if [ ! -f "${privateKeyFile}" ]; then # Write private key file with atomically-correct permissions. - (set -e; umask 077; wg genkey > "${privateKeyFile}") + (set -e; umask 077; ${wgBin} genkey > "${privateKeyFile}") fi ''; @@ -244,11 +269,19 @@ let assert assertMsg (values.configFile != null || ((values.privateKey != null) != (values.privateKeyFile != null))) "Only one of privateKey, configFile or privateKeyFile may be set"; assert assertMsg (values.generatePrivateKeyFile == false || values.privateKeyFile != null) "generatePrivateKeyFile requires privateKeyFile to be set"; let - generateKeyScriptFile = if values.generatePrivateKeyFile then writeScriptFile "generatePrivateKey.sh" (generatePrivateKeyScript values.privateKeyFile) else null; + wgBin = { + wireguard = "wg"; + amneziawg = "awg"; + }.${values.type}; + generateKeyScriptFile = + if values.generatePrivateKeyFile then + writeScriptFile "generatePrivateKey.sh" (generatePrivateKeyScript values.privateKeyFile wgBin) + else + null; preUpFile = if values.preUp != "" then writeScriptFile "preUp.sh" values.preUp else null; postUp = - optional (values.privateKeyFile != null) "wg set ${name} private-key <(cat ${values.privateKeyFile})" ++ - (concatMap (peer: optional (peer.presharedKeyFile != null) "wg set ${name} peer ${peer.publicKey} preshared-key <(cat ${peer.presharedKeyFile})") values.peers) ++ + optional (values.privateKeyFile != null) "${wgBin} set ${name} private-key <(cat ${values.privateKeyFile})" ++ + (concatMap (peer: optional (peer.presharedKeyFile != null) "${wgBin} set ${name} peer ${peer.publicKey} preshared-key <(cat ${peer.presharedKeyFile})") values.peers) ++ optional (values.postUp != "") values.postUp; postUpFile = if postUp != [] then writeScriptFile "postUp.sh" (concatMapStringsSep "\n" (line: line) postUp) else null; preDownFile = if values.preDown != "" then writeScriptFile "preDown.sh" values.preDown else null; @@ -276,6 +309,7 @@ let optionalString (postUpFile != null) "PostUp = ${postUpFile}\n" + optionalString (preDownFile != null) "PreDown = ${preDownFile}\n" + optionalString (postDownFile != null) "PostDown = ${postDownFile}\n" + + concatLines (mapAttrsToList (n: v: "${n} = ${toString v}") values.extraOptions) + concatMapStringsSep "\n" (peer: assert assertMsg (!((peer.presharedKeyFile != null) && (peer.presharedKey != null))) "Only one of presharedKey or presharedKeyFile may be set"; "[Peer]\n" + @@ -301,7 +335,10 @@ let wantedBy = optional values.autostart "multi-user.target"; environment.DEVICE = name; path = [ - pkgs.wireguard-tools + { + wireguard = pkgs.wireguard-tools; + amneziawg = pkgs.amneziawg-tools; + }.${values.type} config.networking.firewall.package # iptables or nftables config.networking.resolvconf.package # openresolv or systemd ]; @@ -312,11 +349,11 @@ let }; script = '' - ${optionalString (!config.boot.isContainer) "${pkgs.kmod}/bin/modprobe wireguard"} + ${optionalString (!config.boot.isContainer) "${pkgs.kmod}/bin/modprobe ${values.type}"} ${optionalString (values.configFile != null) '' cp ${values.configFile} ${configPath} ''} - wg-quick up ${configPath} + ${wgBin}-quick up ${configPath} ''; serviceConfig = { @@ -325,7 +362,7 @@ let }; preStop = '' - wg-quick down ${configPath} + ${wgBin}-quick down ${configPath} ''; }; in { @@ -357,8 +394,12 @@ in { ###### implementation config = mkIf (cfg.interfaces != {}) { - boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard; - environment.systemPackages = [ pkgs.wireguard-tools ]; + boot.extraModulePackages = + optional (any (x: x.type == "wireguard") (attrValues cfg.interfaces) && (versionOlder kernel.kernel.version "5.6")) kernel.wireguard + ++ optional (any (x: x.type == "amneziawg") (attrValues cfg.interfaces)) kernel.amneziawg; + environment.systemPackages = + optional (any (x: x.type == "wireguard") (attrValues cfg.interfaces)) pkgs.wireguard-tools + ++ optional (any (x: x.type == "amneziawg") (attrValues cfg.interfaces)) pkgs.amneziawg-tools; systemd.services = mapAttrs' generateUnit cfg.interfaces; # Prevent networkd from clearing the rules set by wg-quick when restarted (e.g. when waking up from suspend). diff --git a/nixos/tests/wireguard/amneziawg-quick.nix b/nixos/tests/wireguard/amneziawg-quick.nix new file mode 100644 index 000000000000..0433233a2f96 --- /dev/null +++ b/nixos/tests/wireguard/amneziawg-quick.nix @@ -0,0 +1,125 @@ +import ../make-test-python.nix ( + { + pkgs, + lib, + kernelPackages ? null, + nftables ? false, + ... + }: + let + wg-snakeoil-keys = import ./snakeoil-keys.nix; + peer = import ./make-peer.nix { inherit lib; }; + commonConfig = { + boot.kernelPackages = lib.mkIf (kernelPackages != null) kernelPackages; + networking.nftables.enable = nftables; + # Make sure iptables doesn't work with nftables enabled + boot.blacklistedKernelModules = lib.mkIf nftables [ "nft_compat" ]; + }; + extraOptions = { + Jc = 5; + Jmin = 10; + Jmax = 42; + S1 = 60; + S2 = 90; + }; + in + { + name = "amneziawg-quick"; + meta = with pkgs.lib.maintainers; { + maintainers = [ + averyanalex + azahi + ]; + }; + + nodes = { + peer0 = peer { + ip4 = "192.168.0.1"; + ip6 = "fd00::1"; + extraConfig = lib.mkMerge [ + commonConfig + { + networking.firewall.allowedUDPPorts = [ 23542 ]; + networking.wg-quick.interfaces.wg0 = { + type = "amneziawg"; + + address = [ + "10.23.42.1/32" + "fc00::1/128" + ]; + listenPort = 23542; + + inherit (wg-snakeoil-keys.peer0) privateKey; + + peers = lib.singleton { + allowedIPs = [ + "10.23.42.2/32" + "fc00::2/128" + ]; + + inherit (wg-snakeoil-keys.peer1) publicKey; + }; + + dns = [ + "10.23.42.2" + "fc00::2" + "wg0" + ]; + + inherit extraOptions; + }; + } + ]; + }; + + peer1 = peer { + ip4 = "192.168.0.2"; + ip6 = "fd00::2"; + extraConfig = lib.mkMerge [ + commonConfig + { + networking.useNetworkd = true; + networking.wg-quick.interfaces.wg0 = { + type = "amneziawg"; + + address = [ + "10.23.42.2/32" + "fc00::2/128" + ]; + inherit (wg-snakeoil-keys.peer1) privateKey; + + peers = lib.singleton { + allowedIPs = [ + "0.0.0.0/0" + "::/0" + ]; + endpoint = "192.168.0.1:23542"; + persistentKeepalive = 25; + + inherit (wg-snakeoil-keys.peer0) publicKey; + }; + + dns = [ + "10.23.42.1" + "fc00::1" + "wg0" + ]; + + inherit extraOptions; + }; + } + ]; + }; + }; + + testScript = '' + start_all() + + peer0.wait_for_unit("wg-quick-wg0.service") + peer1.wait_for_unit("wg-quick-wg0.service") + + peer1.succeed("ping -c5 fc00::1") + peer1.succeed("ping -c5 10.23.42.1") + ''; + } +) diff --git a/nixos/tests/wireguard/default.nix b/nixos/tests/wireguard/default.nix index 16393f533bc7..b5da59d07ba9 100644 --- a/nixos/tests/wireguard/default.nix +++ b/nixos/tests/wireguard/default.nix @@ -14,6 +14,7 @@ let networkd = callTest ./networkd.nix; wg-quick = callTest ./wg-quick.nix; wg-quick-nftables = args: callTest ./wg-quick.nix ({ nftables = true; } // args); + amneziawg-quick = callTest ./amneziawg-quick.nix; generated = callTest ./generated.nix; dynamic-refresh = callTest ./dynamic-refresh.nix; dynamic-refresh-networkd = args: callTest ./dynamic-refresh.nix ({ useNetworkd = true; } // args);