auth

package
v1.15.0 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package auth implements the gateway's OAuth 2.0 resource server (Bearer JWT validation against an issuer allowlist via cached JWKS, with exact audience matching) and the upstream credential strategies.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ScopesFromClaims added in v1.15.0

func ScopesFromClaims(claims map[string]any) []string

ScopesFromClaims reads the OAuth scopes a verified token carries.

There is no single spelling to read, so both in use are accepted. RFC 6749 §3.3 and RFC 9068 §2.2.3 make "scope" a *space-delimited string*, which is why a policy could not previously express a scope requirement at all: the claims matcher compares whole values, and "write" is not equal to "read write admin". Entra spells the same thing "scp", and some issuers send either as a JSON array. All four shapes are read here so a rule can be written once against the concept rather than against an issuer's spelling.

"scope" is read first and "scp" only when it yields nothing, rather than merging the two: an issuer that sends both sends the same set twice, and merging would silently union two claims that disagreed.

func TrustAnchorClient added in v1.11.0

func TrustAnchorClient() *http.Client

TrustAnchorClient returns the HTTP client fold uses to fetch trust-anchor documents — issuer JWKS sets and the EMA IdP's JWKS. It matches the posture of every other outbound trust-path client (discovery, token endpoints, audit webhooks): a bounded timeout so a slow IdP cannot pin verification goroutines forever, and redirects refused outright — a redirect is never a legitimate step in fetching a key set, and following one would let whoever answers the configured URI hand the fetch to a host of their choosing.

http.DefaultClient has neither property; never wire it here.

func WithPrincipal

func WithPrincipal(ctx context.Context, p *Principal) context.Context

WithPrincipal stores p in ctx.

Types

type EMA

type EMA struct {

	// OnExchange, when set, is told the outcome of every token exchange —
	// including the refusals. The gateway wires it into the audit trail:
	// this endpoint is an authorization server, and a detected ID-JAG
	// replay, or an attacker fuzzing assertions under the rate limit, is
	// exactly the traffic a SIEM exists to see. Set before serving; not
	// synchronized after.
	OnExchange func(TokenExchange)
	// contains filtered or unexported fields
}

EMA is fold's embedded MCP Authorization Server, deliberately one grant wide: exchange an enterprise-IdP-issued ID-JAG (Identity Assertion JWT Authorization Grant) for a short-lived fold-signed access token (Enterprise-Managed Authorization). Everything the gateway later accepts has aud = fold, which keeps upstream token exchange coherent.

func NewEMA

func NewEMA(cfg *config.Auth, client *http.Client, replay state.Once) (*EMA, error)

