1 { config, lib, pkgs, ... }:
4 cfg = config.services.oauth2-proxy;
6 # oauth2-proxy provides many options that are only relevant if you are using
7 # a certain provider. This set maps from provider name to a function that
8 # takes the configuration and returns a string that can be inserted into the
9 # command-line to launch oauth2-proxy.
10 providerSpecificOptions = {
12 azure-tenant = cfg.azure.tenant;
13 resource = cfg.azure.resource;
16 github = cfg: { github = {
17 inherit (cfg.github) org team;
20 google = cfg: { google = with cfg.google; lib.optionalAttrs (groups != []) {
21 admin-email = adminEmail;
22 service-account = serviceAccountJSON;
27 authenticatedEmailsFile = pkgs.writeText "authenticated-emails" cfg.email.addresses;
29 getProviderOptions = cfg: provider: providerSpecificOptions.${provider} or (_: {}) cfg;
31 allConfig = with cfg; {
32 inherit (cfg) provider scope upstream;
33 approval-prompt = approvalPrompt;
34 basic-auth-password = basicAuthPassword;
36 client-secret = clientSecret;
37 custom-templates-dir = customTemplatesDir;
38 email-domain = email.domains;
39 http-address = httpAddress;
41 pass-access-token = passAccessToken;
42 pass-basic-auth = passBasicAuth;
43 pass-host-header = passHostHeader;
44 reverse-proxy = reverseProxy;
45 proxy-prefix = proxyPrefix;
46 profile-url = profileURL;
47 oidc-issuer-url = oidcIssuerUrl;
48 redeem-url = redeemURL;
49 redirect-url = redirectURL;
50 request-logging = requestLogging;
51 skip-auth-regex = skipAuthRegexes;
52 signature-key = signatureKey;
53 validate-url = validateURL;
54 htpasswd-file = htpasswd.file;
56 inherit (cookie) domain secure expire name secret refresh;
57 httponly = cookie.httpOnly;
59 set-xauthrequest = setXauthrequest;
60 } // lib.optionalAttrs (cfg.email.addresses != null) {
61 authenticated-emails-file = authenticatedEmailsFile;
62 } // lib.optionalAttrs (cfg.passBasicAuth) {
63 basic-auth-password = cfg.basicAuthPassword;
64 } // lib.optionalAttrs (cfg.htpasswd.file != null) {
65 display-htpasswd-file = cfg.htpasswd.displayForm;
66 } // lib.optionalAttrs tls.enable {
67 tls-cert-file = tls.certificate;
68 tls-key-file = tls.key;
69 https-address = tls.httpsAddress;
70 } // (getProviderOptions cfg cfg.provider) // cfg.extraConfig;
72 mapConfig = key: attr:
73 lib.optionalString (attr != null && attr != []) (
74 if lib.isDerivation attr then mapConfig key (toString attr) else
75 if (builtins.typeOf attr) == "set" then lib.concatStringsSep " "
76 (lib.mapAttrsToList (name: value: mapConfig (key + "-" + name) value) attr) else
77 if (builtins.typeOf attr) == "list" then lib.concatMapStringsSep " " (mapConfig key) attr else
78 if (builtins.typeOf attr) == "bool" then "--${key}=${lib.boolToString attr}" else
79 if (builtins.typeOf attr) == "string" then "--${key}='${attr}'" else
80 "--${key}=${toString attr}");
82 configString = lib.concatStringsSep " " (lib.mapAttrsToList mapConfig allConfig);
85 options.services.oauth2-proxy = {
86 enable = lib.mkEnableOption "oauth2-proxy";
88 package = lib.mkPackageOption pkgs "oauth2-proxy" { };
90 ##############################################
91 # PROVIDER configuration
92 # Taken from: https://github.com/oauth2-proxy/oauth2-proxy/blob/master/providers/providers.go
93 provider = lib.mkOption {
94 type = lib.types.enum [
116 approvalPrompt = lib.mkOption {
117 type = lib.types.enum ["force" "auto"];
120 OAuth approval_prompt.
124 clientID = lib.mkOption {
125 type = lib.types.nullOr lib.types.str;
129 example = "123456.apps.googleusercontent.com";
132 oidcIssuerUrl = lib.mkOption {
133 type = lib.types.nullOr lib.types.str;
136 The OAuth issuer URL.
138 example = "https://login.microsoftonline.com/{TENANT_ID}/v2.0";
141 clientSecret = lib.mkOption {
142 type = lib.types.nullOr lib.types.str;
144 The OAuth Client Secret.
148 skipAuthRegexes = lib.mkOption {
149 type = lib.types.listOf lib.types.str;
152 Skip authentication for requests matching any of these regular
157 # XXX: Not clear whether these two options are mutually exclusive or not.
159 domains = lib.mkOption {
160 type = lib.types.listOf lib.types.str;
163 Authenticate emails with the specified domains. Use
164 `*` to authenticate any email.
168 addresses = lib.mkOption {
169 type = lib.types.nullOr lib.types.lines;
172 Line-separated email addresses that are allowed to authenticate.
177 loginURL = lib.mkOption {
178 type = lib.types.nullOr lib.types.str;
181 Authentication endpoint.
183 You only need to set this if you are using a self-hosted provider (e.g.
184 Github Enterprise). If you're using a publicly hosted provider
185 (e.g github.com), then the default works.
187 example = "https://provider.example.com/oauth/authorize";
190 redeemURL = lib.mkOption {
191 type = lib.types.nullOr lib.types.str;
194 Token redemption endpoint.
196 You only need to set this if you are using a self-hosted provider (e.g.
197 Github Enterprise). If you're using a publicly hosted provider
198 (e.g github.com), then the default works.
200 example = "https://provider.example.com/oauth/token";
203 validateURL = lib.mkOption {
204 type = lib.types.nullOr lib.types.str;
207 Access token validation endpoint.
209 You only need to set this if you are using a self-hosted provider (e.g.
210 Github Enterprise). If you're using a publicly hosted provider
211 (e.g github.com), then the default works.
213 example = "https://provider.example.com/user/emails";
216 redirectURL = lib.mkOption {
217 # XXX: jml suspects this is always necessary, but the command-line
218 # doesn't require it so making it optional.
219 type = lib.types.nullOr lib.types.str;
222 The OAuth2 redirect URL.
224 example = "https://internalapp.yourcompany.com/oauth2/callback";
228 tenant = lib.mkOption {
229 type = lib.types.str;
232 Go to a tenant-specific or common (tenant-independent) endpoint.
236 resource = lib.mkOption {
237 type = lib.types.str;
239 The resource that is protected.
245 adminEmail = lib.mkOption {
246 type = lib.types.str;
248 The Google Admin to impersonate for API calls.
250 Only users with access to the Admin APIs can access the Admin SDK
251 Directory API, thus the service account needs to impersonate one of
252 those users to access the Admin SDK Directory API.
254 See <https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account>.
258 groups = lib.mkOption {
259 type = lib.types.listOf lib.types.str;
262 Restrict logins to members of these Google groups.
266 serviceAccountJSON = lib.mkOption {
267 type = lib.types.path;
269 The path to the service account JSON credentials.
276 type = lib.types.nullOr lib.types.str;
279 Restrict logins to members of this organisation.
283 team = lib.mkOption {
284 type = lib.types.nullOr lib.types.str;
287 Restrict logins to members of this team.
293 ####################################################
294 # UPSTREAM Configuration
295 upstream = lib.mkOption {
296 type = with lib.types; coercedTo str (x: [x]) (listOf str);
299 The http url(s) of the upstream endpoint or `file://`
300 paths for static files. Routing is based on the path.
304 passAccessToken = lib.mkOption {
305 type = lib.types.bool;
308 Pass OAuth access_token to upstream via X-Forwarded-Access-Token header.
312 passBasicAuth = lib.mkOption {
313 type = lib.types.bool;
316 Pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream.
320 basicAuthPassword = lib.mkOption {
321 type = lib.types.nullOr lib.types.str;
324 The password to set when passing the HTTP Basic Auth header.
328 passHostHeader = lib.mkOption {
329 type = lib.types.bool;
332 Pass the request Host Header to upstream.
336 signatureKey = lib.mkOption {
337 type = lib.types.nullOr lib.types.str;
340 GAP-Signature request signature key.
342 example = "sha1:secret0";
346 domain = lib.mkOption {
347 type = lib.types.nullOr lib.types.str;
350 Optional cookie domains to force cookies to (ie: `.yourcompany.com`).
351 The longest domain matching the request's host will be used (or the shortest
352 cookie domain if there is no match).
354 example = ".yourcompany.com";
357 expire = lib.mkOption {
358 type = lib.types.str;
359 default = "168h0m0s";
361 Expire timeframe for cookie.
365 httpOnly = lib.mkOption {
366 type = lib.types.bool;
369 Set HttpOnly cookie flag.
373 name = lib.mkOption {
374 type = lib.types.str;
375 default = "_oauth2_proxy";
377 The name of the cookie that the oauth_proxy creates.
381 refresh = lib.mkOption {
382 # XXX: Unclear what the behavior is when this is not specified.
383 type = lib.types.nullOr lib.types.str;
386 Refresh the cookie after this duration; 0 to disable.
388 example = "168h0m0s";
391 secret = lib.mkOption {
392 type = lib.types.nullOr lib.types.str;
394 The seed string for secure cookies.
398 secure = lib.mkOption {
399 type = lib.types.bool;
402 Set secure (HTTPS) cookie flag.
407 ####################################################
408 # OAUTH2 PROXY configuration
410 httpAddress = lib.mkOption {
411 type = lib.types.str;
412 default = "http://127.0.0.1:4180";
414 HTTPS listening address. This module does not expose the port by
415 default. If you want this URL to be accessible to other machines, please
416 add the port to `networking.firewall.allowedTCPPorts`.
421 file = lib.mkOption {
422 type = lib.types.nullOr lib.types.path;
425 Additionally authenticate against a htpasswd file. Entries must be
426 created with `htpasswd -s` for SHA encryption.
430 displayForm = lib.mkOption {
431 type = lib.types.bool;
434 Display username / password login form if an htpasswd file is provided.
439 customTemplatesDir = lib.mkOption {
440 type = lib.types.nullOr lib.types.path;
443 Path to custom HTML templates.
447 reverseProxy = lib.mkOption {
448 type = lib.types.bool;
451 In case when running behind a reverse proxy, controls whether headers
452 like `X-Real-Ip` are accepted. Usage behind a reverse
453 proxy will require this flag to be set to avoid logging the reverse
458 proxyPrefix = lib.mkOption {
459 type = lib.types.str;
462 The url root path that this proxy should be nested under.
467 enable = lib.mkOption {
468 type = lib.types.bool;
471 Whether to serve over TLS.
475 certificate = lib.mkOption {
476 type = lib.types.path;
478 Path to certificate file.
483 type = lib.types.path;
485 Path to private key file.
489 httpsAddress = lib.mkOption {
490 type = lib.types.str;
493 `addr:port` to listen on for HTTPS clients.
495 Remember to add `port` to
496 `allowedTCPPorts` if you want other machines to be
497 able to connect to it.
502 requestLogging = lib.mkOption {
503 type = lib.types.bool;
506 Log requests to stdout.
510 ####################################################
513 # XXX: Is this mandatory? Is it part of another group? Is it part of the provider specification?
514 scope = lib.mkOption {
515 # XXX: jml suspects this is always necessary, but the command-line
516 # doesn't require it so making it optional.
517 type = lib.types.nullOr lib.types.str;
520 OAuth scope specification.
524 profileURL = lib.mkOption {
525 type = lib.types.nullOr lib.types.str;
528 Profile access endpoint.
532 setXauthrequest = lib.mkOption {
533 type = lib.types.nullOr lib.types.bool;
536 Set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode). Setting this to 'null' means using the upstream default (false).
540 extraConfig = lib.mkOption {
542 type = lib.types.attrsOf lib.types.anything;
544 Extra config to pass to oauth2-proxy.
548 keyFile = lib.mkOption {
549 type = lib.types.nullOr lib.types.path;
552 oauth2-proxy allows passing sensitive configuration via environment variables.
553 Make a file that contains lines like
554 OAUTH2_PROXY_CLIENT_SECRET=asdfasdfasdf.apps.googleuserscontent.com
555 and specify the path here.
557 example = "/run/keys/oauth2-proxy";
562 (lib.mkRenamedOptionModule [ "services" "oauth2_proxy" ] [ "services" "oauth2-proxy" ])
565 config = lib.mkIf cfg.enable {
566 services.oauth2-proxy = lib.mkIf (cfg.keyFile != null) {
567 clientID = lib.mkDefault null;
568 clientSecret = lib.mkDefault null;
569 cookie.secret = lib.mkDefault null;
572 users.users.oauth2-proxy = {
573 description = "OAuth2 Proxy";
575 group = "oauth2-proxy";
578 users.groups.oauth2-proxy = {};
580 systemd.services.oauth2-proxy =
581 let needsKeycloak = lib.elem cfg.provider ["keycloak" "keycloak-oidc"]
582 && config.services.keycloak.enable;
584 description = "OAuth2 Proxy";
585 path = [ cfg.package ];
586 wantedBy = [ "multi-user.target" ];
587 wants = [ "network-online.target" ] ++ lib.optionals needsKeycloak [ "keycloak.service" ];
588 after = [ "network-online.target" ] ++ lib.optionals needsKeycloak [ "keycloak.service" ];
589 restartTriggers = [ cfg.keyFile ];
591 User = "oauth2-proxy";
593 ExecStart = "${lib.getExe cfg.package} ${configString}";
594 EnvironmentFile = lib.mkIf (cfg.keyFile != null) cfg.keyFile;