internal

package
v0.0.0-...-d3da680 Latest Latest
Warning

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

Go to latest
Published: Dec 4, 2024 License: Apache-2.0 Imports: 45 Imported by: 0

Documentation

Overview

Package internal contains code used internally by auth/integration.

Index

Constants

View Source
const (
	// NoEmail indicates an OAuth2 token is not associated with an email.
	//
	// See Token below. We need this special value to distinguish "an email can
	// not possibly be fetched ever" from "the cached token doesn't have an email
	// yet" cases.
	NoEmail = "-"

	// UnknownEmail indicates an OAuth2 token may potentially be associated with
	// an email, but we haven't tried to fetch the email yet.
	UnknownEmail = ""

	// NoIDToken indicates it was impossible to obtain an ID token, e.g. no
	// "openid" scope in the refresh token or the provider doesn't support ID
	// tokens at all.
	NoIDToken = "-"

	// NoAccessToken indicates the access token was not returned by the provider.
	//
	// This can happen with providers that support only ID tokens.
	NoAccessToken = "-"
)
View Source
const (
	// GCAccessTokenMaxAge defines when to remove unused access tokens from the
	// disk cache.
	//
	// We define "an access token" as an instance of oauth2.Token with
	// RefreshToken set to "".
	//
	// If an access token expired older than GCAccessTokenMaxAge ago, it will be
	// evicted from the cache (it is essentially garbage now anyway).
	GCAccessTokenMaxAge = 2 * time.Hour

	// GCRefreshTokenMaxAge defines when to remove unused refresh tokens from the
	// disk cache.
	//
	// We define "a refresh token" as an instance of oauth2.Token with
	// RefreshToken not set to "".
	//
	// Refresh tokens don't expire, but they get neglected and forgotten by users,
	// staying on their disks forever. We remove such tokens if they haven't been
	// used for more than two weeks.
	//
	// It essentially logs out the user on inactivity. We don't actively revoke
	// evicted tokens though, since it's possible the user has copied the token
	// and uses it elsewhere (it happens). Such token can always be revoked from
	// Google Accounts page manually, if required.
	GCRefreshTokenMaxAge = 14 * 24 * time.Hour
)

Variables

View Source
var (
	// ErrInsufficientAccess is returned by MintToken() if token can't be minted
	// for given OAuth scopes. For example, if GCE instance wasn't granted access
	// to requested scopes when it was created.
	ErrInsufficientAccess = errors.New("can't get access token for the given account and scopes")

	// ErrBadRefreshToken is returned by RefreshToken if refresh token was revoked
	// or otherwise invalid. It means MintToken must be used to get a new refresh
	// token.
	ErrBadRefreshToken = errors.New("refresh_token is not valid")

	// ErrBadCredentials is returned by MintToken or RefreshToken if provided
	// offline credentials (like service account key) are invalid.
	ErrBadCredentials = errors.New("invalid or unavailable service account credentials")
)
View Source
var (
	// NewLUCITSTokenProvider returns TokenProvider that uses a LUCI Token Server
	// to grab tokens belonging to some service account.
	//
	// Implemented in luci_ts.go.
	NewLUCITSTokenProvider func(ctx context.Context, host, actAs, realm string, scopes []string, audience string, transport http.RoundTripper) (TokenProvider, error)

	// NewLoginSessionTokenProvider returns TokenProvider that can perform
	// a user-interacting login flow that involves a LoginSessions service.
	NewLoginSessionTokenProvider func(ctx context.Context, loginSessionsHost, clientID, clientSecret string, scopes []string, transport http.RoundTripper) (TokenProvider, error)
)

Functions

func EnableVirtualTerminal

func EnableVirtualTerminal() (supported bool, done func())

EnableVirtualTerminal is a dummy function on unix systems that always returns supported=true.

func EqualTokens

func EqualTokens(a, b *Token) bool

EqualTokens returns true if tokens are equal.

'nil' token corresponds to an empty access token.

func IsDumbTerminal

func IsDumbTerminal() bool

IsDumbTerminal determines if the current terminal supports CursorUp and CursorDown control characters.

func TokenExpiresIn

func TokenExpiresIn(ctx context.Context, t *Token, lifetime time.Duration) bool

TokenExpiresIn returns True if the token is not valid or expires within given duration.

The function returns True in any of the following conditions:

  • The token is not valid.
  • The token expires before now+lifetime.

In all other cases it returns False.

func TokenExpiresInRnd

func TokenExpiresInRnd(ctx context.Context, t *Token, lifetime time.Duration) bool

