identity

package
v1.61.2 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 13 Imported by: 3

Documentation

Overview

Package identity turns a token somebody else issued into the caller a listener acts on.

It is the consumer half of sluis: a service behind the gateway imports this, points it at the issuer, and gets back who is calling and which internal groups they are in. There is no group re-mapping anywhere in it — the name in the policy is the name in the token is the name in the role check — because a second vocabulary is a second place for access to mean something different.

Two anchors, and a handler never learns which one proved the caller. Issuer verifies a token this installation's issuer signed, for anything further away than the next pod. Cluster verifies a Kubernetes ServiceAccount token for a workload in the same cluster. Both yield Verified, so a handler cannot come to depend on the distinction — which matters, because the right anchor is decided by how far away the caller is and that can change without the handler.

sluis uses this package itself rather than keeping a copy of it. A library its own author does not use is a library nobody has tested against a real listener.

Index

Constants

View Source
const (
	HeaderForwarded     = "X-Auth-Request-Access-Token" //nolint:gosec // a header NAME, not a credential
	HeaderAuthorization = "Authorization"
)

Headers a token arrives in.

The first is what an authenticating gateway forwards; the second is the ordinary bearer, for a direct caller. Both are VERIFIED here — a listener that trusted a header instead would be only as safe as the promise that nothing but the gateway can reach it, and that promise is one NetworkPolicy edit, one port-forward or one sidecar away from false. A signature is not.

View Source
const WhoAmIPath = "/.access/whoami"

WhoAmIPath is where every adapter in this family answers, and what the TypeScript package asks by default. One path, so a console written against one service works against the next.

Variables

View Source
var ErrUnverified = errors.New("identity: the token did not verify")

ErrUnverified is returned when a token is not this verifier's to judge, or does not verify. It is deliberately one error: a caller that distinguished "wrong signature" from "wrong audience" in its response would be telling an attacker which half to fix.

Functions

func Middleware

func Middleware(verifiers ...Verifier) func(http.Handler) http.Handler

Middleware establishes the caller and passes the request on.

It does NOT refuse an unverified request, and that is deliberate: a listener serves pages that run before anybody is established — a health endpoint, a login page, a landing page — and a middleware that decided for them would be a middleware every such route has to be excluded from. Gate in the handler with FromContext, or wrap the routes that need it in Require.

Several verifiers are tried in order and the first that answers wins. A token one of them RECOGNISED and refused stops the chain: a bearer with a bad signature must not be retried as a ServiceAccount token.

func Require

func Require(groups ...string) func(http.Handler) http.Handler

Require refuses anything Middleware did not establish, and anything holding none of the groups named.

Naming no group means "any caller this installation vouches for", which is a real posture: a service whose whole audience is already gated by the issuer's `requires` needs no second list.

func TokenFrom

func TokenFrom(r *http.Request) string

TokenFrom reads the token a request carries, from either header.

func WhoAmI

func WhoAmI(version string) http.Handler

WhoAmI is the endpoint a console asks who it is talking to.

It answers rather than refuses when nobody is established, because "signed out" is something a console has to render and a 401 would make it indistinguishable from a service that is broken.

func WithVerified

func WithVerified(ctx context.Context, who Verified) context.Context

WithVerified puts a caller in a context, for a test or an adapter this package does not ship.

Types

type Cluster

type Cluster struct {
	// Review asks the API server whether a token is genuine and for
	// which audiences, and returns the authenticated username.
	Review func(ctx context.Context, token string, audiences []string) (string, error)
	// Audience the token must have been minted for. Without one, every
	// mounted ServiceAccount token in the cluster is a valid caller.
	Audience string
	// Name of this cluster, put into the subject so that the same
	// namespace and name on two clusters are two callers.
	Name string
	// Groups the workload is treated as holding. A TokenReview says WHO,
	// never what they may do: the policy is not reachable from here, so
	// a listener states what a proven workload is entitled to.
	Groups []string
}

