diff options
Diffstat (limited to 'nixpkgs/nixos/modules/services/security/oauth2_proxy.nix')
-rw-r--r-- | nixpkgs/nixos/modules/services/security/oauth2_proxy.nix | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix b/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix new file mode 100644 index 000000000000..61f203ef9e7d --- /dev/null +++ b/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix @@ -0,0 +1,566 @@ +# NixOS module for oauth2_proxy. + +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.services.oauth2_proxy; + + # oauth2_proxy provides many options that are only relevant if you are using + # a certain provider. This set maps from provider name to a function that + # takes the configuration and returns a string that can be inserted into the + # command-line to launch oauth2_proxy. + providerSpecificOptions = { + azure = cfg: { + azure.tenant = cfg.azure.tenant; + resource = cfg.azure.resource; + }; + + github = cfg: { github = { + inherit (cfg.github) org team; + }; }; + + google = cfg: { google = with cfg.google; optionalAttrs (groups != []) { + admin-email = adminEmail; + service-account = serviceAccountJSON; + group = groups; + }; }; + }; + + authenticatedEmailsFile = pkgs.writeText "authenticated-emails" cfg.email.addresses; + + getProviderOptions = cfg: provider: providerSpecificOptions.${provider} or (_: {}) cfg; + + allConfig = with cfg; { + inherit (cfg) provider scope upstream; + approval-prompt = approvalPrompt; + basic-auth-password = basicAuthPassword; + client-id = clientID; + client-secret = clientSecret; + custom-templates-dir = customTemplatesDir; + email-domain = email.domains; + http-address = httpAddress; + login-url = loginURL; + pass-access-token = passAccessToken; + pass-basic-auth = passBasicAuth; + pass-host-header = passHostHeader; + proxy-prefix = proxyPrefix; + profile-url = profileURL; + redeem-url = redeemURL; + redirect-url = redirectURL; + request-logging = requestLogging; + skip-auth-regex = skipAuthRegexes; + signature-key = signatureKey; + validate-url = validateURL; + htpasswd-file = htpasswd.file; + cookie = { + inherit (cookie) domain secure expire name secret refresh; + httponly = cookie.httpOnly; + }; + set-xauthrequest = setXauthrequest; + } // lib.optionalAttrs (cfg.email.addresses != null) { + authenticated-emails-file = authenticatedEmailsFile; + } // lib.optionalAttrs (cfg.passBasicAuth) { + basic-auth-password = cfg.basicAuthPassword; + } // lib.optionalAttrs (cfg.htpasswd.file != null) { + display-htpasswd-file = cfg.htpasswd.displayForm; + } // lib.optionalAttrs tls.enable { + tls-cert = tls.certificate; + tls-key = tls.key; + https-address = tls.httpsAddress; + } // (getProviderOptions cfg cfg.provider) // cfg.extraConfig; + + mapConfig = key: attr: + if attr != null && attr != [] then ( + if isDerivation attr then mapConfig key (toString attr) else + if (builtins.typeOf attr) == "set" then concatStringsSep " " + (mapAttrsToList (name: value: mapConfig (key + "-" + name) value) attr) else + if (builtins.typeOf attr) == "list" then concatMapStringsSep " " (mapConfig key) attr else + if (builtins.typeOf attr) == "bool" then "--${key}=${boolToString attr}" else + if (builtins.typeOf attr) == "string" then "--${key}='${attr}'" else + "--${key}=${toString attr}") + else ""; + + configString = concatStringsSep " " (mapAttrsToList mapConfig allConfig); +in +{ + options.services.oauth2_proxy = { + enable = mkEnableOption "oauth2_proxy"; + + package = mkOption { + type = types.package; + default = pkgs.oauth2_proxy; + defaultText = "pkgs.oauth2_proxy"; + description = '' + The package that provides oauth2_proxy. + ''; + }; + + ############################################## + # PROVIDER configuration + provider = mkOption { + type = types.enum [ + "google" + "github" + "azure" + "gitlab" + "linkedin" + "myusa" + ]; + default = "google"; + description = '' + OAuth provider. + ''; + }; + + approvalPrompt = mkOption { + type = types.enum ["force" "auto"]; + default = "force"; + description = '' + OAuth approval_prompt. + ''; + }; + + clientID = mkOption { + type = types.nullOr types.str; + description = '' + The OAuth Client ID. + ''; + example = "123456.apps.googleusercontent.com"; + }; + + clientSecret = mkOption { + type = types.nullOr types.str; + description = '' + The OAuth Client Secret. + ''; + }; + + skipAuthRegexes = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Skip authentication for requests matching any of these regular + expressions. + ''; + }; + + # XXX: Not clear whether these two options are mutually exclusive or not. + email = { + domains = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Authenticate emails with the specified domains. Use + <literal>*</literal> to authenticate any email. + ''; + }; + + addresses = mkOption { + type = types.nullOr types.lines; + default = null; + description = '' + Line-separated email addresses that are allowed to authenticate. + ''; + }; + }; + + loginURL = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Authentication endpoint. + + You only need to set this if you are using a self-hosted provider (e.g. + Github Enterprise). If you're using a publicly hosted provider + (e.g github.com), then the default works. + ''; + example = "https://provider.example.com/oauth/authorize"; + }; + + redeemURL = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Token redemption endpoint. + + You only need to set this if you are using a self-hosted provider (e.g. + Github Enterprise). If you're using a publicly hosted provider + (e.g github.com), then the default works. + ''; + example = "https://provider.example.com/oauth/token"; + }; + + validateURL = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Access token validation endpoint. + + You only need to set this if you are using a self-hosted provider (e.g. + Github Enterprise). If you're using a publicly hosted provider + (e.g github.com), then the default works. + ''; + example = "https://provider.example.com/user/emails"; + }; + + redirectURL = mkOption { + # XXX: jml suspects this is always necessary, but the command-line + # doesn't require it so making it optional. + type = types.nullOr types.str; + default = null; + description = '' + The OAuth2 redirect URL. + ''; + example = "https://internalapp.yourcompany.com/oauth2/callback"; + }; + + azure = { + tenant = mkOption { + type = types.str; + default = "common"; + description = '' + Go to a tenant-specific or common (tenant-independent) endpoint. + ''; + }; + + resource = mkOption { + type = types.str; + description = '' + The resource that is protected. + ''; + }; + }; + + google = { + adminEmail = mkOption { + type = types.str; + description = '' + The Google Admin to impersonate for API calls. + + Only users with access to the Admin APIs can access the Admin SDK + Directory API, thus the service account needs to impersonate one of + those users to access the Admin SDK Directory API. + + See <link xlink:href="https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account" />. + ''; + }; + + groups = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Restrict logins to members of these Google groups. + ''; + }; + + serviceAccountJSON = mkOption { + type = types.path; + description = '' + The path to the service account JSON credentials. + ''; + }; + }; + + github = { + org = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Restrict logins to members of this organisation. + ''; + }; + + team = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Restrict logins to members of this team. + ''; + }; + }; + + + #################################################### + # UPSTREAM Configuration + upstream = mkOption { + type = with types; coercedTo string (x: [x]) (listOf string); + default = []; + description = '' + The http url(s) of the upstream endpoint or <literal>file://</literal> + paths for static files. Routing is based on the path. + ''; + }; + + passAccessToken = mkOption { + type = types.bool; + default = false; + description = '' + Pass OAuth access_token to upstream via X-Forwarded-Access-Token header. + ''; + }; + + passBasicAuth = mkOption { + type = types.bool; + default = true; + description = '' + Pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream. + ''; + }; + + basicAuthPassword = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The password to set when passing the HTTP Basic Auth header. + ''; + }; + + passHostHeader = mkOption { + type = types.bool; + default = true; + description = '' + Pass the request Host Header to upstream. + ''; + }; + + signatureKey = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + GAP-Signature request signature key. + ''; + example = "sha1:secret0"; + }; + + cookie = { + domain = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + An optional cookie domain to force cookies to. + ''; + example = ".yourcompany.com"; + }; + + expire = mkOption { + type = types.str; + default = "168h0m0s"; + description = '' + Expire timeframe for cookie. + ''; + }; + + httpOnly = mkOption { + type = types.bool; + default = true; + description = '' + Set HttpOnly cookie flag. + ''; + }; + + name = mkOption { + type = types.str; + default = "_oauth2_proxy"; + description = '' + The name of the cookie that the oauth_proxy creates. + ''; + }; + + refresh = mkOption { + # XXX: Unclear what the behavior is when this is not specified. + type = types.nullOr types.str; + default = null; + description = '' + Refresh the cookie after this duration; 0 to disable. + ''; + example = "168h0m0s"; + }; + + secret = mkOption { + type = types.nullOr types.str; + description = '' + The seed string for secure cookies. + ''; + }; + + secure = mkOption { + type = types.bool; + default = true; + description = '' + Set secure (HTTPS) cookie flag. + ''; + }; + }; + + #################################################### + # OAUTH2 PROXY configuration + + httpAddress = mkOption { + type = types.str; + default = "http://127.0.0.1:4180"; + description = '' + HTTPS listening address. This module does not expose the port by + default. If you want this URL to be accessible to other machines, please + add the port to <literal>networking.firewall.allowedTCPPorts</literal>. + ''; + }; + + htpasswd = { + file = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Additionally authenticate against a htpasswd file. Entries must be + created with <literal>htpasswd -s</literal> for SHA encryption. + ''; + }; + + displayForm = mkOption { + type = types.bool; + default = true; + description = '' + Display username / password login form if an htpasswd file is provided. + ''; + }; + }; + + customTemplatesDir = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to custom HTML templates. + ''; + }; + + proxyPrefix = mkOption { + type = types.str; + default = "/oauth2"; + description = '' + The url root path that this proxy should be nested under. + ''; + }; + + tls = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to serve over TLS. + ''; + }; + + certificate = mkOption { + type = types.path; + description = '' + Path to certificate file. + ''; + }; + + key = mkOption { + type = types.path; + description = '' + Path to private key file. + ''; + }; + + httpsAddress = mkOption { + type = types.str; + default = ":443"; + description = '' + <literal>addr:port</literal> to listen on for HTTPS clients. + + Remember to add <literal>port</literal> to + <literal>allowedTCPPorts</literal> if you want other machines to be + able to connect to it. + ''; + }; + }; + + requestLogging = mkOption { + type = types.bool; + default = true; + description = '' + Log requests to stdout. + ''; + }; + + #################################################### + # UNKNOWN + + # XXX: Is this mandatory? Is it part of another group? Is it part of the provider specification? + scope = mkOption { + # XXX: jml suspects this is always necessary, but the command-line + # doesn't require it so making it optional. + type = types.nullOr types.str; + default = null; + description = '' + OAuth scope specification. + ''; + }; + + profileURL = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Profile access endpoint. + ''; + }; + + setXauthrequest = mkOption { + type = types.nullOr types.bool; + default = false; + description = '' + 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). + ''; + }; + + extraConfig = mkOption { + default = {}; + description = '' + Extra config to pass to oauth2_proxy. + ''; + }; + + keyFile = mkOption { + type = types.nullOr types.string; + default = null; + description = '' + oauth2_proxy allows passing sensitive configuration via environment variables. + Make a file that contains lines like + OAUTH2_PROXY_CLIENT_SECRET=asdfasdfasdf.apps.googleuserscontent.com + and specify the path here. + ''; + example = "/run/keys/oauth2_proxy"; + }; + + }; + + config = mkIf cfg.enable { + + services.oauth2_proxy = mkIf (cfg.keyFile != null) { + clientID = mkDefault null; + clientSecret = mkDefault null; + cookie.secret = mkDefault null; + }; + + users.users.oauth2_proxy = { + description = "OAuth2 Proxy"; + }; + + systemd.services.oauth2_proxy = { + description = "OAuth2 Proxy"; + path = [ cfg.package ]; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + User = "oauth2_proxy"; + Restart = "always"; + ExecStart = "${cfg.package.bin}/bin/oauth2_proxy ${configString}"; + EnvironmentFile = mkIf (cfg.keyFile != null) cfg.keyFile; + }; + }; + + }; +} |