521 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			521 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
# NixOS module for oauth2_proxy.
 | 
						|
 | 
						|
{ config, lib, pkgs, ... }:
 | 
						|
 | 
						|
with lib;
 | 
						|
let
 | 
						|
  cfg = config.services.oauth2_proxy;
 | 
						|
 | 
						|
  # Use like:
 | 
						|
  #   repeatedArgs (arg: "--arg=${arg}") args
 | 
						|
  repeatedArgs = concatMapStringsSep " ";
 | 
						|
 | 
						|
  # 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: ''
 | 
						|
      $(optionalString (!isNull cfg.github.org) "--github-org=${cfg.github.org}") \
 | 
						|
      $(optionalString (!isNull cfg.github.team) "--github-org=${cfg.github.team}") \
 | 
						|
    '';
 | 
						|
 | 
						|
    google = cfg: ''
 | 
						|
      --google-admin-email=${cfg.google.adminEmail} \
 | 
						|
      --google-service-account=${cfg.google.serviceAccountJSON} \
 | 
						|
      $(repeatedArgs (group: "--google-group=${group}") cfg.google.groups) \
 | 
						|
    '';
 | 
						|
  };
 | 
						|
 | 
						|
  authenticatedEmailsFile = pkgs.writeText "authenticated-emails" cfg.email.addresses;
 | 
						|
 | 
						|
  getProviderOptions = cfg: provider:
 | 
						|
    if providerSpecificOptions ? provider then providerSpecificOptions.provider cfg else "";
 | 
						|
 | 
						|
  mkCommandLine = cfg: ''
 | 
						|
    --provider='${cfg.provider}' \
 | 
						|
    ${optionalString (!isNull cfg.email.addresses) "--authenticated-emails-file='${authenticatedEmailsFile}'"} \
 | 
						|
    --approval-prompt='${cfg.approvalPrompt}' \
 | 
						|
    ${optionalString (cfg.passBasicAuth && !isNull cfg.basicAuthPassword) "--basic-auth-password='${cfg.basicAuthPassword}'"} \
 | 
						|
    --client-id='${cfg.clientID}' \
 | 
						|
    --client-secret='${cfg.clientSecret}' \
 | 
						|
    ${optionalString (!isNull cfg.cookie.domain) "--cookie-domain='${cfg.cookie.domain}'"} \
 | 
						|
    --cookie-expire='${cfg.cookie.expire}' \
 | 
						|
    --cookie-httponly=${boolToString cfg.cookie.httpOnly} \
 | 
						|
    --cookie-name='${cfg.cookie.name}' \
 | 
						|
    --cookie-secret='${cfg.cookie.secret}' \
 | 
						|
    --cookie-secure=${boolToString cfg.cookie.secure} \
 | 
						|
    ${optionalString (!isNull cfg.cookie.refresh) "--cookie-refresh='${cfg.cookie.refresh}'"} \
 | 
						|
    ${optionalString (!isNull cfg.customTemplatesDir) "--custom-templates-dir='${cfg.customTemplatesDir}'"} \
 | 
						|
    ${repeatedArgs (x: "--email-domain='${x}'") cfg.email.domains} \
 | 
						|
    --http-address='${cfg.httpAddress}' \
 | 
						|
    ${optionalString (!isNull cfg.htpasswd.file) "--htpasswd-file='${cfg.htpasswd.file}' --display-htpasswd-form=${boolToString cfg.htpasswd.displayForm}"} \
 | 
						|
    ${optionalString (!isNull cfg.loginURL) "--login-url='${cfg.loginURL}'"} \
 | 
						|
    --pass-access-token=${boolToString cfg.passAccessToken} \
 | 
						|
    --pass-basic-auth=${boolToString cfg.passBasicAuth} \
 | 
						|
    --pass-host-header=${boolToString cfg.passHostHeader} \
 | 
						|
    --proxy-prefix='${cfg.proxyPrefix}' \
 | 
						|
    ${optionalString (!isNull cfg.profileURL) "--profile-url='${cfg.profileURL}'"} \
 | 
						|
    ${optionalString (!isNull cfg.redeemURL) "--redeem-url='${cfg.redeemURL}'"} \
 | 
						|
    ${optionalString (!isNull cfg.redirectURL) "--redirect-url='${cfg.redirectURL}'"} \
 | 
						|
    --request-logging=${boolToString cfg.requestLogging} \
 | 
						|
    ${optionalString (!isNull cfg.scope) "--scope='${cfg.scope}'"} \
 | 
						|
    ${repeatedArgs (x: "--skip-auth-regex='${x}'") cfg.skipAuthRegexes} \
 | 
						|
    ${optionalString (!isNull cfg.signatureKey) "--signature-key='${cfg.signatureKey}'"} \
 | 
						|
    --upstream='${cfg.upstream}' \
 | 
						|
    ${optionalString (!isNull cfg.validateURL) "--validate-url='${cfg.validateURL}'"} \
 | 
						|
    ${optionalString cfg.tls.enable "--tls-cert='${cfg.tls.certificate}' --tls-key='${cfg.tls.key}' --https-address='${cfg.tls.httpsAddress}'"} \
 | 
						|
  '' + getProviderOptions cfg cfg.provider;
 | 
						|
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.str;
 | 
						|
      description = ''
 | 
						|
        The OAuth Client ID.
 | 
						|
      '';
 | 
						|
      example = "123456.apps.googleusercontent.com";
 | 
						|
    };
 | 
						|
 | 
						|
    clientSecret = mkOption {
 | 
						|
      type = 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 = types.commas;
 | 
						|
      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.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 = "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.
 | 
						|
      '';
 | 
						|
    };
 | 
						|
 | 
						|
  };
 | 
						|
 | 
						|
  config = mkIf cfg.enable {
 | 
						|
 | 
						|
    users.extraUsers.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 ${mkCommandLine cfg}";
 | 
						|
      };
 | 
						|
    };
 | 
						|
 | 
						|
  };
 | 
						|
}
 |