Documentation
¶
Overview ¶
Package authn holds the transport-independent authentication primitives shared by the HTTP, WebSocket, and gRPC surfaces: the tenant key store and the authenticated principal that travels on a request context.
The package deliberately knows nothing about net/http or gRPC — each transport adapts its own credential carrier (Authorization header, Sec-WebSocket-Protocol entry, gRPC metadata) into the same Principal.
Index ¶
- Constants
- Variables
- func BoundTenantFromContext(ctx context.Context) (string, bool)
- func RecordConflict(surface, reason string)
- func TokenFromAuthorization(header string) (string, string)
- func WithPrincipal(ctx context.Context, p Principal) context.Context
- type Authenticator
- func (a *Authenticator) AuthenticateToken(token string) (Principal, string, bool)
- func (a *Authenticator) Enabled() bool
- func (a *Authenticator) ExternalPrincipal(raw string) (Principal, bool)
- func (a *Authenticator) HasTenantKeys() bool
- func (a *Authenticator) TenantKeyCount() int
- func (a *Authenticator) Tenants() []string
- func (a *Authenticator) TrustExternal() bool
- type KeyStore
- type Kind
- type Principal
Constants ¶
const ( ReasonMissingCredential = "missing_header" ReasonBadScheme = "bad_scheme" ReasonBadKey = "bad_key" ReasonBadOrigin = "bad_origin" ReasonBadTenant = "bad_tenant" )
Failure reasons reported to the auth-failure metric. They are stable strings: "missing_header", "bad_scheme", and "bad_key" predate this package and are kept byte-identical so existing dashboards keep working.
const ( // WSSubprotocol is the only WebSocket subprotocol the server echoes. A // browser that must carry a credential offers it alongside an // `auth.<base64url-token>` entry; the server selects this one, so the token // never appears in the negotiated protocol or in a log line. WSSubprotocol = "otelcontext.v1" // WSAuthProtoPrefix marks the credential-bearing subprotocol entry. WSAuthProtoPrefix = "auth." )
const BearerPrefix = "Bearer "
BearerPrefix is the only credential scheme accepted on every surface.
const MaxKeyFileBytes = 1 << 20
MaxKeyFileBytes bounds how much of an operator-supplied key file is read. 1 MiB is ~20k entries; anything larger is a misconfiguration, not a deployment.
Variables ¶
var ConflictHook func(surface, reason string)
ConflictHook is called when a bound principal ignores a client-asserted tenant. surface is "http", "ws", or "grpc"; reason names the ignored carrier ("header", "metadata", "resource_attribute"). Wired by main.go to a Prometheus counter; safe to leave nil. Never called with credential material — only the surface and the carrier name.
Functions ¶
func BoundTenantFromContext ¶
BoundTenantFromContext returns the tenant a bound principal pinned onto ctx. Operator and unauthenticated contexts return ("", false), which leaves the caller's existing tenant precedence untouched.
func RecordConflict ¶
func RecordConflict(surface, reason string)
RecordConflict reports an ignored tenant assertion. No-op when unset.
func TokenFromAuthorization ¶
TokenFromAuthorization extracts the bearer token from an Authorization header value. The second result is a failure reason when no usable token is present; callers that have a second credential carrier (WebSocket subprotocol, gRPC metadata) may ignore it and try that instead.
Types ¶
type Authenticator ¶
type Authenticator struct {
// contains filtered or unexported fields
}
Authenticator resolves a bearer token into a Principal. It is shared by the HTTP, WebSocket, and gRPC adapters so all three surfaces agree on what a credential means; only credential extraction differs per transport.
A zero-value / disabled Authenticator authenticates nothing and reports Enabled() == false, which is how a development deployment with no API_KEY, no tenant keys file, and no external trust keeps today's open behaviour.
func NewAuthenticator ¶
func NewAuthenticator(operatorKey string, store *KeyStore, trustExternal bool) *Authenticator
NewAuthenticator wires the operator key (API_KEY), the per-tenant key store (API_TENANT_KEYS_FILE), and the AUTH_TRUST_EXTERNAL switch.
func (*Authenticator) AuthenticateToken ¶
func (a *Authenticator) AuthenticateToken(token string) (Principal, string, bool)
AuthenticateToken resolves a raw bearer token. The operator key is checked first with a constant-time compare, then the tenant key store. A miss returns ReasonBadKey; an empty token returns ReasonMissingCredential.
func (*Authenticator) Enabled ¶
func (a *Authenticator) Enabled() bool
Enabled reports whether any credential source is configured. When false the caller must not gate anything: authentication arrives with configuration, never by surprise.
func (*Authenticator) ExternalPrincipal ¶
func (a *Authenticator) ExternalPrincipal(raw string) (Principal, bool)
ExternalPrincipal converts a proxy-injected tenant value into a bound principal. It returns false unless AUTH_TRUST_EXTERNAL is on and the value survives the shared tenant sanitizer.
The injected header is trusted ONLY because the operator asserted that a front proxy authenticates the caller, strips inbound copies of this header, and makes the application ports unreachable except through it. Without those conditions this is an authentication bypass — see CLAUDE.md.
func (*Authenticator) HasTenantKeys ¶
func (a *Authenticator) HasTenantKeys() bool
HasTenantKeys reports whether per-tenant keys are configured.
func (*Authenticator) TenantKeyCount ¶
func (a *Authenticator) TenantKeyCount() int
TenantKeyCount is the number of configured tenant keys (never their values).
func (*Authenticator) Tenants ¶
func (a *Authenticator) Tenants() []string
Tenants lists the tenants covered by the key store.
func (*Authenticator) TrustExternal ¶
func (a *Authenticator) TrustExternal() bool
TrustExternal reports whether a proxy-injected identity header is honoured.
type KeyStore ¶
type KeyStore struct {
// contains filtered or unexported fields
}
KeyStore maps bearer keys to tenants using digest-only storage and constant-time comparison. A nil or empty store is disabled and every Lookup misses, which is what keeps the default (no keys file) deployment on the legacy shared-API_KEY path.
func LoadKeyStore ¶
LoadKeyStore reads API_TENANT_KEYS_FILE. Format is chosen by extension: `.json` for JSON, `.yaml`/`.yml` for YAML. Both carry the same shape — an object mapping bearer key to tenant ID:
{"3f7c…": "acme", "9b21…": "acme", "c40d…": "beta"}
Multiple keys may map to the same tenant (rotation, per-agent keys). The load is startup-only; there is no reload path by design, so a key file swap is an explicit restart.
Refused: unreadable files, files readable or writable by group/other, unknown extensions, empty files, empty keys, keys carrying whitespace or control characters, tenant IDs the storage sanitizer rejects, and duplicate keys. Errors never quote key material.
func NewKeyStoreFromMap ¶
NewKeyStoreFromMap builds a store from an in-memory mapping. Iteration order of a Go map is random, so entries are sorted by tenant then key digest to keep error messages deterministic.
type Kind ¶
type Kind string
Kind classifies an authenticated credential.
const ( // KindOperator is the shared API_KEY. It is authorized for every tenant, // so tenant selection keeps its historical precedence (explicit header or // metadata → trusted resource attribute → DEFAULT_TENANT). KindOperator Kind = "operator" // KindTenant is a key from API_TENANT_KEYS_FILE. Its tenant binding is // absolute: client-asserted tenant headers, gRPC metadata, and OTLP // `tenant.id` resource attributes are ignored (and counted) for the // lifetime of the request or connection. KindTenant Kind = "tenant" // KindExternal is an identity injected by a front proxy and trusted only // because AUTH_TRUST_EXTERNAL=true. It binds like KindTenant. KindExternal Kind = "external" )
type Principal ¶
Principal is the authenticated identity of a request or connection. The zero value means "unauthenticated" — which is the normal state of a development deployment with no API_KEY and no tenant keys file.
func PrincipalFromContext ¶
PrincipalFromContext returns the principal stashed by WithPrincipal. The second result is false when the request was never authenticated.
func (Principal) Authenticated ¶
Authenticated reports whether a credential was presented and accepted.