access

package
v1.65.1 Latest Latest
Warning

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

Go to latest
Published: Oct 5, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package access decides who is at the console and what they may do.

It never proves who anyone is. A principal arrives already authenticated — by a bearer an authenticating gateway forwarded, by a sign-in the hub delegated to a connected directory or an external issuer, or by the break-glass admin account — and this package turns that principal into an identity, by asking the directory which groups the account is in and running the policy.

The hub holds no role vocabulary of its own: an identity is an operator because the policy puts it in the operators group, exactly as any other relying party's roles work.

Index

Constants

View Source
const (
	ConnectCookieName = "access_roster_connect"
	LoginCookieName   = "access_roster_login"
	LinkCookieName    = "access_roster_link"
)

The two flows' state cookies. Each carries its flow's state alongside the URL, so that a callback proves it belongs to the browser that started the flow.

Two names, because the flows are different in the way that matters: consent is an operator granting this hub access to a company, sign-in is a person proving who they are. One cookie would let a callback finish a flow the browser did not start.

A third for a person linking a GitHub account, for the same reason: an operator connecting an organisation in one tab and linking their own account in another must not have either flow finish the other.

View Source
const CookieName = "access_roster_session"

CookieName is the console's session cookie.

View Source
const SessionKeyBytes = 32

SessionKeyBytes is the length of a session-signing key.

Variables

View Source
var ErrBadState = errors.New("access: state is not valid")

ErrBadState is returned for a state that is forged, stale or malformed.

View Source
var ErrNoSession = errors.New("access: no session")

ErrNoSession is returned when a request carries no readable session.

View Source
var ErrSuspended = errors.New("access: the account is not live")

ErrSuspended is returned when the directory says, authoritatively, that the account signing in is not live.

Functions

func ConnectCookie

func ConnectCookie(value string, secure bool, ttl time.Duration) *http.Cookie

ConnectCookie builds the consent flow's state cookie, and — with an empty value — the one that clears it.

Both come from here because they were built in two places and drifted: the cookie was set Secure and cleared without it. Attributes are not part of a cookie's identity, so the clearing still worked, but a browser being asked to store a cookie over plain HTTP on a site that only ever speaks HTTPS is the kind of difference that stops being harmless the moment someone copies it.

func LinkCookie

func LinkCookie(value string, secure bool, ttl time.Duration) *http.Cookie

LinkCookie is the same for linking a GitHub account.

func LoginCookie

func LoginCookie(value string, secure bool, ttl time.Duration) *http.Cookie

LoginCookie is the same for the sign-in flow.

func NewSessionKey

func NewSessionKey() ([]byte, error)

NewSessionKey returns a fresh random session key.

func ServiceAccountSubject added in v1.58.0

func ServiceAccountSubject(namespace, name string) string

ServiceAccountSubject is the username a Kubernetes API server gives a ServiceAccount: what a policy's service_account matcher and a recovery account are compared against. It lives here, with no cluster client behind it, so the Lambda build can name a subject without linking one.

Types

type Authorizer

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

Authorizer turns principals into identities.

func NewAuthorizer

func NewAuthorizer(set *policy.Set, dir Directory, holdWindow time.Duration) *Authorizer

NewAuthorizer returns an authorizer over a policy and a directory.

func (*Authorizer) Authorize

func (a *Authorizer) Authorize(ctx context.Context, p Principal) (Identity, error)

Authorize resolves a principal into an identity.

A recovery sign-in is an operator by construction: it exists for the day the policy or the directory is what is broken, and it was already authorised — by the cluster's RBAC, or by holding the one generated password — before it reached here. Everyone else is resolved through the directory and the policy, and an authoritative "not live" is a refusal rather than an empty role — a suspended account must not reach the console at all.

func (*Authorizer) Explain

func (a *Authorizer) Explain(ctx context.Context, proof Proof) (Explanation, error)

Explain answers what a proof would effectively get. Unlike Authorize it never refuses: a suspended account is reported as suspended, which is the whole point of looking.

func (*Authorizer) HoldersOf

func (a *Authorizer) HoldersOf(people []hub.Person, group, client string) []Holder

HoldersOf resolves an internal group, or a client, to the people who hold it right now.

The policy says which directory groups count; only the directory knows who is in them. Answering that is what makes an access review possible at all, and it is a read over snapshots already in memory.

A suspended account still appears, marked: seeing that a leaver is still counted somewhere is the whole point of looking.

func (*Authorizer) Policy

func (a *Authorizer) Policy() *policy.Set

Policy returns the policy in force.

func (*Authorizer) SetClock

func (a *Authorizer) SetClock(now func() time.Time)

SetClock replaces the clock. For tests.

type Binding

