encryptedcookies

package
v0.0.0-...-7bf56e6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 3, 2024 License: Apache-2.0 Imports: 32 Imported by: 1

Documentation

Overview

Package encryptedcookies implements authentication using encrypted cookies.

A session cookie contains a session ID and a per-session encryption key. The session cookie is itself encrypted (using an AEAD scheme) with the server-global primary encryption key. The session ID points to a session entity in a database that contains information about the user as well as various OAuth2 tokens established during the login flow when the session was created. Tokens are encrypted (again using an AEAD scheme) with the per-session encryption key from the cookie.

One of the stored tokens is an OAuth2 refresh token. Periodically, during the session validation process, it is used to refresh other stored tokens (in particular an OAuth2 access token and an OpenID Connect ID token). This procedure fails if the user revoked access to their accounts via the ID provider or if the user's account is not longer active (from the ID provider's point of view). That way the state of the session is tied to the state of the user account at the ID provider which fully offloads user accounts management to the ID provider.

Configuration

To use this module you'll need to create an encryption key and to register an OAuth2 client with the ID provider. Instructions below assume you are using the Google Accounts ID provider and the created encryption key will be used as the server primary encryption key (i.e. it will be used to encrypt not only cookies but also any other secrets that the server may wish to encrypt).

Start by creating two Google Secret Manager secrets (with no values) named `tink-aead-primary` and `oauth-client-secret` in the cloud project that the server is running in (referred to as `<cloud-project>` below). Make sure the server account has "Secret Manager Secret Accessor" role in them. These steps are usually done using Terraform.

Initialize `tink-aead-primary` key by using https://go.chromium.org/luci/server/cmd/secret-tool tool:

cd server/cmd/secret-tool
go run main.go create sm://<cloud-project>/tink-aead-primary -secret-type tink-aes256-gcm

This secret now contains a serialized Tink keyset with the primary encryption key. If necessary it can be rotated using the same `secret-tool` tool:

cd server/cmd/secret-tool
go run main.go rotation-begin sm://<cloud-project>/tink-aead-primary
# wait several hours for the new key to propagate into all caches
# confirm by looking at /chrome/infra/secrets/gsm/version metric
go run main.go rotation-end sm://<cloud-project>/tink-aead-primary

This will add a new active key to the keyset. It will be used to encrypt new cookies, but the old key will still be recognized when decrypting existing cookies.

Next, create a new OAuth2 client ID that will represent your server. Follow instructions on https://support.google.com/cloud/answer/6158849?hl=en and pick the application type "Web application". Add an authorized redirect URI equal to "https://<your-server-host>/auth/openid/callback". Do not add any authorized JavaScript origins.

After creating the OAuth2 client, note the client ID (usually looks like "<number>-<gibberish>.apps.googleusercontent.com") and the client secret (just a random looking string). Put the value of the secret into a new `oauth-client-secret` Google Secret Manager secret using the `secret-tool`:

cd server/cmd/secret-tool
go run main.go create sm://<cloud-project>/oauth-client-secret -secret-type password
# paste the client secret

All prerequisites are done. Pass the following flags to the server binary to instruct it to use the generated secrets and the OAuth2 client:

server \
    ...
    -primary-tink-aead-key sm://tink-aead-primary \
    -encrypted-cookies-client-id <number>-<gibberish>.apps.googleusercontent.com \
    -encrypted-cookies-client-secret sm://oauth-client-secret \
    -encrypted-cookies-redirect-url https://<your-server-host>/auth/openid/callback

Note that the value of `-encrypted-cookies-redirect-url` must match exactly what you specified when creating the OAuth2 client (e.g. if you used some custom DNS domain name there, specify it in the `-encrypted-cookies-redirect-url` as well).

If you want to use a dedicated key set for encrypting cookies specifically, replace `-primary-tink-aead-key` with `-encrypted-cookies-tink-aead-key` (and perhaps use some different name for the secret).

Session store

The module needs to know where and how to store user sessions. Link to a concrete implementation (e.g. Cloud Datastore) by using the following blank import line in the main.go:

import (
  ...
  // Store auth sessions in the datastore.
  _ "go.chromium.org/luci/server/encryptedcookies/session/datastore"
)

Inactive sessions cleanup

When using Cloud Datastore as a session storage, configure a time-to-live policy to delete `encryptedcookies.Session` entities based on `ExpireAt` field. See https://cloud.google.com/datastore/docs/ttl. This step is usually done via Terraform.

A session is considered expired if it wasn't accessed for more than 14 days.

Exposed routes

The module exposes 3 routes involved in the login/logout process: `/auth/openid/login`, `/auth/openid/logout` and `/auth/openid/callback`. When configuring your load balancer (or dispatch.yaml on Appengine), make sure `/auth/openid/*` requests are routed appropriately.

