diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index d1a786d8f629..ff66aef783d4 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -437,6 +437,7 @@ ./services/networking/wakeonlan.nix ./services/networking/websockify.nix ./services/networking/wicd.nix + ./services/networking/wireguard.nix ./services/networking/wpa_supplicant.nix ./services/networking/xinetd.nix ./services/networking/xl2tpd.nix diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix new file mode 100644 index 000000000000..35918e42b402 --- /dev/null +++ b/nixos/modules/services/networking/wireguard.nix @@ -0,0 +1,225 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.networking.wireguard; + + kernel = config.boot.kernelPackages; + + # interface options + + interfaceOpts = { name, ... }: { + + options = { + + ips = mkOption { + example = [ "192.168.2.1/24" ]; + default = []; + type = with types; listOf str; + description = "The IP addresses of the interface."; + }; + + privateKey = mkOption { + example = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk="; + type = types.str; + description = "Base64 private key generated by wg genkey."; + }; + + presharedKey = mkOption { + default = null; + example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I="; + type = with types; nullOr str; + description = ''base64 preshared key generated by wg genpsk. Optional, + and may be omitted. This option adds an additional layer of + symmetric-key cryptography to be mixed into the already existing + public-key cryptography, for post-quantum resistance.''; + }; + + listenPort = mkOption { + default = null; + type = with types; nullOr int; + example = 51820; + description = ''16-bit port for listening. Optional; if not specified, + automatically generated based on interface name.''; + }; + + preSetup = mkOption { + example = literalExample ['' + ${pkgs.iproute}/bin/ip netns add foo + '']; + default = []; + type = with types; listOf str; + description = ''A list of commands called at the start of the interface + setup.''; + }; + + postSetup = mkOption { + example = literalExample ['' + ${pkgs.bash} -c 'printf "nameserver 10.200.100.1" | ${pkgs.openresolv}/bin/resolvconf -a wg0 -m 0' + '']; + default = []; + type = with types; listOf str; + description = "A list of commands called at the end of the interface setup."; + }; + + postShutdown = mkOption { + example = literalExample ["${pkgs.openresolv}/bin/resolvconf -d wg0"]; + default = []; + type = with types; listOf str; + description = "A list of commands called after shutting down the interface."; + }; + + peers = mkOption { + default = []; + description = "Peers linked to the interface."; + type = with types; listOf (submodule peerOpts); + }; + + }; + + }; + + # peer options + + peerOpts = { + + options = { + + publicKey = mkOption { + example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg="; + type = types.str; + description = "The base64 public key the peer."; + }; + + allowedIPs = mkOption { + example = [ "10.192.122.3/32" "10.192.124.1/24" ]; + type = with types; listOf str; + description = ''List of IP (v4 or v6) addresses with CIDR masks from + which this peer is allowed to send incoming traffic and to which + outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may + be specified for matching all IPv4 addresses, and ::/0 may be specified + for matching all IPv6 addresses.''; + }; + + endpoint = mkOption { + default = null; + example = "demo.wireguard.io:12913"; + type = with types; nullOr str; + description = ''Endpoint IP or hostname of the peer, followed by a colon, + and then a port number of the peer.''; + }; + + persistentKeepalive = mkOption { + default = null; + type = with types; nullOr int; + example = 25; + description = ''This is optional and is by default off, because most + users will not need it. It represents, in seconds, between 1 and 65535 + inclusive, how often to send an authenticated empty packet to the peer, + for the purpose of keeping a stateful firewall or NAT mapping valid + persistently. For example, if the interface very rarely sends traffic, + but it might at anytime receive traffic from a peer, and it is behind + NAT, the interface might benefit from having a persistent keepalive + interval of 25 seconds; however, most users will not need this.''; + }; + + }; + + }; + + generateConf = name: values: pkgs.writeText "wireguard-${name}.conf" '' + [Interface] + PrivateKey = ${values.privateKey} + ${optionalString (values.presharedKey != null) "PresharedKey = ${values.presharedKey}"} + ${optionalString (values.listenPort != null) "ListenPort = ${toString values.listenPort}"} + + ${concatStringsSep "\n\n" (map (peer: '' + [Peer] + PublicKey = ${peer.publicKey} + ${optionalString (peer.allowedIPs != []) "AllowedIPs = ${concatStringsSep ", " peer.allowedIPs}"} + ${optionalString (peer.endpoint != null) "Endpoint = ${peer.endpoint}"} + ${optionalString (peer.persistentKeepalive != null) "PersistentKeepalive = ${toString peer.persistentKeepalive}"} + '') values.peers)} + ''; + + ipCommand = "${pkgs.iproute}/bin/ip"; + wgCommand = "${pkgs.wireguard}/bin/wg"; + + generateUnit = name: values: + nameValuePair "wireguard-${name}" + { + description = "WireGuard Tunnel - ${name}"; + wantedBy = [ "ip-up.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = lib.flatten([ + values.preSetup + + "-${ipCommand} link del dev ${name}" + "${ipCommand} link add dev ${name} type wireguard" + "${wgCommand} setconf ${name} ${generateConf name values}" + + (map (ip: + ''${ipCommand} address add ${ip} dev ${name}'' + ) values.ips) + + "${ipCommand} link set up dev ${name}" + + (flatten (map (peer: (map (ip: + "${ipCommand} route add ${ip} dev ${name}" + ) peer.allowedIPs)) values.peers)) + + values.postSetup + ]); + + ExecStop = [ ''${ipCommand} link del dev "${name}"'' ] ++ values.postShutdown; + }; + }; + +in + +{ + + ###### interface + + options = { + + networking.wireguard = { + + interfaces = mkOption { + description = "Wireguard interfaces."; + default = {}; + example = { + wg0 = { + ips = [ "192.168.20.4/24" ]; + privateKey = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk="; + peers = [ + { allowedIPs = [ "192.168.20.1/32" ]; + publicKey = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg="; + endpoint = "demo.wireguard.io:12913"; } + ]; + }; + }; + type = with types; attrsOf (submodule interfaceOpts); + }; + + }; + + }; + + + ###### implementation + + config = mkIf (cfg.interfaces != {}) { + + boot.extraModulePackages = [ kernel.wireguard ]; + environment.systemPackages = [ pkgs.wireguard ]; + + systemd.services = mapAttrs' generateUnit cfg.interfaces; + + }; + +}