Cluster verifies a Kubernetes ServiceAccount token with a TokenReview, for a workload calling a service in the SAME cluster.

Anything further away uses Issuer: a workload in another cluster exchanges its token at the issuer first, and arrives here as an ordinary bearer. That is the two-anchor rule, and the reason this type exists at all is that asking the API server is cheaper and more immediate than a round trip through an issuer for the pod next door.

Review is supplied rather than built so that this package does not drag Kubernetes client libraries into every consumer that only needs the issuer. Pass k8s.io/client-go's TokenReview, or the wrapper sluis's own `kube` package offers.

func (*Cluster) Verify

func (c *Cluster) Verify(ctx context.Context, token string) (Verified, error)

Verify checks a ServiceAccount token and returns the caller.

type Issuer

type Issuer struct {
	// URL is the issuer, exactly as it appears in a token's `iss`.
	URL string
	// Audience the token must carry: this service's own client id.
	//
	// Empty accepts any audience, which is almost always wrong: a token
	// minted for another service is a perfectly valid token, and taking
	// it here makes every audience that issuer serves a way in.
	Audience string
	// Client fetches discovery and the keys. Nil uses one with a
	// ten-second timeout.
	Client *http.Client
	// contains filtered or unexported fields
}

Issuer verifies a token sluis signed, against the key set it publishes.

Discovery is lazy and cached: a service must start whether or not the issuer is up, and an issuer that is down must not be a service that will not boot. The first request after it returns is the one that pays for discovery, and the key set refetches itself when a signature names a key it has not seen, which makes key rotation a non-event.

func (*Issuer) Verify

func (i *Issuer) Verify(ctx context.Context, token string) (Verified, error)

Verify checks a bearer token and returns the caller.

type Verified

type Verified struct {
	// Subject is the token's `sub`: an address for a person, and
	// `<cluster>:k8s:<namespace>:<name>` for a workload.
	Subject string
	// Email is the person's address, empty for a workload.
	Email string
	// Name is what the directory calls the person, for display. Empty for
	// a workload, a recovery sign-in, or a person the directory gives no
	// name -- show the address then. Never authorize on it.
	Name string
	// GivenName and FamilyName are its halves, for a UI that wants them.
	GivenName  string
	FamilyName string
	// ClientID is the client the token was minted for: the `azp` claim,
	// or `client_id` where that is absent. For a record of which software
	// called, never a thing to authorize on.
	ClientID string
	// Groups are the internal groups the policy put the caller in,
	// verbatim from the token. Never re-mapped.
	Groups []string
	// ServiceAccount is set when the caller is a workload rather than a
	// person, parsed from the subject.
	ServiceAccount *policy.ServiceAccountRef
}

Verified is a caller, after the signature was checked.

It carries what a listener authorizes on and nothing else. There is no field saying which anchor proved it, on purpose.

func FromContext

func FromContext(ctx context.Context) (Verified, bool)

FromContext returns the caller a Middleware established, if any.

The second return is the whole contract: false means nobody was established, never "somebody with no groups", so a handler cannot accidentally treat an unauthenticated request as an empty-group one.

func (Verified) Has

func (v Verified) Has(group string) bool

Has reports whether the caller is in an internal group.

func (Verified) HasAny

func (v Verified) HasAny(groups ...string) bool

HasAny reports whether the caller is in any of them, which is how a client's `requires` reads and how a listener should gate.

type Verifier

type Verifier interface {
	Verify(ctx context.Context, token string) (Verified, error)
}

Verifier is what Middleware needs: anything that turns a token into a caller. Issuer and Cluster both satisfy it.

Directories

Path Synopsis
Package resource is the resource-server half of the Model Context Protocol's authorization spec, for one protected resource.
Package resource is the resource-server half of the Model Context Protocol's authorization spec, for one protected resource.

Jump to

Keyboard shortcuts

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