TokenExpiresInRnd is like TokenExpiresIn, except it slightly randomizes the token expiration time.

If the function returns False, the token expires past now+lifetime. In other words, it is totally safe to use the token until now+lifetime. The inverse of this statement is not correct though: if the function returns True, it doesn't necessarily imply the token will expire before now+lifetime.

The function returns True in any of the following conditions:

  • The token is not valid.
  • The token expires before now+lifetime.
  • The token expiration time is between (now+lifetime, now+lifetime+rnd), where rnd is a uniformly distributed random number between 0 and expiryRandInterval sec (which is set to 30 sec).

This is useful for processes that use multiple service account keys at around the same time. Without randomization, access tokens for such keys expire at the same time (strictly 1h after process startup, where 1h is the default token lifetime). This causes unnecessary contention on the token cache file.

Types

type CacheKey

type CacheKey struct {
	// Key identifies an auth method being used to get the token and its
	// parameters.
	//
	// Its exact form is not important, since it is used only for string matching
	// when searching for a token inside the cache.
	//
	// The following forms are being used currently:
	//  * user/<client_id> when using UserCredentialsMethod with some ClientID.
	//  * service_account/<email>/<key_id> when using ServiceAccountMethod.
	//  * gce/<account> when using GCEMetadataMethod.
	//  * iam/<account> when using IAM actor mode.
	//  * luci_ts/<account>/<host>/<realm> when using Token Server actor mode.
	//  * luci_ctx/<digest> when using LUCIContextMethod.
	Key string `json:"key"`

	// Scopes is the list of requested OAuth scopes or an ID token audience.
	//
	// The token audience is indicated by a fake scope that looks like
	// "audience:<value>". Cache keys are used only for map indexing, their exact
	// content doesn't matter. Adding a separate field (like `Audience`) to the
	// key causes complication with older binaries that read the token cache and
	// don't know about the new field, so we abuse `Scopes` field instead.
	Scopes []string `json:"scopes,omitempty"`
}

CacheKey identifies a slot in the token cache to store the token in.

func (*CacheKey) ToMapKey

func (k *CacheKey) ToMapKey() string

ToMapKey returns a string that can be used as map[string] key.

This string IS NOT PRINTABLE. It's a merely a string-looking []byte.

type DiskTokenCache

type DiskTokenCache struct {
	Context    context.Context // for logging and timing
	SecretsDir string
}

DiskTokenCache implements TokenCache on top of a file.

It uses single file to store all tokens. If multiple processes try to write to it at the same time, only one process wins (so some updates may be lost).

TODO(vadimsh): Either use file locking or split the cache into multiple files to avoid concurrency issues.

