From 0c066f0d0e1ce6739a9a2f1e11429ddc22d8c7ba Mon Sep 17 00:00:00 2001 From: alyaeanyx Date: Sat, 26 Mar 2022 22:54:02 +0100 Subject: [PATCH] nixos/openconnect: add module --- .../from_md/release-notes/rl-2205.section.xml | 7 + .../manual/release-notes/rl-2205.section.md | 2 + nixos/modules/module-list.nix | 1 + .../services/networking/openconnect.nix | 135 ++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 nixos/modules/services/networking/openconnect.nix diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml index 4453c907f67..feb3a8bd2d4 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml @@ -1631,6 +1631,13 @@ true. + + + A module for declarative configuration of openconnect VPN + profiles was added under + networking.openconnect. + + The element-desktop package now has an diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md index abc16b8d66e..9a7ee88e617 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -569,6 +569,8 @@ In addition to numerous new and upgraded packages, this release has the followin using `fetchgit` or `fetchhg` if the argument `fetchSubmodules` is set to `true`. +- A module for declarative configuration of openconnect VPN profiles was added under `networking.openconnect`. + - The `element-desktop` package now has an `useKeytar` option (defaults to `true`), which allows disabling `keytar` and in turn `libsecret` usage (which binds to native credential managers / keychain libraries). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 1ad2592b1dd..813ac397d7f 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -851,6 +851,7 @@ ./services/networking/ofono.nix ./services/networking/oidentd.nix ./services/networking/onedrive.nix + ./services/networking/openconnect.nix ./services/networking/openvpn.nix ./services/networking/ostinato.nix ./services/networking/owamp.nix diff --git a/nixos/modules/services/networking/openconnect.nix b/nixos/modules/services/networking/openconnect.nix new file mode 100644 index 00000000000..7b2ef48e1c4 --- /dev/null +++ b/nixos/modules/services/networking/openconnect.nix @@ -0,0 +1,135 @@ +{ config, lib, options, pkgs, ... }: +with lib; +let + cfg = config.networking.openconnect; + openconnect = cfg.package; + pkcs11 = types.strMatching "pkcs11:.+" // { + name = "pkcs11"; + description = "PKCS#11 URI"; + }; + interfaceOptions = { + options = { + gateway = mkOption { + description = "Gateway server to connect to."; + example = "gateway.example.com"; + type = types.str; + }; + + protocol = mkOption { + description = "Protocol to use."; + example = "anyconnect"; + type = + types.enum [ "anyconnect" "array" "nc" "pulse" "gp" "f5" "fortinet" ]; + }; + + user = mkOption { + description = "Username to authenticate with."; + example = "example-user"; + type = types.nullOr types.str; + }; + + # Note: It does not make sense to provide a way to declaratively + # set an authentication cookie, because they have to be requested + # for every new connection and would only work once. + passwordFile = mkOption { + description = '' + File containing the password to authenticate with. This + is passed to openconnect via the + --passwd-on-stdin option. + ''; + default = null; + example = "/var/lib/secrets/openconnect-passwd"; + type = types.nullOr types.path; + }; + + certificate = mkOption { + description = "Certificate to authenticate with."; + default = null; + example = "/var/lib/secrets/openconnect_certificate.pem"; + type = with types; nullOr (either path pkcs11); + }; + + privateKey = mkOption { + description = "Private key to authenticate with."; + example = "/var/lib/secrets/openconnect_private_key.pem"; + default = null; + type = with types; nullOr (either path pkcs11); + }; + + extraOptions = mkOption { + description = '' + Extra config to be appended to the interface config. It should + contain long-format options as would be accepted on the command + line by openconnect + (see https://www.infradead.org/openconnect/manual.html). + Non-key-value options like deflate can be used by + declaring them as booleans, i. e. deflate = true;. + ''; + default = { }; + example = { + compression = "stateless"; + + no-http-keepalive = true; + no-dtls = true; + }; + type = with types; attrsOf (either str bool); + }; + }; + }; + generateExtraConfig = extra_cfg: + strings.concatStringsSep "\n" (attrsets.mapAttrsToList + (name: value: if (value == true) then name else "${name}=${value}") + (attrsets.filterAttrs (_: value: value != false) extra_cfg)); + generateConfig = name: icfg: + pkgs.writeText "config" '' + interface=${name} + ${optionalString (icfg.user != null) "user=${icfg.user}"} + ${optionalString (icfg.passwordFile != null) "passwd-on-stdin"} + ${optionalString (icfg.certificate != null) + "certificate=${icfg.certificate}"} + ${optionalString (icfg.privateKey != null) "sslkey=${icfg.privateKey}"} + + ${generateExtraConfig icfg.extraOptions} + ''; + generateUnit = name: icfg: { + description = "OpenConnect Interface - ${name}"; + requires = [ "network-online.target" ]; + after = [ "network.target" "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "simple"; + ExecStart = "${openconnect}/bin/openconnect --config=${ + generateConfig name icfg + } ${icfg.gateway}"; + StandardInput = "file:${icfg.passwordFile}"; + }; + }; +in { + options.networking.openconnect = { + package = mkPackageOption pkgs "openconnect" { }; + + interfaces = mkOption { + description = "OpenConnect interfaces."; + default = { }; + example = { + openconnect0 = { + gateway = "gateway.example.com"; + protocol = "anyconnect"; + user = "example-user"; + passwordFile = "/var/lib/secrets/openconnect-passwd"; + }; + }; + type = with types; attrsOf (submodule interfaceOptions); + }; + }; + + config = { + systemd.services = mapAttrs' (name: value: { + name = "openconnect-${name}"; + value = generateUnit name value; + }) cfg.interfaces; + }; + + meta.maintainers = with maintainers; [ alyaeanyx ]; +}