at 23.05-pre 4.7 kB view raw
1{ config, lib, options, pkgs, ... }: 2with lib; 3let 4 cfg = config.networking.openconnect; 5 openconnect = cfg.package; 6 pkcs11 = types.strMatching "pkcs11:.+" // { 7 name = "pkcs11"; 8 description = "PKCS#11 URI"; 9 }; 10 interfaceOptions = { 11 options = { 12 autoStart = mkOption { 13 default = true; 14 description = lib.mdDoc "Whether this VPN connection should be started automatically."; 15 type = types.bool; 16 }; 17 18 gateway = mkOption { 19 description = lib.mdDoc "Gateway server to connect to."; 20 example = "gateway.example.com"; 21 type = types.str; 22 }; 23 24 protocol = mkOption { 25 description = lib.mdDoc "Protocol to use."; 26 example = "anyconnect"; 27 type = 28 types.enum [ "anyconnect" "array" "nc" "pulse" "gp" "f5" "fortinet" ]; 29 }; 30 31 user = mkOption { 32 description = lib.mdDoc "Username to authenticate with."; 33 example = "example-user"; 34 type = types.nullOr types.str; 35 }; 36 37 # Note: It does not make sense to provide a way to declaratively 38 # set an authentication cookie, because they have to be requested 39 # for every new connection and would only work once. 40 passwordFile = mkOption { 41 description = lib.mdDoc '' 42 File containing the password to authenticate with. This 43 is passed to `openconnect` via the 44 `--passwd-on-stdin` option. 45 ''; 46 default = null; 47 example = "/var/lib/secrets/openconnect-passwd"; 48 type = types.nullOr types.path; 49 }; 50 51 certificate = mkOption { 52 description = lib.mdDoc "Certificate to authenticate with."; 53 default = null; 54 example = "/var/lib/secrets/openconnect_certificate.pem"; 55 type = with types; nullOr (either path pkcs11); 56 }; 57 58 privateKey = mkOption { 59 description = lib.mdDoc "Private key to authenticate with."; 60 example = "/var/lib/secrets/openconnect_private_key.pem"; 61 default = null; 62 type = with types; nullOr (either path pkcs11); 63 }; 64 65 extraOptions = mkOption { 66 description = lib.mdDoc '' 67 Extra config to be appended to the interface config. It should 68 contain long-format options as would be accepted on the command 69 line by `openconnect` 70 (see https://www.infradead.org/openconnect/manual.html). 71 Non-key-value options like `deflate` can be used by 72 declaring them as booleans, i. e. `deflate = true;`. 73 ''; 74 default = { }; 75 example = { 76 compression = "stateless"; 77 78 no-http-keepalive = true; 79 no-dtls = true; 80 }; 81 type = with types; attrsOf (either str bool); 82 }; 83 }; 84 }; 85 generateExtraConfig = extra_cfg: 86 strings.concatStringsSep "\n" (attrsets.mapAttrsToList 87 (name: value: if (value == true) then name else "${name}=${value}") 88 (attrsets.filterAttrs (_: value: value != false) extra_cfg)); 89 generateConfig = name: icfg: 90 pkgs.writeText "config" '' 91 interface=${name} 92 ${optionalString (icfg.user != null) "user=${icfg.user}"} 93 ${optionalString (icfg.passwordFile != null) "passwd-on-stdin"} 94 ${optionalString (icfg.certificate != null) 95 "certificate=${icfg.certificate}"} 96 ${optionalString (icfg.privateKey != null) "sslkey=${icfg.privateKey}"} 97 98 ${generateExtraConfig icfg.extraOptions} 99 ''; 100 generateUnit = name: icfg: { 101 description = "OpenConnect Interface - ${name}"; 102 requires = [ "network-online.target" ]; 103 after = [ "network.target" "network-online.target" ]; 104 wantedBy = optional icfg.autoStart "multi-user.target"; 105 106 serviceConfig = { 107 Type = "simple"; 108 ExecStart = "${openconnect}/bin/openconnect --config=${ 109 generateConfig name icfg 110 } ${icfg.gateway}"; 111 StandardInput = "file:${icfg.passwordFile}"; 112 113 ProtectHome = true; 114 }; 115 }; 116in { 117 options.networking.openconnect = { 118 package = mkPackageOption pkgs "openconnect" { }; 119 120 interfaces = mkOption { 121 description = lib.mdDoc "OpenConnect interfaces."; 122 default = { }; 123 example = { 124 openconnect0 = { 125 gateway = "gateway.example.com"; 126 protocol = "anyconnect"; 127 user = "example-user"; 128 passwordFile = "/var/lib/secrets/openconnect-passwd"; 129 }; 130 }; 131 type = with types; attrsOf (submodule interfaceOptions); 132 }; 133 }; 134 135 config = { 136 systemd.services = mapAttrs' (name: value: { 137 name = "openconnect-${name}"; 138 value = generateUnit name value; 139 }) cfg.interfaces; 140 }; 141 142 meta.maintainers = with maintainers; [ alyaeanyx ]; 143}