type Binding struct {
	// Bind is the workspace being reconnected, empty for a new one.
	Bind string
	// Actor is the identity that was authorised when the flow began.
	//
	// It is here because a third-party callback is the one request in a
	// flow whose identity cannot be assumed: it arrives as a redirect from
	// Google, and the route it lands on need not be the one the gateway
	// authenticates. The state is signed by this hub and pinned to the
	// browser by a cookie the callback checks, so it can say who started
	// the flow when nothing else in the request can.
	//
	// The authorisation itself still happens at the start, where an
	// operator asked for the consent. This only carries the answer.
	Actor string
	// Owner is the directory workspace id the flow will record as the
	// owner of what it connects, empty for none. It is chosen where the
	// flow begins, under the role question asked then, and read back by the
	// callback that creates the record: signed, so a browser cannot change
	// it on the way.
	Owner string
}

Binding is what a state carries from the start of a flow to its callback.

type ClientAdmission

type ClientAdmission struct {
	ID       string
	Kind     string
	Requires []string
	Admitted bool
	Lifetime time.Duration
}

ClientAdmission is one relying party and whether a proof reaches it.

type Directory

type Directory interface {
	ResolveUser(ctx context.Context, email string, maxAge *time.Duration) (hub.UserResult, error)
}

Directory is the part of the hub this package needs: the groups an address is in, whether the account is live, and whether that answer may be acted on.

type Explanation

type Explanation struct {
	Email           string
	Workspace       string
	GivenName       string
	FamilyName      string
	InDomain        bool
	Found           bool
	Suspended       bool
	Authoritative   bool
	DirectoryGroups []string
	Result          policy.Result
	Role            Role
	Clients         []ClientAdmission
}

Explanation is what a proof effectively gets, and why.

type Holder

type Holder struct {
	Email         string
	GivenName     string
	FamilyName    string
	Live          bool
	Authoritative bool
	Via           []string
	Lifetime      time.Duration
}

Holder is one account that holds a group or reaches a client, and why.

type Identity

type Identity struct {
	Email      string
	Subject    string
	GivenName  string
	FamilyName string
	Source     Source
	Role       Role
	// Groups are the internal groups held.
	Groups []string
	// Held carries the same with the reason for each.
	Held []policy.Held
	// Claims is what a token for this identity would carry.
	Claims map[string]any
	// Lifetime is how long such a token would live.
	Lifetime time.Duration
	// Scopes are roles held over ONE workspace each, keyed by workspace
	// id. They exist because the hub is built for several companies and
	// the roles above are not: without them every tenant's administrator
	// administers every other tenant's directory, including disconnecting
	// it and reading its credential state.
	//
	// A scope never widens the global role and never narrows it. An
	// identity with a global role may act on every workspace; an identity
	// with only scopes may act on the ones it names.
	Scopes map[string]Role
}

Identity is an authorized caller: a principal, the internal groups the policy puts it in, and what those groups grant.

func (Identity) Can

func (i Identity) Can(role Role) bool

Can reports whether the identity holds at least the given role over the whole installation. Scopes do not count: they are about one workspace, and everything that asks this is about all of them — the policy, the OAuth client, connecting a directory that does not exist yet.

func (Identity) CanAnywhere

func (i Identity) CanAnywhere(role Role) bool

CanAnywhere reports whether the identity holds the role over the installation or over any single workspace. It is the gate on a page that then shows only what the identity may see.

func (Identity) CanFor

func (i Identity) CanFor(role Role, workspace string) bool

CanFor reports whether the identity may act on one workspace: because it holds the role over the installation, or because it holds it there.

func (Identity) Name

func (i Identity) Name() string

Name is the person's name as the directory has it, or who they are when it has none.

func (Identity) Who

func (i Identity) Who() string

Who is the identity as it should be written down: the address where there is one, the subject where there is not.

A recovery sign-in completes as a ServiceAccount and has NO address — so every line that reached for Email alone recorded a blank for exactly the sign-in whose actions most need a name against them.

func (Identity) Workspaces

func (i Identity) Workspaces(role Role) []string

Workspaces returns the workspace ids this identity may act on at the given role, or nil when it may act on all of them. Nil is "everything", not "nothing": a global role is not a list.

type Principal

type Principal struct {
	Email   string
	Subject string
	Source  Source
	Issuer  string
	Claims  map[string][]string
	// ServiceAccount is set when the forwarded token names a cluster
	// workload rather than a person -- a recovery sign-in at the issuer,
	// which completes as the account and not as an address. What it is
	// entitled to is the policy's `service_account` matchers, the same
	// table that answers for a workload exchanging a token.
	ServiceAccount *policy.ServiceAccountRef
	// AWS is set when the bearer is an AWS IAM role's outbound identity
	// federation token (a Lambda controller reading the console). What it is
	// entitled to is the policy's `aws` matchers, the same table that answers
	// for a role exchanging a token.
	AWS *policy.AWSRole
}

