Machine Identity
A service proves who it is with one OIDC client_credentials call to hanzo.id; the org in its token authorizes every hyphen-delimited sub-scope under it.
Abstract
A non-human caller — a validator, an operator, a venue daemon — proves who
it is with one call: OIDC client_credentials to hanzo.id. The returned
JWT carries an org. That org is the whole authority. A token for org lux
reaches every sub-scope written lux-<x> — lux-infra, lux-mainnet —
and nothing else.
This is not DID (LP-10093, an on-chain identity a user owns) and not KMS (LP-7326, which holds the secrets). It is only the answer to "who is this service, and what may it touch."
Invariant
A machine authenticates one way —
client_credentialsagainst hanzo.id. Its token's orgOauthorizes scopeSwhenS == OorSbegins withO + "-". No other prefix matches.
Why (first principles)
A service has no password to type and no browser to redirect. The OAuth2
client_credentials grant is exactly this case: a clientId/clientSecret
pair exchanged for a bearer token, no user in the loop. One grant, one path
— a second auth path is a second thing to secure and a second place to be
wrong.
The org boundary is a value, not a list. KMS never stores "lux may read
lux-infra, lux-mainnet, …"; it derives the answer from the token's org and
the requested scope. Add a project lux-staking and the same lux token
reaches it with no new grant. The hyphen is what keeps the value safe: lux
authorizes lux-infra but not luxx, because only O + "-" extends the
scope — a bare prefix would leak across orgs.
Enforcement
The grant is one handler. POST /v1/kms/auth/login takes a
clientId/clientSecret, posts grant_type=client_credentials to
hanzo.id, and returns the access token — "Machine identity auth via IAM"
(kms/cmd/kms/main.go:197-229). The token endpoint is the canonical
/v1/iam/oauth/token, one env-overridable default
(kms/cmd/kms/main.go:117).
The boundary is one function. orgAuthorizes(tokenOrg, requested) returns
true only when requested == tokenOrg || strings.HasPrefix(requested, tokenOrg+"-") — the hyphen "prevents a token for lux from leaking into an
unrelated org like luxx" (kms/cmd/kms/auth.go:93-96). Every secrets
request runs through it: the request's {org} path param must be authorized
by some org the token resolves to, or the request is 403
(kms/cmd/kms/auth.go:201-250).
The org a token resolves to comes from its IAM claims, fail-closed: a
client_credentials application token carries owner admin, so its org is
the prefix of its <org>-<service> name; a user token carries the org in
owner. A token with no resolvable org is rejected
(kms/cmd/kms/auth.go:64-78, :187-189).
Copyright
Copyright and related rights waived via CC0.