at 18.03-beta 9.2 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 = { name, ... }: { 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; listOf str; 61 description = '' 62 A list of commands called at the start of the interface setup. 63 ''; 64 }; 65 66 postSetup = mkOption { 67 example = literalExample ['' 68 ${pkgs.bash} -c 'printf "nameserver 10.200.100.1" | ${pkgs.openresolv}/bin/resolvconf -a wg0 -m 0' 69 '']; 70 default = []; 71 type = with types; listOf str; 72 description = "A list of 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; listOf str; 79 description = "A list of 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 ipCommand = "${pkgs.iproute}/bin/ip"; 186 wgCommand = "${pkgs.wireguard}/bin/wg"; 187 188 generateUnit = name: values: 189 # exactly one way to specify the private key must be set 190 assert (values.privateKey != null) != (values.privateKeyFile != null); 191 let privKey = if values.privateKeyFile != null then values.privateKeyFile else pkgs.writeText "wg-key" values.privateKey; 192 in 193 nameValuePair "wireguard-${name}" 194 { 195 description = "WireGuard Tunnel - ${name}"; 196 after = [ "network.target" ]; 197 wantedBy = [ "multi-user.target" ]; 198 environment.DEVICE = name; 199 200 serviceConfig = { 201 Type = "oneshot"; 202 RemainAfterExit = true; 203 ExecStart = flatten([ 204 values.preSetup 205 206 "-${ipCommand} link del dev ${name}" 207 "${ipCommand} link add dev ${name} type wireguard" 208 209 (map (ip: 210 "${ipCommand} address add ${ip} dev ${name}" 211 ) values.ips) 212 213 ("${wgCommand} set ${name} private-key ${privKey}" + 214 optionalString (values.listenPort != null) " listen-port ${toString values.listenPort}") 215 216 (map (peer: 217 assert (peer.presharedKeyFile == null) || (peer.presharedKey == null); # at most one of the two must be set 218 let psk = if peer.presharedKey != null then pkgs.writeText "wg-psk" peer.presharedKey else peer.presharedKeyFile; 219 in 220 "${wgCommand} set ${name} peer ${peer.publicKey}" + 221 optionalString (psk != null) " preshared-key ${psk}" + 222 optionalString (peer.endpoint != null) " endpoint ${peer.endpoint}" + 223 optionalString (peer.persistentKeepalive != null) " persistent-keepalive ${toString peer.persistentKeepalive}" + 224 optionalString (peer.allowedIPs != []) " allowed-ips ${concatStringsSep "," peer.allowedIPs}" 225 ) values.peers) 226 227 "${ipCommand} link set up dev ${name}" 228 229 (optionals (values.allowedIPsAsRoutes != false) (map (peer: 230 (map (allowedIP: 231 "${ipCommand} route replace ${allowedIP} dev ${name} table ${values.table}" 232 ) peer.allowedIPs) 233 ) values.peers)) 234 235 values.postSetup 236 ]); 237 ExecStop = flatten([ 238 "${ipCommand} link del dev ${name}" 239 values.postShutdown 240 ]); 241 }; 242 }; 243 244in 245 246{ 247 248 ###### interface 249 250 options = { 251 252 networking.wireguard = { 253 254 interfaces = mkOption { 255 description = "Wireguard interfaces."; 256 default = {}; 257 example = { 258 wg0 = { 259 ips = [ "192.168.20.4/24" ]; 260 privateKey = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk="; 261 peers = [ 262 { allowedIPs = [ "192.168.20.1/32" ]; 263 publicKey = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg="; 264 endpoint = "demo.wireguard.io:12913"; } 265 ]; 266 }; 267 }; 268 type = with types; attrsOf (submodule interfaceOpts); 269 }; 270 271 }; 272 273 }; 274 275 276 ###### implementation 277 278 config = mkIf (cfg.interfaces != {}) { 279 280 boot.extraModulePackages = [ kernel.wireguard ]; 281 environment.systemPackages = [ pkgs.wireguard ]; 282 283 systemd.services = mapAttrs' generateUnit cfg.interfaces; 284 285 }; 286 287}