at 18.09-beta 9.3 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg = config.networking.wireguard; 8 9 kernel = config.boot.kernelPackages; 10 11 # interface options 12 13 interfaceOpts = { ... }: { 14 15 options = { 16 17 ips = mkOption { 18 example = [ "192.168.2.1/24" ]; 19 default = []; 20 type = with types; listOf str; 21 description = "The IP addresses of the interface."; 22 }; 23 24 privateKey = mkOption { 25 example = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk="; 26 type = with types; nullOr str; 27 default = null; 28 description = '' 29 Base64 private key generated by wg genkey. 30 31 Warning: Consider using privateKeyFile instead if you do not 32 want to store the key in the world-readable Nix store. 33 ''; 34 }; 35 36 privateKeyFile = mkOption { 37 example = "/private/wireguard_key"; 38 type = with types; nullOr str; 39 default = null; 40 description = '' 41 Private key file as generated by wg genkey. 42 ''; 43 }; 44 45 listenPort = mkOption { 46 default = null; 47 type = with types; nullOr int; 48 example = 51820; 49 description = '' 50 16-bit port for listening. Optional; if not specified, 51 automatically generated based on interface name. 52 ''; 53 }; 54 55 preSetup = mkOption { 56 example = literalExample '' 57 ${pkgs.iproute}/bin/ip netns add foo 58 ''; 59 default = ""; 60 type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines; 61 description = '' 62 Commands called at the start of the interface setup. 63 ''; 64 }; 65 66 postSetup = mkOption { 67 example = literalExample '' 68 printf "nameserver 10.200.100.1" | ${pkgs.openresolv}/bin/resolvconf -a wg0 -m 0 69 ''; 70 default = ""; 71 type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines; 72 description = "Commands called at the end of the interface setup."; 73 }; 74 75 postShutdown = mkOption { 76 example = literalExample "${pkgs.openresolv}/bin/resolvconf -d wg0"; 77 default = ""; 78 type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines; 79 description = "Commands called after shutting down the interface."; 80 }; 81 82 table = mkOption { 83 default = "main"; 84 type = types.str; 85 description = ''The kernel routing table to add this interface's 86 associated routes to. Setting this is useful for e.g. policy routing 87 ("ip rule") or virtual routing and forwarding ("ip vrf"). Both numeric 88 table IDs and table names (/etc/rt_tables) can be used. Defaults to 89 "main".''; 90 }; 91 92 peers = mkOption { 93 default = []; 94 description = "Peers linked to the interface."; 95 type = with types; listOf (submodule peerOpts); 96 }; 97 98 allowedIPsAsRoutes = mkOption { 99 example = false; 100 default = true; 101 type = types.bool; 102 description = '' 103 Determines whether to add allowed IPs as routes or not. 104 ''; 105 }; 106 }; 107 108 }; 109 110 # peer options 111 112 peerOpts = { 113 114 options = { 115 116 publicKey = mkOption { 117 example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg="; 118 type = types.str; 119 description = "The base64 public key the peer."; 120 }; 121 122 presharedKey = mkOption { 123 default = null; 124 example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I="; 125 type = with types; nullOr str; 126 description = '' 127 Base64 preshared key generated by wg genpsk. Optional, 128 and may be omitted. This option adds an additional layer of 129 symmetric-key cryptography to be mixed into the already existing 130 public-key cryptography, for post-quantum resistance. 131 132 Warning: Consider using presharedKeyFile instead if you do not 133 want to store the key in the world-readable Nix store. 134 ''; 135 }; 136 137 presharedKeyFile = mkOption { 138 default = null; 139 example = "/private/wireguard_psk"; 140 type = with types; nullOr str; 141 description = '' 142 File pointing to preshared key as generated by wg pensk. Optional, 143 and may be omitted. This option adds an additional layer of 144 symmetric-key cryptography to be mixed into the already existing 145 public-key cryptography, for post-quantum resistance. 146 ''; 147 }; 148 149 allowedIPs = mkOption { 150 example = [ "10.192.122.3/32" "10.192.124.1/24" ]; 151 type = with types; listOf str; 152 description = ''List of IP (v4 or v6) addresses with CIDR masks from 153 which this peer is allowed to send incoming traffic and to which 154 outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may 155 be specified for matching all IPv4 addresses, and ::/0 may be specified 156 for matching all IPv6 addresses.''; 157 }; 158 159 endpoint = mkOption { 160 default = null; 161 example = "demo.wireguard.io:12913"; 162 type = with types; nullOr str; 163 description = ''Endpoint IP or hostname of the peer, followed by a colon, 164 and then a port number of the peer.''; 165 }; 166 167 persistentKeepalive = mkOption { 168 default = null; 169 type = with types; nullOr int; 170 example = 25; 171 description = ''This is optional and is by default off, because most 172 users will not need it. It represents, in seconds, between 1 and 65535 173 inclusive, how often to send an authenticated empty packet to the peer, 174 for the purpose of keeping a stateful firewall or NAT mapping valid 175 persistently. For example, if the interface very rarely sends traffic, 176 but it might at anytime receive traffic from a peer, and it is behind 177 NAT, the interface might benefit from having a persistent keepalive 178 interval of 25 seconds; however, most users will not need this.''; 179 }; 180 181 }; 182 183 }; 184 185 generateUnit = name: values: 186 # exactly one way to specify the private key must be set 187 assert (values.privateKey != null) != (values.privateKeyFile != null); 188 let privKey = if values.privateKeyFile != null then values.privateKeyFile else pkgs.writeText "wg-key" values.privateKey; 189 in 190 nameValuePair "wireguard-${name}" 191 { 192 description = "WireGuard Tunnel - ${name}"; 193 requires = [ "network-online.target" ]; 194 after = [ "network.target" "network-online.target" ]; 195 wantedBy = [ "multi-user.target" ]; 196 environment.DEVICE = name; 197 path = with pkgs; [ kmod iproute wireguard-tools ]; 198 199 serviceConfig = { 200 Type = "oneshot"; 201 RemainAfterExit = true; 202 }; 203 204 script = '' 205 modprobe wireguard 206 207 ${values.preSetup} 208 209 ip link add dev ${name} type wireguard 210 211 ${concatMapStringsSep "\n" (ip: 212 "ip address add ${ip} dev ${name}" 213 ) values.ips} 214 215 wg set ${name} private-key ${privKey} ${ 216 optionalString (values.listenPort != null) " listen-port ${toString values.listenPort}"} 217 218 ${concatMapStringsSep "\n" (peer: 219 assert (peer.presharedKeyFile == null) || (peer.presharedKey == null); # at most one of the two must be set 220 let psk = if peer.presharedKey != null then pkgs.writeText "wg-psk" peer.presharedKey else peer.presharedKeyFile; 221 in 222 "wg set ${name} peer ${peer.publicKey}" + 223 optionalString (psk != null) " preshared-key ${psk}" + 224 optionalString (peer.endpoint != null) " endpoint ${peer.endpoint}" + 225 optionalString (peer.persistentKeepalive != null) " persistent-keepalive ${toString peer.persistentKeepalive}" + 226 optionalString (peer.allowedIPs != []) " allowed-ips ${concatStringsSep "," peer.allowedIPs}" 227 ) values.peers} 228 229 ip link set up dev ${name} 230 231 ${optionalString (values.allowedIPsAsRoutes != false) (concatStringsSep "\n" (concatMap (peer: 232 (map (allowedIP: 233 "ip route replace ${allowedIP} dev ${name} table ${values.table}" 234 ) peer.allowedIPs) 235 ) values.peers))} 236 237 ${values.postSetup} 238 ''; 239 240 postStop = '' 241 ip link del dev ${name} 242 ${values.postShutdown} 243 ''; 244 }; 245 246in 247 248{ 249 250 ###### interface 251 252 options = { 253 254 networking.wireguard = { 255 256 interfaces = mkOption { 257 description = "Wireguard interfaces."; 258 default = {}; 259 example = { 260 wg0 = { 261 ips = [ "192.168.20.4/24" ]; 262 privateKey = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk="; 263 peers = [ 264 { allowedIPs = [ "192.168.20.1/32" ]; 265 publicKey = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg="; 266 endpoint = "demo.wireguard.io:12913"; } 267 ]; 268 }; 269 }; 270 type = with types; attrsOf (submodule interfaceOpts); 271 }; 272 273 }; 274 275 }; 276 277 278 ###### implementation 279 280 config = mkIf (cfg.interfaces != {}) { 281 282 boot.extraModulePackages = [ kernel.wireguard ]; 283 environment.systemPackages = [ pkgs.wireguard-tools ]; 284 285 systemd.services = mapAttrs' generateUnit cfg.interfaces; 286 287 }; 288 289}