summary refs log tree commit diff
path: root/nixos/modules/services/networking/openconnect.nix
blob: bc873b2198bcc1f2d4808be69ae13ed26fb1dead (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
{ 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 = {
      autoStart = mkOption {
        default = true;
        description = "Whether this VPN connection should be started automatically.";
        type = types.bool;
      };

      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 <code>openconnect</code> via the
          <code>--passwd-on-stdin</code> 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 <code>openconnect</code>
          (see https://www.infradead.org/openconnect/manual.html).
          Non-key-value options like <code>deflate</code> can be used by
          declaring them as booleans, i. e. <code>deflate = true;</code>.
        '';
        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 = optional icfg.autoStart "multi-user.target";

    serviceConfig = {
      Type = "simple";
      ExecStart = "${openconnect}/bin/openconnect --config=${
          generateConfig name icfg
        } ${icfg.gateway}";
      StandardInput = "file:${icfg.passwordFile}";

      ProtectHome = true;
    };
  };
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 ];
}