 478e7184f8
			
		
	
	
		478e7184f8
		
			
		
	
	
	
	
		
			
			And replace them with a more appropriate type Also fix up some minor module problems along the way
		
			
				
	
	
		
			567 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			567 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| # 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 str (x: [x]) (listOf str);
 | |
|       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.path;
 | |
|       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;
 | |
|       };
 | |
|     };
 | |
| 
 | |
|   };
 | |
| }
 |