at 17.09-beta 8.9 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 }; 99 100 }; 101 102 # peer options 103 104 peerOpts = { 105 106 options = { 107 108 publicKey = mkOption { 109 example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg="; 110 type = types.str; 111 description = "The base64 public key the peer."; 112 }; 113 114 presharedKey = mkOption { 115 default = null; 116 example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I="; 117 type = with types; nullOr str; 118 description = '' 119 Base64 preshared key generated by wg genpsk. Optional, 120 and may be omitted. This option adds an additional layer of 121 symmetric-key cryptography to be mixed into the already existing 122 public-key cryptography, for post-quantum resistance. 123 124 Warning: Consider using presharedKeyFile instead if you do not 125 want to store the key in the world-readable Nix store. 126 ''; 127 }; 128 129 presharedKeyFile = mkOption { 130 default = null; 131 example = "/private/wireguard_psk"; 132 type = with types; nullOr str; 133 description = '' 134 File pointing to preshared key as generated by wg pensk. Optional, 135 and may be omitted. This option adds an additional layer of 136 symmetric-key cryptography to be mixed into the already existing 137 public-key cryptography, for post-quantum resistance. 138 ''; 139 }; 140 141 allowedIPs = mkOption { 142 example = [ "10.192.122.3/32" "10.192.124.1/24" ]; 143 type = with types; listOf str; 144 description = ''List of IP (v4 or v6) addresses with CIDR masks from 145 which this peer is allowed to send incoming traffic and to which 146 outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may 147 be specified for matching all IPv4 addresses, and ::/0 may be specified 148 for matching all IPv6 addresses.''; 149 }; 150 151 endpoint = mkOption { 152 default = null; 153 example = "demo.wireguard.io:12913"; 154 type = with types; nullOr str; 155 description = ''Endpoint IP or hostname of the peer, followed by a colon, 156 and then a port number of the peer.''; 157 }; 158 159 persistentKeepalive = mkOption { 160 default = null; 161 type = with types; nullOr int; 162 example = 25; 163 description = ''This is optional and is by default off, because most 164 users will not need it. It represents, in seconds, between 1 and 65535 165 inclusive, how often to send an authenticated empty packet to the peer, 166 for the purpose of keeping a stateful firewall or NAT mapping valid 167 persistently. For example, if the interface very rarely sends traffic, 168 but it might at anytime receive traffic from a peer, and it is behind 169 NAT, the interface might benefit from having a persistent keepalive 170 interval of 25 seconds; however, most users will not need this.''; 171 }; 172 173 }; 174 175 }; 176 177 ipCommand = "${pkgs.iproute}/bin/ip"; 178 wgCommand = "${pkgs.wireguard}/bin/wg"; 179 180 generateUnit = name: values: 181 # exactly one way to specify the private key must be set 182 assert (values.privateKey != null) != (values.privateKeyFile != null); 183 let privKey = if values.privateKeyFile != null then values.privateKeyFile else pkgs.writeText "wg-key" values.privateKey; 184 in 185 nameValuePair "wireguard-${name}" 186 { 187 description = "WireGuard Tunnel - ${name}"; 188 after = [ "network.target" ]; 189 wantedBy = [ "multi-user.target" ]; 190 191 serviceConfig = { 192 Type = "oneshot"; 193 RemainAfterExit = true; 194 ExecStart = flatten([ 195 values.preSetup 196 197 "-${ipCommand} link del dev ${name}" 198 "${ipCommand} link add dev ${name} type wireguard" 199 200 (map (ip: 201 "${ipCommand} address add ${ip} dev ${name}" 202 ) values.ips) 203 204 ("${wgCommand} set ${name} private-key ${privKey}" + 205 optionalString (values.listenPort != null) " listen-port ${toString values.listenPort}") 206 207 (map (peer: 208 assert (peer.presharedKeyFile == null) || (peer.presharedKey == null); # at most one of the two must be set 209 let psk = if peer.presharedKey != null then pkgs.writeText "wg-psk" peer.presharedKey else peer.presharedKeyFile; 210 in 211 "${wgCommand} set ${name} peer ${peer.publicKey}" + 212 optionalString (psk != null) " preshared-key ${psk}" + 213 optionalString (peer.endpoint != null) " endpoint ${peer.endpoint}" + 214 optionalString (peer.persistentKeepalive != null) " persistent-keepalive ${toString peer.persistentKeepalive}" + 215 optionalString (peer.allowedIPs != []) " allowed-ips ${concatStringsSep "," peer.allowedIPs}" 216 ) values.peers) 217 218 "${ipCommand} link set up dev ${name}" 219 220 (map (peer: 221 (map (allowedIP: 222 "${ipCommand} route replace ${allowedIP} dev ${name} table ${values.table}" 223 ) peer.allowedIPs) 224 ) values.peers) 225 226 values.postSetup 227 ]); 228 ExecStop = flatten([ 229 "${ipCommand} link del dev ${name}" 230 values.postShutdown 231 ]); 232 }; 233 }; 234 235in 236 237{ 238 239 ###### interface 240 241 options = { 242 243 networking.wireguard = { 244 245 interfaces = mkOption { 246 description = "Wireguard interfaces."; 247 default = {}; 248 example = { 249 wg0 = { 250 ips = [ "192.168.20.4/24" ]; 251 privateKey = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk="; 252 peers = [ 253 { allowedIPs = [ "192.168.20.1/32" ]; 254 publicKey = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg="; 255 endpoint = "demo.wireguard.io:12913"; } 256 ]; 257 }; 258 }; 259 type = with types; attrsOf (submodule interfaceOpts); 260 }; 261 262 }; 263 264 }; 265 266 267 ###### implementation 268 269 config = mkIf (cfg.interfaces != {}) { 270 271 boot.extraModulePackages = [ kernel.wireguard ]; 272 environment.systemPackages = [ pkgs.wireguard ]; 273 274 systemd.services = mapAttrs' generateUnit cfg.interfaces; 275 276 }; 277 278}