summary refs log tree commit diff
path: root/nixos/modules/services/security/oauth2_proxy.nix
diff options
context:
space:
mode:
authorJonathan Lange <jml@mumak.net>2016-05-06 20:54:51 +0100
committerJonathan Lange <jml@mumak.net>2016-06-09 15:00:23 +0100
commit58599744ee9673d309d5e4d8415077c242545e8d (patch)
treeb9d89b864d77900b00ed2044e4e3d8dcae979a7d /nixos/modules/services/security/oauth2_proxy.nix
parent5f8d14546b6a5a5df8a4768833497a674083dd8e (diff)
downloadnixpkgs-58599744ee9673d309d5e4d8415077c242545e8d.tar
nixpkgs-58599744ee9673d309d5e4d8415077c242545e8d.tar.gz
nixpkgs-58599744ee9673d309d5e4d8415077c242545e8d.tar.bz2
nixpkgs-58599744ee9673d309d5e4d8415077c242545e8d.tar.lz
nixpkgs-58599744ee9673d309d5e4d8415077c242545e8d.tar.xz
nixpkgs-58599744ee9673d309d5e4d8415077c242545e8d.tar.zst
nixpkgs-58599744ee9673d309d5e4d8415077c242545e8d.zip
Add module for oauth2_proxy
Diffstat (limited to 'nixos/modules/services/security/oauth2_proxy.nix')
-rw-r--r--nixos/modules/services/security/oauth2_proxy.nix528
1 files changed, 528 insertions, 0 deletions
diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix
new file mode 100644
index 00000000000..aa962743f85
--- /dev/null
+++ b/nixos/modules/services/security/oauth2_proxy.nix
@@ -0,0 +1,528 @@
+# 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 " ";
+
+  # 'toString' doesn't quite do what we want for bools.
+  fromBool = x: if x then "true" else "false";
+
+  # 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=${fromBool cfg.cookie.httpOnly} \
+    --cookie-name='${cfg.cookie.name}' \
+    --cookie-secret='${cfg.cookie.secret}' \
+    --cookie-secure=${fromBool 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=${fromBool cfg.htpasswd.displayForm}"} \
+    ${optionalString (!isNull cfg.loginURL) "--login-url='${cfg.loginURL}'"} \
+    --pass-access-token=${fromBool cfg.passAccessToken} \
+    --pass-basic-auth=${fromBool cfg.passBasicAuth} \
+    --pass-host-header=${fromBool 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=${fromBool 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 = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to run oauth2_proxy.
+      '';
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = 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 = ''
+       List of regular expressions which will bypass authentication when
+       requests path's match.
+     '';
+    };
+
+    # 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 * 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="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 file:// 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 = ''
+        [http://]<addr>:<port> or unix://<path> to listen on for HTTP clients.
+
+        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
+        networking.firewall.allowedTCPPorts.
+      '';
+    };
+
+    htpasswd = {
+      file = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          Additionally authenticate against a htpasswd file. Entries must be
+          created with "htpasswd -s" 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 (e.g. /<oauth2>/sign_in);
+      '';
+    };
+
+    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 = ''
+          <addr>:<port> to listen on for HTTPS clients.
+
+          Remember to add <port> to allowedTCPPorts 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-interfaces.target" ];
+
+      serviceConfig = {
+        User = "oauth2_proxy";
+        Restart = "always";
+        ExecStart = "${cfg.package}/bin/oauth2_proxy ${mkCommandLine cfg}";
+      };
+    };
+
+  };
+}