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