Principal is an authenticated caller, before the policy has run.

type Proof

type Proof struct {
	Email          string
	GitHub         *policy.GitHubClaims
	ServiceAccount *policy.ServiceAccountRef
	AWS            *policy.AWSRole
}

Proof is what to explain: a person by address, a CI job, or a cluster workload. Exactly the three kinds the policy can resolve, so the same page answers what a pipeline is entitled to as well as what a person is.

func (Proof) IsPerson

func (p Proof) IsPerson() bool

IsPerson reports whether the proof is an address.

type Role

type Role string

Role is what an identity may do in this console.

const (
	RoleNone     Role = ""
	RoleViewer   Role = "viewer"
	RoleOperator Role = "operator"
)

The roles. Operator implies viewer.

func (Role) Implies

func (r Role) Implies(other Role) bool

Implies reports whether holding r also confers other.

type Sessions

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

Sessions issues and reads the console's session cookie.

The cookie is the principal, signed with the hub's session key and short-lived. It is deliberately stateless: there is no session table to keep, and revoking everyone at once is rotating the key. What that cannot do — ending one person's session before it expires — is the gateway's job in an installation that has one, and the reason the lifetime is short in one that does not.

func NewSessions

func NewSessions(key []byte, lifetime time.Duration, secure bool) (*Sessions, error)

NewSessions returns a session codec. The key must be SessionKeyBytes long; secure marks the cookie so, which a plain-HTTP development run turns off.

func (*Sessions) Clear

func (s *Sessions) Clear(w http.ResponseWriter)

Clear removes the cookie.

func (*Sessions) Issue

func (s *Sessions) Issue(w http.ResponseWriter, p Principal) error

Issue signs a principal into the response's cookie.

func (*Sessions) Lifetime

func (s *Sessions) Lifetime() time.Duration

Lifetime is how long an issued session lasts.

func (*Sessions) Read

func (s *Sessions) Read(r *http.Request) (Principal, error)

Read returns the principal a request carries.

func (*Sessions) Secure

func (s *Sessions) Secure() bool

Secure reports whether cookies are marked Secure, so that everything else this installation sets matches its sessions rather than guessing.

func (*Sessions) SetClock

func (s *Sessions) SetClock(now func() time.Time)

SetClock replaces the clock. For tests.

type Source

type Source string

Source says how a principal was established.

const (
	// SourceForwarded is a bearer an authenticating gateway forwarded —
	// the normal path where a proxy fronts every console.
	SourceForwarded Source = "forwarded"
	// SourceDirectory is the hub's own sign-in with a connected directory,
	// for a standalone installation.
	SourceDirectory Source = "directory"
	// SourceOIDC is the hub's own sign-in against an external issuer.
	SourceOIDC Source = "oidc"
	// SourceRecovery is the way in for the day the ordinary one is
	// broken: proven cluster access, or a generated password outside a
	// cluster.
	SourceRecovery Source = "recovery"
	// SourceWorkload is a workload presenting its own ServiceAccount
	// token as the bearer, verified against its cluster's published key
	// set — a controller calling the console's API, not a person at it.
	//
	// It is NOT recovery, and the difference is the whole reason it is a
	// source of its own. Recovery is an operator by construction; a
	// workload is whatever the policy's `service_account` matchers make
	// it, which for a controller that only reads is a viewer and nothing
	// more.
	SourceWorkload Source = "workload"
)

The sources, in the order an installation usually meets them.

type StateCodec

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

StateCodec signs the opaque state an OAuth flow carries. It keeps nothing: the state is its own record, and the cookie beside it is what makes the callback the same browser's.

func NewStateCodec

func NewStateCodec(key []byte, ttl time.Duration) *StateCodec

NewStateCodec returns a codec over the hub's session key.

func (*StateCodec) Issue

func (c *StateCodec) Issue(bind string) (string, error)

Issue returns a signed state bound to a workspace and to nobody.

func (*StateCodec) IssueAs

func (c *StateCodec) IssueAs(b Binding) (string, error)

IssueAs returns a signed state carrying the whole binding.

func (*StateCodec) SetClock

func (c *StateCodec) SetClock(now func() time.Time)

SetClock replaces the clock. For tests.

func (*StateCodec) Verify

func (c *StateCodec) Verify(state string) (string, error)

Verify checks a state and returns what it was bound to.

func (*StateCodec) VerifyBinding

func (c *StateCodec) VerifyBinding(state string) (Binding, error)

VerifyBinding checks a state and returns everything it carries.

Jump to

Keyboard shortcuts

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