Note that cookies established by one server process can be validated by another, as long as they are both configured identically (i.e. all CLI flags mentioned above are passed to both binaries). For example, you can configure the load balancer to pass all `/auth/openid/*` requests to a dedicated server responsible for the login/logout, but then validate user cookies on another server.

Note also that the server that only validates cookies still needs write access to the session store, to be able to refresh encrypted tokens stored there. It means if there are some caching layers in front of the datastore, they must be configured identically across all servers as well.

Running locally

If the server is starting in the development mode (no `-prod` flag is passed), and the `-encrypted-cookies-client-id` flag is not set, the module switches to use fake cookies that have a similar semantics to the real encrypted cookies, but require no extra configuration. They are absolutely insecure and must never be used outside of local runs. They exist only to simplify the local development of servers that use LoginURL/LogoutURL APIs.

Index

Constants

This section is empty.

Variables

View Source
var ModuleName = module.RegisterName("go.chromium.org/luci/server/encryptedcookies")

ModuleName can be used to refer to this module when declaring dependencies.

Functions

func NewModule

func NewModule(opts *ModuleOptions) module.Module

NewModule returns a server module that configures an authentication method based on encrypted cookies.

func NewModuleFromFlags

func NewModuleFromFlags() module.Module

NewModuleFromFlags is a variant of NewModule that initializes options through command line flags.

Calling this function registers flags in flag.CommandLine. They are usually parsed in server.Main(...).

Types

type AuthMethod

type AuthMethod struct {
	// Configuration returns OpenID Connect configuration parameters.
	//
	// Required.
	OpenIDConfig func(ctx context.Context) (*OpenIDConfig, error)

	// AEADProvider returns an implementation of Authenticated Encryption with
	// Additional Authenticated primitive used to encrypt the cookies and other
	// sensitive state.
	AEADProvider func(ctx context.Context) tink.AEAD

	// Sessions keeps user sessions in some permanent storage.
	//
	// Required.
	Sessions session.Store

	// Insecure is true to allow http:// URLs and non-https cookies. Useful for
	// local development.
	Insecure bool

	// IncompatibleCookies is a list of cookies to remove when setting or clearing
	// the session cookie. It is useful to get rid of cookies from previously used
	// authentication methods.
	IncompatibleCookies []string

	// LimitCookieExposure, if set, limits the cookie to be set only on
	// "/auth/openid/" HTTP path and makes it `SameSite: strict`.
	//
	// This is useful for SPAs that exchange cookies for authentication tokens via
	// fetch(...) requests to "/auth/openid/state". In this case the cookie is
	// not normally used by any other HTTP handler and it makes no sense to send
	// it in every request.
	LimitCookieExposure bool

	// RequiredScopes is a list of required OAuth scopes that will be requested
	// when making the OAuth authorization request, in addition to the default
	// scopes (openid email profile) and the OptionalScopes.
	//
	// Existing sessions that don't have the required scopes will be closed. All
	// scopes in the RequiredScopes must be in the RequiredScopes or
	// OptionalScopes of other running instances of the app. Otherwise a session
	// opened by other running instances could be closed immediately.
	RequiredScopes []string

	// OptionalScopes is a list of optional OAuth scopes that will be requested
	// when making the OAuth authorization request, in addition to the default
	// scopes (openid email profile) and the RequiredScopes.
	//
	// Existing sessions that don't have the optional scopes will not be closed.
	// This is useful for rolling out changes incrementally. Once the new version
	// takes over all the traffic, promote the optional scopes to RequiredScopes.
	OptionalScopes []string

	// ExposeStateEndpoint controls whether "/auth/openid/state" endpoint should
	// be exposed.
	//
	// See auth.StateEndpointResponse struct for details.
	//
	// It is off by default since it can potentially make XSS vulnerabilities more
	// severe by exposing OAuth and ID tokens to malicious injected code. It
	// should be enabled only if the frontend code needs it and it is aware of
	// XSS risks.
	ExposeStateEndpoint bool
}

Method is an auth.Method implementation that uses encrypted cookies.

Uses OpenID Connect to establish sessions and refresh tokens to verify OpenID identity provider still knows about the user.

func (*AuthMethod) Authenticate

func (m *AuthMethod) Authenticate(ctx context.Context, r auth.RequestMetadata) (*auth.User, auth.Session, error)

Authenticate authenticates the request.

Implements auth.Method.

func (*AuthMethod) InstallHandlers

func (m *AuthMethod) InstallHandlers(r *router.Router, base router.MiddlewareChain)

InstallHandlers installs HTTP handlers used in the login protocol.

Implements auth.HasHandlers.

func (*AuthMethod) LoginURL

