Documentation
¶
Overview ¶
Package valiss implements the core of the tenant authentication scheme, a three-level chain of Ed25519 keys:
- An operator holds an Ed25519 nkey; its public key is the trust anchor servers pin.
- The operator signs each tenant an account token that the tenant's own account nkey must back. Issued account tokens are recorded in a server-side allowlist.
- A tenant may delegate: it signs user tokens with its account seed.
- The subject signs every request with its nkey; the server verifies the token chain up to the pinned operator key, the request signature against the token's subject key, and the account token against the allowlist, then hands the verified identity to the handler for data segmentation.
Tokens are this scheme's own typed claims carried in an nkey-signed JWT: the sub claim is the subject's public key and name carries the human-readable label. Key levels are strict: operator keys (SO.../O...) sign account tokens for account keys (SA.../A...), account keys sign user tokens for user keys (SU.../U...).
Authorization rides named extension claims (Extension, WithExtension): typed payloads the scheme signs and transports but assigns no meaning. The contrib transports enforce their extensions (HTTP hosts/methods/paths, gRPC methods); consumers add domain extensions the same way and read them back in handlers with ExtOf.
Index ¶
- Constants
- func ContextWithIdentity(ctx context.Context, id *Identity) context.Context
- func Covered(granted []string, required string) bool
- func ExtOf[T Extension](exts Extensions) (T, bool, error)
- func Issue(operator nkeys.KeyPair, name, tenantPubKey string, opts ...IssueOption) (string, error)
- func IssueOperator(operator nkeys.KeyPair, opts ...IssueOption) (string, error)
- func IssueUser(account nkeys.KeyPair, name, userPubKey string, opts ...IssueOption) (string, error)
- func IssuerOf(token string) (string, error)
- func SignRequest(tenant nkeys.KeyPair, now time.Time) (timestamp, signature string, err error)
- func VerifySignature(tenantPubKey, timestamp, signature string, now time.Time, skew time.Duration) error
- type AccountClaims
- type AccountTokenResolver
- type AllowAll
- type Allowlist
- type Claims
- type ClaimsValidator
- type Extension
- type Extensions
- type Identity
- type IssueOption
- type OperatorClaims
- type Request
- type StaticAllowlist
- type UserClaims
- type Verifier
- type VerifierOption
- func WithAccountTokenResolver(fn AccountTokenResolver) VerifierOption
- func WithClaimsValidator(fn ClaimsValidator) VerifierOption
- func WithClock(now func() time.Time) VerifierOption
- func WithExtensionType[T Extension]() VerifierOption
- func WithOperatorToken(token string) VerifierOption
- func WithSkew(d time.Duration) VerifierOption
Constants ¶
const ( HeaderAccountToken = "valiss-account-token" HeaderUserToken = "valiss-user-token" HeaderTimestamp = "valiss-timestamp" HeaderSignature = "valiss-signature" )
Header field names carrying the credential on each request. Used as gRPC metadata keys and HTTP header names alike.
const DefaultSkew = 2 * time.Minute
DefaultSkew bounds request-timestamp drift and token-expiry slack.
Variables ¶
This section is empty.
Functions ¶
func ContextWithIdentity ¶
ContextWithIdentity returns a context carrying the verified identity. Transport middlewares call this after verification.
func Covered ¶
Covered reports whether any granted pattern covers the required value, honoring trailing-"*" prefix wildcards. Transport extensions use it for paths and methods.
func ExtOf ¶
func ExtOf[T Extension](exts Extensions) (T, bool, error)
ExtOf decodes the extension claim named by T's zero value. An absent name yields the zero value and false.
func Issue ¶
Issue mints an account token signed by the operator key. The token subject is the tenant's account public key and name carries the tenant id; the tenant signs requests with the seed matching the subject key.
func IssueOperator ¶ added in v0.6.0
func IssueOperator(operator nkeys.KeyPair, opts ...IssueOption) (string, error)
IssueOperator mints the self-signed operator token: the trust domain's policy statement (epoch, validity window, extensions), signed by the operator key over its own public key. Servers configured with it via WithOperatorToken enforce the policy on every request; the pinned public key remains the trust anchor.
func IssueUser ¶
IssueUser mints a user token signed by a tenant's account key, delegating to an end user. The token subject is the user's public key and name carries the user id. WithBearer produces a token the server accepts without per-request signatures.
func IssuerOf ¶
IssuerOf returns the public key that signed a token, after checking the token's own signature against it. It does not establish trust: the caller must still verify the issuer's place in the chain.
func SignRequest ¶
SignRequest produces the timestamp and base64 signature a tenant attaches to a request, signing with its nkey seed.
Types ¶
type AccountClaims ¶
type AccountClaims struct {
Claims
// Name is the tenant's human-readable label; it segments all stored
// data. Falls back to the subject key when the token carries no name.
Name string
// Epoch is the trust-domain epoch the token was issued in (WithEpoch).
Epoch uint64
// Ext carries the named extension claims (WithExtension).
Ext Extensions
}
AccountClaims is the verified content of an account (tenant) token.
func VerifyAccount ¶
func VerifyAccount(token, operatorPubKey string) (*AccountClaims, error)
VerifyAccount decodes an account token, checks its type, signature, and issuer, and returns the claims. It does NOT check expiry, activation, or the allowlist; the Verifier layers those so callers get precise errors.
type AccountTokenResolver ¶
AccountTokenResolver supplies the operator-signed account token for an account public key, serving requests that carry only a user token (the default creds shape). The resolved token goes through the full verification: operator signature, expiry, allowlist.
func StaticAccountTokens ¶
func StaticAccountTokens(tokens ...string) (AccountTokenResolver, error)
StaticAccountTokens builds a resolver over a fixed token set, e.g. from server configuration. Tokens are indexed by their subject account key; their signatures are checked here, their trust is established per request.
type AllowAll ¶
type AllowAll struct{}
AllowAll accepts every token; for local development where no allowlist is configured. The token signature and expiry still gate access.
type Allowlist ¶
Allowlist decides whether an issued token (by jti) is still accepted. Only tokens the issuer explicitly deposited server-side pass, so a token can be revoked by removing it even before expiry.
type Claims ¶
type Claims struct {
// ID is the token's unique identifier (jti), the allowlist key for
// account tokens.
ID string
// Issuer is the issuer public key that signed the token (iss).
Issuer string
// Subject is the subject's nkey public key (sub) that must sign
// requests.
Subject string
// IssuedAt is the token mint time (iat).
IssuedAt time.Time
// ExpiresAt is the token expiry (exp); zero means the token never
// expires.
ExpiresAt time.Time
// NotBefore is the token activation time (nbf); zero means immediately
// valid.
NotBefore time.Time
}
Claims is the verified RFC 7519 registered-claims content of a token. Level-specific data lives on AccountClaims and UserClaims, which embed it.
func Decode ¶
Decode parses a token of either level without establishing trust: the signature is checked against the token's own embedded issuer only. For inspection and tooling; servers must use VerifyAccount or VerifyUser.
type ClaimsValidator ¶
ClaimsValidator is custom validation logic injected into the Verifier. It runs after the token chain is verified and the identity is assembled, and before the request signature check. A non-nil error rejects the request as unauthenticated.
func ExtValidator ¶
func ExtValidator[T Extension](fn func(req Request, id *Identity, acct, user T) error) ClaimsValidator
ExtValidator adapts a typed validator over the extension claim named by T's zero value into a ClaimsValidator: the extension is decoded from both the account and user tokens before fn runs. Missing extensions pass zero values.
type Extension ¶
type Extension interface {
ExtensionName() string
}
Extension is a named claim payload carried in a token's ext field. Implementations are value struct types whose zero value reports the name.
type Extensions ¶
type Extensions map[string]json.RawMessage
Extensions is the named extension claims of a token: consumer- or transport-defined claim bodies keyed by extension name. This scheme signs and transports them; meaning is assigned by whoever registered the name (e.g. the httpauth and grpcauth packages, or the library consumer).
type Identity ¶
type Identity struct {
// Account is the tenant the request acts under; always present.
Account *AccountClaims
// User is the delegated end user; nil for account-level requests.
User *UserClaims
}
Identity is the verified result of a request.
type IssueOption ¶
type IssueOption func(*issueConfig)
IssueOption customizes a minted token. Without a WithTTL or WithExpiry option the token never expires.
func WithBearer ¶
func WithBearer() IssueOption
WithBearer marks a user token as a bearer token: the server accepts it without per-request signatures. Bearer tokens are replayable until they expire or their account leaves the allowlist, so pair them with TLS and a short validity window. Only IssueUser accepts this option.
func WithEpoch ¶ added in v0.6.0
func WithEpoch(epoch uint64) IssueOption
WithEpoch stamps the trust-domain epoch on the token. On an operator token it declares the domain's current epoch; on account and user tokens it must echo it. Verifiers configured with the operator token reject tokens from any other epoch, so bumping the epoch and re-minting rotates the whole domain at once. Unstamped tokens are epoch 0.
func WithExpiry ¶
func WithExpiry(t time.Time) IssueOption
WithExpiry makes the token expire at t (the JWT exp claim).
func WithExtension ¶
func WithExtension(v Extension) IssueOption
WithExtension embeds a named extension claim into the token's ext field; the name comes from the value's ExtensionName. Repeat the option for multiple extensions; a duplicate name is an error. The scheme signs and transports the value untouched; servers read it back with ExtOf or validate it with an ExtValidator.
func WithNotBefore ¶
func WithNotBefore(t time.Time) IssueOption
WithNotBefore makes the token invalid before t (the JWT nbf claim).
func WithTTL ¶
func WithTTL(ttl time.Duration) IssueOption
WithTTL makes the token expire ttl from now (the JWT exp claim).
type OperatorClaims ¶ added in v0.6.0
type OperatorClaims struct {
Claims
// Epoch is the trust domain's current epoch. A verifier configured with
// the operator token accepts only account and user tokens that echo it.
Epoch uint64
// Ext carries the named extension claims (WithExtension).
Ext Extensions
}
OperatorClaims is the verified content of a self-signed operator token: the trust domain's policy statement, signed by the pinned anchor key.
func VerifyOperator ¶ added in v0.6.0
func VerifyOperator(token, operatorPubKey string) (*OperatorClaims, error)
VerifyOperator decodes a self-signed operator token, checks its type and that it is signed by the pinned operator key over itself, and returns the claims. Expiry and activation checks belong to the Verifier.
type Request ¶
type Request struct {
// AccountToken is the operator-signed account token.
AccountToken string
// UserToken is the account-signed user token on chain credentials; empty
// when the tenant itself makes the request.
UserToken string
// Timestamp and Signature are the per-request signing proof; both empty
// on bearer requests.
Timestamp string
Signature string
}
Request is the per-request material a transport extracts from headers.
type StaticAllowlist ¶
type StaticAllowlist struct {
// contains filtered or unexported fields
}
StaticAllowlist is an in-memory set of accepted token IDs.
func LoadAllowlistFile ¶
func LoadAllowlistFile(path string) (*StaticAllowlist, error)
LoadAllowlistFile reads a newline-delimited allowlist file of token IDs. Blank lines and lines beginning with '#' are ignored.
func NewStaticAllowlist ¶
func NewStaticAllowlist(ids ...string) *StaticAllowlist
func (*StaticAllowlist) Allowed ¶
func (a *StaticAllowlist) Allowed(jti string) bool
func (*StaticAllowlist) Set ¶
func (a *StaticAllowlist) Set(ids []string)
Set replaces the accepted set, e.g. after reloading the file.
type UserClaims ¶
type UserClaims struct {
Claims
// Name is the user's human-readable label. Falls back to the subject
// key when the token carries no name.
Name string
// Epoch is the trust-domain epoch the token was issued in (WithEpoch).
Epoch uint64
// Bearer marks a token whose holder authenticates by the token alone,
// without per-request signatures.
Bearer bool
// Ext carries the named extension claims (WithExtension).
Ext Extensions
}
UserClaims is the verified content of a user token.
func VerifyUser ¶
func VerifyUser(token, accountPubKey string) (*UserClaims, error)
VerifyUser decodes a user token, checks its type, signature, and issuer (the account public key that delegated it), and returns the claims. Expiry and activation checks belong to the Verifier.
type Verifier ¶
type Verifier struct {
// contains filtered or unexported fields
}
Verifier checks the full per-request credential: account token signature against the pinned operator key, expiry and activation, allowlist membership, the optional user-token chain, registered extension types, custom validators, and the request signature within the skew window. Requests without a signature pass only when the effective token is a bearer user token. Transport layers (gRPC interceptor, HTTP middleware) wrap it with header extraction and error mapping.
func NewVerifier ¶
func NewVerifier(operatorPubKey string, allowlist Allowlist, opts ...VerifierOption) *Verifier
func (*Verifier) VerifyRequest ¶
VerifyRequest authenticates a request credential and returns the verified identity. Any error means the request must be rejected as unauthenticated.
A credential with a user token is verified as a chain: the account token against the operator key and the allowlist, then the user token against the account token's subject key. An empty timestamp and signature is a bearer request, accepted only when the effective token is a bearer user token; account-level requests must always sign.
type VerifierOption ¶
type VerifierOption func(*Verifier)
VerifierOption configures a Verifier.
func WithAccountTokenResolver ¶
func WithAccountTokenResolver(fn AccountTokenResolver) VerifierOption
WithAccountTokenResolver accepts requests that carry only a user token, resolving the account token server-side. Without it such requests are rejected.
func WithClaimsValidator ¶
func WithClaimsValidator(fn ClaimsValidator) VerifierOption
WithClaimsValidator injects custom validation into the verification pipeline. Validators run in registration order; the first error wins.
func WithClock ¶
func WithClock(now func() time.Time) VerifierOption
WithClock overrides the time source; for tests.
func WithExtensionType ¶
func WithExtensionType[T Extension]() VerifierOption
WithExtensionType registers an extension type for eager validation: when either token carries the extension named by T's zero value, it must decode into T or the request is rejected. Retrieval via ExtOf never requires registration; this only moves malformed-extension failures to auth time.
func WithOperatorToken ¶ added in v0.6.0
func WithOperatorToken(token string) VerifierOption
WithOperatorToken supplies the trust domain's self-signed operator token and enforces its policy on every request: the operator token must be within its own validity window, and every account and user token must echo its epoch. Bumping the epoch in a fresh operator token therefore revokes everything minted in earlier epochs at once. The pinned operator public key remains the trust anchor; a token not self-signed by it poisons the verifier so every request fails rather than silently skipping policy.
func WithSkew ¶
func WithSkew(d time.Duration) VerifierOption
WithSkew overrides the DefaultSkew window for timestamp drift and token expiry slack.
Directories
¶
| Path | Synopsis |
|---|---|
|
contrib
|
|
|
grpcauth
Package grpcauth wires the tenant authentication scheme into gRPC: server interceptors that verify the per-request credential and enforce the gRPC extension claim (Ext), and a client per-RPC credential that attaches the credential.
|
Package grpcauth wires the tenant authentication scheme into gRPC: server interceptors that verify the per-request credential and enforce the gRPC extension claim (Ext), and a client per-RPC credential that attaches the credential. |
|
httpauth
Package httpauth wires the tenant authentication scheme into net/http: a server middleware that verifies the per-request credential and enforces the HTTP extension claim (Ext), and a client http.RoundTripper that attaches the credential.
|
Package httpauth wires the tenant authentication scheme into net/http: a server middleware that verifies the per-request credential and enforces the HTTP extension claim (Ext), and a client http.RoundTripper that attaches the credential. |
|
Package creds implements the client credentials file: the subject's token plus the seed that signs its requests, in one marker-delimited text file.
|
Package creds implements the client credentials file: the subject's token plus the seed that signs its requests, in one marker-delimited text file. |
|
examples
|
|
|
grpcauth
command
Example grpcauth shows the full tenant-auth wiring for gRPC: an operator issues an account token, the server installs the auth interceptors, and the client attaches the credential to every call.
|
Example grpcauth shows the full tenant-auth wiring for gRPC: an operator issues an account token, the server installs the auth interceptors, and the client attaches the credential to every call. |
|
httpauth
command
Example httpauth shows the full tenant-auth wiring for net/http: an operator signs an account token carrying an HTTP extension, the server wraps its mux with the auth middleware, and the client signs every request via the transport.
|
Example httpauth shows the full tenant-auth wiring for net/http: an operator signs an account token carrying an HTTP extension, the server wraps its mux with the auth middleware, and the client signs every request via the transport. |
|
minter
command
Command minter issues valiss tenant credentials for gRPC and HTTP services using the operator/account/user nkey model.
|
Command minter issues valiss tenant credentials for gRPC and HTTP services using the operator/account/user nkey model. |