nixos/wireguard: add AmneziaWG support

Co-authored-by: azahi <azat@bahawi.net>
This commit is contained in:
AveryanAlex 2024-12-20 16:26:55 +03:00
parent 1ce7180d60
commit 0240773f49
No known key found for this signature in database
GPG key ID: 3C23C7BD99452036
3 changed files with 164 additions and 11 deletions

View file

@ -15,6 +15,15 @@ let
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.
'';
};
ips = mkOption {
example = [ "192.168.2.1/24" ];
default = [];
@ -204,6 +213,22 @@ let
:::
'';
};
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.
'';
};
};
};
@ -342,6 +367,16 @@ let
};
wgBins = {
wireguard = "wg";
amneziawg = "awg";
};
wgPackages = {
wireguard = pkgs.wireguard-tools;
amneziawg = pkgs.amneziawg-tools;
};
generateKeyServiceUnit = name: values:
assert values.generatePrivateKeyFile;
nameValuePair "wireguard-${name}-key"
@ -350,7 +385,7 @@ let
wantedBy = [ "wireguard-${name}.service" ];
requiredBy = [ "wireguard-${name}.service" ];
before = [ "wireguard-${name}.service" ];
path = with pkgs; [ wireguard-tools ];
path = [ wgPackages.${values.type} ];
serviceConfig = {
Type = "oneshot";
@ -366,7 +401,7 @@ let
if [ ! -f "${values.privateKeyFile}" ]; then
# Write private key file with atomically-correct permissions.
(set -e; umask 077; wg genkey > "${values.privateKeyFile}")
(set -e; umask 077; ${wgBins.${values.type}} genkey > "${values.privateKeyFile}")
fi
'';
};
@ -391,7 +426,7 @@ let
src = interfaceCfg.socketNamespace;
dst = interfaceCfg.interfaceNamespace;
ip = nsWrap "ip" src dst;
wg = nsWrap "wg" src dst;
wg = nsWrap wgBins.${interfaceCfg.type} src dst;
dynamicEndpointRefreshSeconds = dynamicRefreshSeconds interfaceCfg peer;
dynamicRefreshEnabled = dynamicEndpointRefreshSeconds != 0;
# We generate a different name (a `-refresh` suffix) when `dynamicEndpointRefreshSeconds`
@ -408,7 +443,7 @@ let
wantedBy = [ "wireguard-${interfaceName}.service" ];
environment.DEVICE = interfaceName;
environment.WG_ENDPOINT_RESOLUTION_RETRIES = "infinity";
path = with pkgs; [ iproute2 wireguard-tools ];
path = with pkgs; [ iproute2 wgPackages.${interfaceCfg.type} ];
serviceConfig =
if !dynamicRefreshEnabled
@ -497,7 +532,7 @@ let
dst = values.interfaceNamespace;
ipPreMove = nsWrap "ip" src null;
ipPostMove = nsWrap "ip" src dst;
wg = nsWrap "wg" src dst;
wg = nsWrap wgBins.${values.type} src dst;
ns = if dst == "init" then "1" else dst;
in
@ -508,7 +543,7 @@ let
wants = [ "network.target" ];
before = [ "network.target" ];
environment.DEVICE = name;
path = with pkgs; [ kmod iproute2 wireguard-tools ];
path = with pkgs; [ kmod iproute2 wgPackages.${values.type} ];
serviceConfig = {
Type = "oneshot";
@ -516,10 +551,10 @@ let
};
script = concatStringsSep "\n" (
optional (!config.boot.isContainer) "modprobe wireguard || true"
optional (!config.boot.isContainer) "modprobe ${values.type} || true"
++ [
values.preSetup
''${ipPreMove} link add dev "${name}" type wireguard''
''${ipPreMove} link add dev "${name}" type ${values.type}''
]
++ optional (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) ''${ipPreMove} link set "${name}" netns "${ns}"''
++ optional (values.mtu != null) ''${ipPostMove} link set "${name}" mtu ${toString values.mtu}''
@ -531,6 +566,7 @@ let
[ ''${wg} set "${name}" private-key "${privKey}"'' ]
++ optional (values.listenPort != null) ''listen-port "${toString values.listenPort}"''
++ optional (values.fwMark != null) ''fwmark "${values.fwMark}"''
++ mapAttrsToList (k: v: ''${toLower k} "${toString v}"'') values.extraOptions
))
''${ipPostMove} link set up dev "${name}"''
values.postSetup
@ -550,6 +586,9 @@ let
ns = last nsList;
in
if (length nsList > 0 && ns != "init") then ''ip netns exec "${ns}" "${cmd}"'' else cmd;
usingWg = any (x: x.type == "wireguard") (attrValues cfg.interfaces);
usingAwg = any (x: x.type == "amneziawg") (attrValues cfg.interfaces);
in
{
@ -624,9 +663,11 @@ in
message = "networking.wireguard.interfaces.${interfaceName} peer «${peer.publicKey}» has both presharedKey and presharedKeyFile set, but only one can be used.";
}) all_peers;
boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard;
boot.kernelModules = [ "wireguard" ];
environment.systemPackages = [ pkgs.wireguard-tools ];
boot.extraModulePackages =
optional (usingWg && (versionOlder kernel.kernel.version "5.6")) kernel.wireguard
++ optional usingAwg kernel.amneziawg;
boot.kernelModules = optional usingWg "wireguard" ++ optional usingAwg "amneziawg";
environment.systemPackages = optional usingWg pkgs.wireguard-tools ++ optional usingAwg pkgs.amneziawg-tools;
systemd.services = mkIf (!cfg.useNetworkd) (
(mapAttrs' generateInterfaceUnit cfg.interfaces)

View file

@ -0,0 +1,111 @@
import ../make-test-python.nix (
{
pkgs,
lib,
kernelPackages ? null,
...
}:
let
wg-snakeoil-keys = import ./snakeoil-keys.nix;
peer = (import ./make-peer.nix) { inherit lib; };
extraOptions = {
Jc = 5;
Jmin = 10;
Jmax = 42;
S1 = 60;
S2 = 90;
};
in
{
name = "amneziawg";
meta = with pkgs.lib.maintainers; {
maintainers = [
averyanalex
azahi
];
};
nodes = {
peer0 = peer {
ip4 = "192.168.0.1";
ip6 = "fd00::1";
extraConfig = {
boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
networking.firewall.allowedUDPPorts = [ 23542 ];
networking.wireguard.interfaces.wg0 = {
type = "amneziawg";
ips = [
"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;
};
inherit extraOptions;
};
};
};
peer1 = peer {
ip4 = "192.168.0.2";
ip6 = "fd00::2";
extraConfig = {
boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
networking.wireguard.interfaces.wg0 = {
type = "amneziawg";
ips = [
"10.23.42.2/32"
"fc00::2/128"
];
listenPort = 23542;
allowedIPsAsRoutes = false;
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;
};
postSetup =
let
inherit (pkgs) iproute2;
in
''
${iproute2}/bin/ip route replace 10.23.42.1/32 dev wg0
${iproute2}/bin/ip route replace fc00::1/128 dev wg0
'';
inherit extraOptions;
};
};
};
};
testScript = ''
start_all()
peer0.wait_for_unit("wireguard-wg0.service")
peer1.wait_for_unit("wireguard-wg0.service")
peer1.succeed("ping -c5 fc00::1")
peer1.succeed("ping -c5 10.23.42.1")
'';
}
)

View file

@ -10,6 +10,7 @@ with pkgs.lib;
let
tests = let callTest = p: args: import p ({ inherit system pkgs; } // args); in {
basic = callTest ./basic.nix;
amneziawg = callTest ./amneziawg.nix;
namespaces = callTest ./namespaces.nix;
networkd = callTest ./networkd.nix;
wg-quick = callTest ./wg-quick.nix;