NewEMA loads the ES256 signing key named by signingKeyRef (a PKCS#8 PEM in an environment variable) and assembles the exchange service.

func (*EMA) AuthorizationServerMetadata added in v1.15.0

func (m *EMA) AuthorizationServerMetadata() map[string]any

AuthorizationServerMetadata is the RFC 8414 document for the embedded authorization server.

fold lists itself in `authorization_servers` of its RFC 9728 protected- resource metadata whenever EMA is on, which tells a client to discover this document — so not serving it left the advertisement pointing at a 404 and forced every operator to configure the token endpoint out of band. The document is deliberately narrow: it describes the one grant this server implements and claims nothing else.

func (*EMA) AuthorizationServerMetadataPaths added in v1.15.0

func (m *EMA) AuthorizationServerMetadataPaths() []string

AuthorizationServerMetadataPaths returns every well-known path this document must be reachable at.

RFC 8414 §3.1 locates the document by inserting the well-known segment *before* the issuer's path, so an issuer with a path is discovered at "/.well-known/oauth-authorization-server/{path}" rather than at the root. fold advertises itself in the RFC 9728 document using whatever auth.resource says, so whichever form that takes has to resolve — hence both, when they differ.

func (*EMA) Issuer

func (m *EMA) Issuer() string

Issuer returns the issuer string of fold-minted tokens (the resource URI).

func (*EMA) PublicKey

func (m *EMA) PublicKey() *ecdsa.PublicKey

PublicKey returns the verification key for fold-minted tokens.

func (*EMA) ServeAuthorizationServerMetadata added in v1.15.0

func (m *EMA) ServeAuthorizationServerMetadata(w http.ResponseWriter, _ *http.Request)

ServeAuthorizationServerMetadata answers the RFC 8414 well-known paths.

CORS mirrors what the SDK sets on the protected-resource document: a browser-based MCP client discovers this from script, so without the header it can read fold's RFC 9728 metadata and then fail on the authorization server it points at.

func (*EMA) ServeJWKS

func (m *EMA) ServeJWKS(w http.ResponseWriter, _ *http.Request)

ServeJWKS answers GET /.well-known/jwks.json with the public key set.

func (*EMA) ServeToken

func (m *EMA) ServeToken(w http.ResponseWriter, r *http.Request)

ServeToken answers POST /oauth/token — the ID-JAG exchange. The caller is unauthenticated by design (the assertion is the credential); the gateway rate-limits this handler before it runs. Every terminal response reports through OnExchange — refusals included — so the trail this endpoint produces matches the single-exit-door rule the rest of the gateway obeys.

type Principal

type Principal struct {
	Subject string    // "sub" claim
	Issuer  string    // "iss" claim
	Groups  []string  // from the issuer's configured groups claim
	Scopes  []string  // OAuth scopes, from the "scope" or "scp" claim
	Token   string    // the raw bearer token (for passthrough / token-exchange)
	Expiry  time.Time // token expiration

	// Claims is the verified token's full claim set, for attribute-based
	// policy (policy subjects' "claims" matcher). Values are as decoded
	// from JSON: string, float64, bool, nil, []any, map[string]any.
	Claims map[string]any
}

Principal is the authenticated caller of a gateway request.

func PrincipalFromContext

func PrincipalFromContext(ctx context.Context) *Principal

PrincipalFromContext returns the authenticated principal, or nil when the gateway runs with auth disabled.

type TokenExchange added in v1.11.0

type TokenExchange struct {
	// Outcome is "minted" for a successful exchange, "replayed" for an
	// ID-JAG presented twice (the security event), or the OAuth error code
	// returned otherwise ("invalid_request", "unsupported_grant_type",
	// "invalid_grant", "server_error").
	Outcome string
	Detail  string // the error_description sent to the caller ("" when minted)
	Subject string // the assertion's subject, when validation got that far
	Issuer  string // the IdP issuer the assertion was verified against
}

TokenExchange describes one terminal /oauth/token response.

type UpstreamCredentials

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

UpstreamCredentials attaches credentials to requests bound for one upstream, according to its configured strategy:

none               — nothing attached
static             — an API key from the secret store (env vars)
passthrough        — the caller's bearer token, forwarded as-is
client-credentials — a service-identity token (OAuth 2.0 CC grant),
                     cached until 60s before expiry
token-exchange     — RFC 8693: the caller's token exchanged for an
                     upstream-audience token, cached per subject

func NewUpstreamCredentials

func NewUpstreamCredentials(cfg *config.UpstreamAuth, client *http.Client) *UpstreamCredentials

NewUpstreamCredentials builds the credential injector for one upstream. A nil cfg (or strategy "none") attaches nothing.

The caller's client is wrapped so token-endpoint requests never follow a redirect. Those requests carry the most sensitive material fold handles — the client secret under client-credentials, and the caller's own bearer token as subject_token under token-exchange — and Go replays a POST body verbatim on 307/308, so a token endpoint that redirects (compromised, misconfigured, or hosting an open redirect) would otherwise hand both to whatever host it names. Refusing every redirect is safe here: token endpoints answer 200 with the token, and a redirect is never a legitimate step in the grant.

func (*UpstreamCredentials) Apply

func (c *UpstreamCredentials) Apply(ctx context.Context, hdr http.Header) error

Apply sets credential headers on hdr for a request running under ctx.

func (*UpstreamCredentials) PerRequest

func (c *UpstreamCredentials) PerRequest() bool

PerRequest reports whether this strategy derives credentials from the caller (so they must be attached per request, not per session).

type Verifier

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

Verifier validates gateway bearer tokens: trusted issuer (checked before any network I/O), signature via cached JWKS, exact audience match.

func NewVerifier

func NewVerifier(cfg *config.Auth, client *http.Client) *Verifier

NewVerifier builds a verifier from the auth config section. Only "direct" issuers are trusted for straight token presentation: an "exchange" issuer's tokens (ID-JAGs) must go through the EMA exchange — accepting one directly would let it stand in for a fold access token.

func (*Verifier) TrustLocal

func (v *Verifier) TrustLocal(issuer string, key any)

TrustLocal registers an issuer whose tokens verify against a locally held public key — fold's own EMA-minted tokens — with no JWKS fetch.

func (*Verifier) Verify

func (v *Verifier) Verify(ctx context.Context, raw string) (*Principal, error)

Verify validates a raw bearer token and returns the principal it names.

Jump to

Keyboard shortcuts

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