TODO(vadimsh): Once this implementation settles and is deployed everywhere, add a cleanup step that removes <cache_dir>/*.tok left from the previous version of this code.

func (*DiskTokenCache) DeleteToken

func (c *DiskTokenCache) DeleteToken(key *CacheKey) error

DeleteToken removes the token from cache.

func (*DiskTokenCache) GetToken

func (c *DiskTokenCache) GetToken(key *CacheKey) (*Token, error)

GetToken reads the token from cache.

func (*DiskTokenCache) PutToken

func (c *DiskTokenCache) PutToken(key *CacheKey, tok *Token) error

PutToken writes the token to cache.

type IDTokenClaims

type IDTokenClaims struct {
	Aud           string `json:"aud"`
	Email         string `json:"email"`
	EmailVerified bool   `json:"email_verified"`
	Exp           int64  `json:"exp"`
	Iss           string `json:"iss"`
	Nonce         string `json:"nonce"`
	Sub           string `json:"sub"`
}

IDTokenClaims contains a *subset* of ID token claims we are interested in.

func ParseIDTokenClaims

func ParseIDTokenClaims(tok string) (*IDTokenClaims, error)

ParseIDTokenClaims extracts claims of the ID token.

It doesn't validate the signature nor the validity of the claims.

type MemoryTokenCache

type MemoryTokenCache struct {
	// contains filtered or unexported fields
}

MemoryTokenCache implements TokenCache on top of in-process memory.

func (*MemoryTokenCache) DeleteToken

func (c *MemoryTokenCache) DeleteToken(key *CacheKey) error

DeleteToken removes the token from cache.

func (*MemoryTokenCache) GetToken

func (c *MemoryTokenCache) GetToken(key *CacheKey) (*Token, error)

GetToken reads the token from cache.

func (*MemoryTokenCache) PutToken

func (c *MemoryTokenCache) PutToken(key *CacheKey, tok *Token) error

PutToken writes the token to cache.

type Token

type Token struct {
	oauth2.Token

	IDToken string // an ID token derived directly from the access token or NoIDToken
	Email   string // an email or NoEmail or empty string (aka UnknownEmail)
}

Token is an oauth2.Token with an email and ID token that correspond to it.

Email may be an empty string, in which case we assume the email hasn't been fetched yet. It can also be a special NoEmail string, which means the token is not associated with an email (happens for tokens without 'userinfo.email' scope).

type TokenCache

type TokenCache interface {
	// GetToken reads the token from cache.
	//
	// Returns (nil, nil) if requested token is not in the cache.
	GetToken(key *CacheKey) (*Token, error)

	// PutToken writes the token to cache.
	PutToken(key *CacheKey, tok *Token) error

	// DeleteToken removes the token from cache.
	DeleteToken(key *CacheKey) error
}

TokenCache stores access and refresh tokens to avoid requesting them all the time.

var (
	// ProcTokenCache is shared in-process cache to use if disk cache is disabled.
	ProcTokenCache TokenCache = &MemoryTokenCache{}
)

type TokenProvider

type TokenProvider interface {
	// RequiresInteraction is true if provider may start user interaction
	// in MintToken.
	RequiresInteraction() bool

	// Lightweight is true if MintToken is very cheap to call.
	//
	// In this case the token is not being cached on disk (only in memory), since
	// it's easy to get a new one each time the process starts.
	//
	// By avoiding the disk cache, we reduce the chance of a leak.
	Lightweight() bool

	// Email is email associated with tokens produced by the provider, if known.
	//
	// May return UnknownEmail, which means the provider doesn't know the email
	// in advance and RefreshToken must be used to get the token and the email.
	// This happens, for example, for interactive providers before user has
	// logged in.
	//
	// It can also be NoEmail which means the email is not available, even if
	// caller is using RefreshToken.
	Email() string

	// CacheKey identifies a slot in the token cache to store the token in.
	//
	// Note: CacheKey MAY change during lifetime of a TokenProvider. It happens,
	// for example, for ServiceAccount token provider if the underlying service
	// account key is replaced while the process is still running.
	CacheKey(ctx context.Context) (*CacheKey, error)

	// MintToken launches authentication flow (possibly interactive) and returns
	// a new refreshable token (or error). It must never return (nil, nil).
	//
	// In actor mode 'base' is an IAM-scoped sufficiently fresh oauth token. It's
	// nil otherwise. Used by IAM-based token provider.
	MintToken(ctx context.Context, base *Token) (*Token, error)

	// RefreshToken takes existing token (probably expired, but not necessarily)
	// and returns a new refreshed token. It should never do any user interaction.
	// If a user interaction is required, a error should be returned instead.
	//
	// In actor mode 'base' is an IAM-scoped sufficiently fresh oauth token. It's
	// nil otherwise. Used by IAM-based token provider.
	RefreshToken(ctx context.Context, prev, base *Token) (*Token, error)
}

TokenProvider knows how to mint new tokens or refresh existing ones.

func NewGCETokenProvider

func NewGCETokenProvider(ctx context.Context, account string, attachScopes bool, scopes []string, audience string) (TokenProvider, error)

NewGCETokenProvider returns TokenProvider that knows how to use GCE metadata server.

func NewIAMTokenProvider

func NewIAMTokenProvider(ctx context.Context, actAs string, scopes []string, audience string, transport http.RoundTripper) (TokenProvider, error)

NewIAMTokenProvider returns TokenProvider that uses generateAccessToken IAM API to grab tokens belonging to some service account.

func NewLUCIContextTokenProvider

func NewLUCIContextTokenProvider(ctx context.Context, scopes []string, audience string, transport http.RoundTripper) (TokenProvider, error)

NewLUCIContextTokenProvider returns TokenProvider that knows how to use a local auth server to mint tokens.

It requires LUCI_CONTEXT["local_auth"] to be present in the 'ctx'. It's a description of how to locate and contact the local auth server.

See auth/integration/localauth package for the implementation of the server.

func NewServiceAccountTokenProvider

func NewServiceAccountTokenProvider(ctx context.Context, jsonKey []byte, path string, scopes []string, audience string) (TokenProvider, error)

NewServiceAccountTokenProvider returns TokenProvider that uses service account private key (on disk or in memory) to make access tokens.

Jump to

Keyboard shortcuts

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