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