1 { config, lib, options, pkgs, ... }:
4 cfg = config.networking.openconnect;
5 openconnect = cfg.package;
6 pkcs11 = types.strMatching "pkcs11:.+" // {
8 description = "PKCS#11 URI";
12 autoStart = mkOption {
14 description = lib.mdDoc "Whether this VPN connection should be started automatically.";
19 description = lib.mdDoc "Gateway server to connect to.";
20 example = "gateway.example.com";
25 description = lib.mdDoc "Protocol to use.";
26 example = "anyconnect";
28 types.enum [ "anyconnect" "array" "nc" "pulse" "gp" "f5" "fortinet" ];
32 description = lib.mdDoc "Username to authenticate with.";
33 example = "example-user";
34 type = types.nullOr types.str;
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.
47 example = "/var/lib/secrets/openconnect-passwd";
48 type = types.nullOr types.path;
51 certificate = mkOption {
52 description = lib.mdDoc "Certificate to authenticate with.";
54 example = "/var/lib/secrets/openconnect_certificate.pem";
55 type = with types; nullOr (either path pkcs11);
58 privateKey = mkOption {
59 description = lib.mdDoc "Private key to authenticate with.";
60 example = "/var/lib/secrets/openconnect_private_key.pem";
62 type = with types; nullOr (either path pkcs11);
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
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;`.
76 compression = "stateless";
78 no-http-keepalive = true;
81 type = with types; attrsOf (either str bool);
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" ''
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}"}
98 ${generateExtraConfig icfg.extraOptions}
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";
108 ExecStart = "${openconnect}/bin/openconnect --config=${
109 generateConfig name icfg
111 StandardInput = "file:${icfg.passwordFile}";
117 options.networking.openconnect = {
118 package = mkPackageOption pkgs "openconnect" { };
120 interfaces = mkOption {
121 description = lib.mdDoc "OpenConnect interfaces.";
125 gateway = "gateway.example.com";
126 protocol = "anyconnect";
127 user = "example-user";
128 passwordFile = "/var/lib/secrets/openconnect-passwd";
131 type = with types; attrsOf (submodule interfaceOptions);
136 systemd.services = mapAttrs' (name: value: {
137 name = "openconnect-${name}";
138 value = generateUnit name value;
142 meta.maintainers = with maintainers; [ alyaeanyx ];