Documentation
¶
Overview ¶
Package edge is the identity boundary: it strips what a client claimed, verifies the credential, and writes the headers everything behind it reads.
It is SEPARATE from the decision (package authz) on purpose. A decision is a pure function of claims — Can(subject, verb, path) — and every plugin in the fleet asks it. An edge touches a request, so it links a transport. Folding the two together made the decision drag the whole HTTP stack into every caller that only wanted to ask a question, which is the same error as a dependency struct naming every client its callers might want.
So: `authz` is the question, imported everywhere. `authz/edge` is the answer's custody, imported ONLY by whoever holds the edge role (HIP-0519).
ONE EDGE, NOT ONE PER TRANSPORT. The estate serves on zip and also fronts an HTTP proxy edge, and when each held its own copy of this reasoning they drifted: the platform-authority header was corrected in one and went on being written from an org role in the other. So the rules here take a Headers — the three operations rewriting an identity needs — which net/http's own header type satisfies as written and a zip request reaches through Of.
Index ¶
- Variables
- func Apply(h Headers, cl *authz.Claims, selected string, at *authz.Grant)
- func Basic(h Headers) string
- func Bearer(h Headers) string
- func Cookie(h Headers) string
- func Strip(h Headers) (claimedOrg string)
- func Token(h Headers) string
- type Header
- type Headers
- type Keys
- type Peeker
- type Verifier
Constants ¶
This section is empty.
Variables ¶
var ErrNoToken = errors.New("edge: no credential")
ErrNoToken reports that no credential was present. It is distinct from an invalid one so a caller can fall through to a session or anonymous path instead of refusing — "absent" and "wrong" are different answers.
Functions ¶
func Apply ¶ added in v1.10.26
Apply writes Render's output to the headers the next tier reads. It is Strip's counterpart: one deletes what a client claimed, the other writes what a token proved.
func Basic ¶
Basic extracts the token from an HTTP Basic Authorization header: the password, falling back to the username when the password is empty. This is how a `go` client authenticates to a module proxy from ~/.netrc —
machine goproxy.hanzo.ai login <email> password <IAM token>
— so the username is an address, not a secret, and the token may arrive in either field.
func Cookie ¶
Cookie extracts an access token from the session cookie names a first-party sign-in mints.
The Cookie header is parsed here rather than delegated to a transport's own accessor, so both edges read the same names in the same order. A duplicate name takes the FIRST value: a later one is what an attacker who can set a cookie on a sibling host appends, and last-wins would let it overwrite the real session.
func Strip ¶
Strip deletes every identity header a client supplied and returns the org it had CLAIMED, which is an input to scope selection and never an authority.
The claimed org is RETURNED rather than left on the request precisely so it cannot be mistaken for a verified one: the only way to act on it is to hand it to Render, which admits it only if the signed membership set does.
The capture and the strip are ONE operation because the selection must be read BEFORE the header is deleted. A separate "read it first" function would be an ordering trap that fails silently — the org switcher would simply stop working — and a caller that ignores the return value keeps the safe behaviour: nothing claimed, nothing written.
Types ¶
type Header ¶ added in v1.10.18
type Header struct{ Name, Value string }
Header is one header name and its value.
func Render ¶ added in v1.10.26
Render is the identity AS A VALUE: the headers a verified token entitles this request to carry, with the zero values already dropped.
It RENDERS rather than mints. Minting is issuing authority, and only IAM does that — it signs the token. Nothing here creates any: the authority already exists, signed, and this restates it in the form a transport that cannot carry a token can read. Calling both operations "mint" put the issuer's job and the edge's under one word, and a word that covers two operations is a word that stops distinguishing them.
selected is the org the client asked to act in, as returned by Strip; it is honoured only when the signed membership set admits it, or when the caller may masquerade. at is the grant the edge RESOLVED for this request, or nil when none was requested — and it is rendered only when it belongs to THIS subject, because an edge forwarding a grant resolved for anyone else is the whole class of bug this package exists to prevent, one tier deeper.
TWO ADMIN SCOPES, TWO HEADERS, never interchangeable. HeaderUserAdmin is PLATFORM authority — a human whose HOME org is the reserved admin org. HeaderUserOrgAdmin is admin of one's OWN org, resolved against the EFFECTIVE org so an operator viewing another tenant carries sudo without that tenant's self-service authority.
Writing the platform header from Claims.IsAdmin — the ORG-role bit — is the escalation this package makes unrepeatable: every org owner arrived as a platform admin, and the money gates read that header.
Absent is distinct from empty throughout: a header whose value is the zero value is not in the slice, so applying it never writes a header the token did not earn and never blanks one.
type Headers ¶ added in v1.10.20
Headers is the header set an edge rewrites. It is the smallest surface the rules below use, so a transport qualifies by having it rather than by being named here: net/http's http.Header satisfies it with no adapter at all.
type Keys ¶ added in v1.10.19
type Keys struct {
// contains filtered or unexported fields
}
Keys is the edge's JWKS cache: it fetches the published set, caches it for a TTL, and resolves a token's `kid` to the keys it may be verified against. It satisfies authz.Keys through Resolve.
STALE ON ERROR. A publisher that is briefly unreachable must not 401 the estate, so a failed refresh keeps serving the keys already held. Signing keys are long lived and a rotation is additive, so serving a slightly old set costs at most one retry for a token signed under a brand-new key — where failing closed on every token would be an outage.
func NewKeys ¶ added in v1.10.19
NewKeys returns a cache for a JWKS URL. A non-positive ttl takes the default.
func (*Keys) Resolve ¶ added in v1.10.19
Resolve returns the keys published under kid, refreshing past the TTL. It is the authz.Keys the decision consumes:
authz.Verify(token, keys.Resolve, issuer)
An empty kid resolves nothing. Verify already refuses a token that names no key — trying every key in a set is how a reader ends up accepting a signature from a key the token never claimed — and this agrees with it rather than offering a second, looser path to the same question.
type Peeker ¶ added in v1.10.24
Peeker is the OTHER shape a header set comes in: fasthttp's, where reading returns bytes. A zip request's headers are one — reach them with
edge.Of(&c.Fiber().Request().Header)
It is stated as an interface rather than imported as a type on purpose. Naming the type would make this package depend on the whole zip/fasthttp stack, and then every consumer would link it for one adapter — which is the same mistake as a decision that drags an HTTP client into a caller that only wanted to ask a question. Neither transport is named here; both qualify by shape.
type Verifier ¶ added in v1.10.23
type Verifier struct {
// contains filtered or unexported fields
}
Verifier is the whole credential check: the keys to verify against, the issuer to require, and the audiences this deployment accepts.
It exists HERE, once, because every service that terminates identity needs exactly this and each one that wrote it again got it slightly wrong — one tried every key in the set rather than the one the token named, another skipped the issuer comparison whenever its configured issuer was empty. What legitimately differs per deployment is the VALUES (and the environment variable names they come from), so a deployment supplies those and nothing else.
func NewVerifier ¶ added in v1.10.23
NewVerifier builds a Verifier. A non-positive ttl takes the key cache's default.
The two lists are NOT symmetric, and the asymmetry is the point. An empty AUDIENCE list disables that check, because `aud` names the requesting client rather than the accepting server, so having no opinion about it is a coherent position. An empty ISSUER list refuses everything, because the issuer is who SIGNED the token and having no opinion about that is not a position, it is an open door.
func (*Verifier) Verify ¶ added in v1.10.23
Verify extracts the credential a request carries — Bearer, then HTTP Basic, then a session cookie — and verifies it.
func (*Verifier) VerifyRaw ¶ added in v1.10.23
VerifyRaw verifies a credential held out of band, such as the token an OAuth2 code-exchange handler has just received.
Signature, key id, algorithm, issuer and expiry are authz.Verify's; the audience allowlist is applied after. The order is not incidental: an unverified token's claims are evidence of nothing, so no claim is read off it before the signature holds.