func (m *AuthMethod) LoginURL(ctx context.Context, dest string) (string, error)

LoginURL returns a URL that, when visited, prompts the user to sign in, then redirects the user to the URL specified by dest.

Implements auth.UsersAPI.

func (*AuthMethod) LogoutURL

func (m *AuthMethod) LogoutURL(ctx context.Context, dest string) (string, error)

LogoutURL returns a URL that, when visited, signs the user out, then redirects the user to the URL specified by dest.

Implements auth.UsersAPI.

func (*AuthMethod) StateEndpointURL

func (m *AuthMethod) StateEndpointURL(ctx context.Context) (string, error)

StateEndpointURL returns an URL that serves the authentication state.

Implements auth.HasStateEndpoint.

func (*AuthMethod) Warmup

func (m *AuthMethod) Warmup(ctx context.Context) error

Warmup prepares local caches.

Implements auth.Warmable.

type ModuleOptions

type ModuleOptions struct {
	// TinkAEADKey is a "sm://..." reference to a Tink AEAD keyset to use.
	//
	// If empty, will use the primary keyset via secrets.PrimaryTinkAEAD().
	TinkAEADKey string

	// DiscoveryURL is an URL of the discovery document with provider's config.
	DiscoveryURL string

	// ClientID identifies OAuth2 Web client representing the application.
	ClientID string

	// ClientSecret is a "sm://..." reference to OAuth2 client secret.
	ClientSecret string

	// RedirectURL must be `https://<host>/auth/openid/callback`.
	RedirectURL string

	// SessionStoreKind can be used to pick a concrete implementation of a store.
	SessionStoreKind string

	// SessionStoreNamespace can be used to namespace sessions in the store.
	SessionStoreNamespace string

	// RequiredScopes is a list of required OAuth scopes that will be requested
	// when making the OAuth authorization request, in addition to the default
	// scopes (openid email profile) and the OptionalScopes.
	//
	// Existing sessions that don't have the required scopes will be closed. All
	// scopes in the RequiredScopes must be in the RequiredScopes or
	// OptionalScopes of other running instances of the app. Otherwise a session
	// opened by other running instances could be closed immediately.
	RequiredScopes stringlistflag.Flag

	// OptionalScopes is a list of optional OAuth scopes that will be requested
	// when making the OAuth authorization request, in addition to the default
	// scopes (openid email profile) and the RequiredScopes.
	//
	// Existing sessions that don't have the optional scopes will not be closed.
	// This is useful for rolling out changes incrementally. Once the new version
	// takes over all the traffic, promote the optional scopes to RequiredScopes.
	OptionalScopes stringlistflag.Flag

	// ExposeStateEndpoint controls whether "/auth/openid/state" endpoint should
	// be exposed.
	//
	// See auth.StateEndpointResponse struct for details.
	//
	// It is off by default since it can potentially make XSS vulnerabilities more
	// severe by exposing OAuth and ID tokens to malicious injected code. It
	// should be enabled only if the frontend code needs it and it is aware of
	// XSS risks.
	ExposeStateEndpoint bool

	// LimitCookieExposure, if set, limits the cookie to be set only on
	// "/auth/openid/" HTTP path and makes it `SameSite: strict`.
	//
	// This is useful for SPAs that exchange cookies for authentication tokens via
	// fetch(...) requests to "/auth/openid/state". In this case the cookie is
	// not normally used by any other HTTP handler and it makes no sense to send
	// it in every request.
	LimitCookieExposure bool
}

ModuleOptions contain configuration of the encryptedcookies server module.

func (*ModuleOptions) Register

func (o *ModuleOptions) Register(f *flag.FlagSet)

Register registers the command line flags.

type OpenIDConfig

type OpenIDConfig struct {
	// DiscoveryURL is where to grab discovery document with provider's config.
	DiscoveryURL string

	// ClientID identifies OAuth2 Web client representing the application.
	//
	// Can be obtained by registering the OAuth2 client with the identity
	// provider.
	ClientID string

	// ClientSecret is a secret associated with ClientID.
	//
	// Can be obtained by registering the OAuth2 client with the identity
	// provider.
	ClientSecret string

	// RedirectURI must be `https://<host>/auth/openid/callback`.
	//
	// The OAuth2 client should be configured to allow this redirect URL.
	RedirectURI string
}

OpenIDConfig is a configuration related to OpenID Connect provider.

All parameters are required.

Directories

Path Synopsis
fakecookies
Package fakecookies implements a cookie-based fake authentication method.
Package fakecookies implements a cookie-based fake authentication method.
Package session defines API for the session storage.
Package session defines API for the session storage.
datastore
Package datastore implements session storage over Cloud Datastore.
Package datastore implements session storage over Cloud Datastore.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL