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 thier 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 the primary encryption key and to register an OAuth2 client with the ID provider. Instructions below assume you are using the Google Accounts ID provider.

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.

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

cd server/cmd/tink-aead-key
go run main.go login
go run main.go create sm://<cloud-project>/tink-aead-primary

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

cd server/cmd/tink-aead-key
go run main.go rotate 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 version of `oauth-client-secret` Google Secret Manager 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 \
    ...
    -encrypted-cookies-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).

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"
)

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
}

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 *http.Request) (*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) 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.
	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
}

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
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.
fakecookies
Package fakecookies implements a cookie-based fake authentication method.
Package fakecookies implements a cookie-based fake authentication method.