Documentation
¶
Overview ¶
Package credsource is the §4.4 seam through which an OAuth provider's OWN client credential (`client_id` / `client_secret`) resolves. It answers a single question: WHERE does the runtime get the credential it uses to authenticate itself to an authorization server or a credential broker?
Two sources ship:
- env — the credential is read from the process environment ONCE at boot (the historical behavior; the default). Fail-loud at boot when a named env var is unset. A credential that changes after exec can never reach a running runtime — but the process env is fixed at exec anyway, so this is not a limitation of env, it is a property of env.
- remote — the runtime PULLS the credential from a coordinator- served endpoint at first need, authenticated by the runtime's own service token (itself env-indirected). The pulled credential is held in memory only, TTL-capped, single-flighted, and refetched on expiry. A coordinator that mints a runtime's credential AFTER the runtime booted reaches it with zero touch — the motivation for the whole seam (a broker credential for the `tokenexchange` driver, one level up from the brokered downstream tokens that strategy already pulls).
Why a pull, never a push ¶
A credential arriving in-band over the Protocol is credential passthrough (CLAUDE.md §7 rule 3), the shape the token-exchange strategy already rejects. The remote source mirrors that pull posture one level up: the credential is pulled from the authority that owns it, cached in memory only, never persisted (a sealed per-runtime copy would recreate the shadow-store revocation hole a persisted copy would create). With `remote`, the broker client secret never enters the runtime's environment at all — defense-in-depth.
Fail loud, one mode per source ¶
Every failure is explicit (CLAUDE.md §13): the env source fails the boot when a named env var is empty; the remote source fails the tool call with the typed ErrCredentialSourceUnavailable when a fetch is unreachable / non-200 / malformed. There is NEVER a fallback from remote to env, to an unauthenticated call, or to the interactive flow. A provider declares exactly ONE source; the config validator rejects a dual declaration.
No import of the auth package ¶
This package deliberately depends on nothing under `internal/tools/auth` (only the low-level `internal/events`, `internal/audit`, `internal/identity`). `internal/tools/auth` (`BuildProviders`) imports THIS package to construct the source; the concrete drivers (`oauth2`, `tokenexchange`) reach the resolved credential through the Source carried on `auth.ProviderConfig`. The one-directional dependency keeps the seam a leaf.
Index ¶
- Constants
- Variables
- func MustRegister(name string, factory Factory)
- func Register(name string, factory Factory) error
- func RegisteredSources() []string
- type ClientCredential
- type Config
- type Factory
- type ProviderCredentialFetchFailedPayload
- type ProviderCredentialFetchedPayload
- type RemoteConfig
- type Source
Constants ¶
const ( // SourceEnv resolves the client credential from the process // environment once at boot (the default). SourceEnv = "env" // SourceRemote pulls the client credential from a coordinator-served // endpoint at first need. SourceRemote = "remote" )
Canonical source names. The `internal/config` validator's `allowedCredentialSources` allowlist mirrors these constants; the drift guard `TestRegisteredSourcesMatchConfigAllowlist` asserts no drift between the two surfaces.
const ( // EventTypeProviderCredentialFetched — emitted once per ACTUAL // remote credential fetch that succeeds (a cache hit emits nothing). // Payload is ProviderCredentialFetchedPayload. EventTypeProviderCredentialFetched events.EventType = "tool.provider_credential_fetched" // EventTypeProviderCredentialFetchFailed — emitted when a remote // credential fetch fails (unreachable / non-200 / malformed). Payload // is ProviderCredentialFetchFailedPayload. The tool call fails loud // with ErrCredentialSourceUnavailable; this event makes the failure // observable. EventTypeProviderCredentialFetchFailed events.EventType = "tool.provider_credential_fetch_failed" )
Canonical credential-fetch event types. Registered from this package's init() so a Publish never trips events.ErrUnknownEventType. Both carry caller-safe surface only (provider name, endpoint host, expiry, a redacted reason) — NEVER the fetched client_id / client_secret / service-token bytes. Both payloads embed events.SafeSealed so the bus accepts them on the typed SafePayload path.
These describe the fetch of a provider's OWN client credential (the runtime→authority leg) and are distinct from `tool.credential_exchanged` (the runtime→broker downstream-token exchange the credential enables).
Variables ¶
var ( // ErrSourceUnknown — [Resolve] was called with a name no driver has // registered for. The message lists the registered names. ErrSourceUnknown = errors.New("credsource: credential source not registered") // ErrSourceEmptyName — [Register] was called with an empty name. ErrSourceEmptyName = errors.New("credsource: source registration: empty name") // ErrSourceNilFactory — [Register] was called with a nil Factory. ErrSourceNilFactory = errors.New("credsource: source registration: nil factory") // ErrSourceDuplicate — [Register] was called twice for one name // (typically a double blank-import). ErrSourceDuplicate = errors.New("credsource: source registration: duplicate name") )
Sentinel errors specific to the registry.
ErrCredentialSourceUnavailable is the typed sentinel a remote credential fetch fails with — unreachable endpoint, non-200 status, or a malformed / strict-parse-rejected response. Callers `errors.Is` against it; the tool call fails loud (never a fallback to env, to an unauthenticated call, or to the interactive flow — §13).
The wrapped message carries the provider name and the endpoint URL (operator configuration, not a secret) but NEVER the runtime's service token, the fetched client_id, or the fetched client_secret. The no-secret-bytes property is asserted by test.
Functions ¶
func MustRegister ¶
MustRegister wraps Register and panics on error — the driver-side init() idiom. A duplicate-name panic signals a build misconfiguration.
func Register ¶
Register installs a Factory under name. Drivers self-register from their package init(). Re-registering a name returns ErrSourceDuplicate; the function does NOT panic itself so tests can exercise the duplicate path.
func RegisteredSources ¶
func RegisteredSources() []string
RegisteredSources returns a sorted list of registered source names.
Types ¶
type ClientCredential ¶
type ClientCredential struct {
// ClientID is the resolved OAuth client_id. Required — a resolver
// that yields an empty ClientID has failed and returns an error
// instead.
ClientID string
// ClientSecret is the resolved OAuth client_secret. NEVER logged.
ClientSecret string
// ExpiresAt is the credential's validity horizon. Zero means "no
// expiry advertised" (env / static). The remote source's in-memory
// serve horizon never exceeds this value.
ExpiresAt time.Time
}
ClientCredential is one resolved OAuth client credential. The zero ClientCredential.ExpiresAt means "no expiry advertised" — env and static credentials never expire; only the remote source populates it (derived from the coordinator response's `expires_in`).
The secret fields are NEVER logged and NEVER appear in an error string; callers pass them straight into the authorization-server / broker request body.
type Config ¶
type Config struct {
// ProviderName is the operator-facing provider name — surfaced in
// boot / fetch error messages and used to attribute fetch events.
ProviderName string
// ProviderIndex is the provider's position in
// `tools.oauth_providers[]`, used to reproduce the historical env
// fail-loud message shape.
ProviderIndex int
// ClientIDEnv / ClientSecretEnv name the env vars the `env` source
// reads. Empty for the remote source.
ClientIDEnv string
ClientSecretEnv string
// Remote carries the `remote` source's parameters; nil for env.
Remote *RemoteConfig
// HTTPClient is the client the remote source fetches through.
// Optional — the remote driver applies a bounded default.
HTTPClient *http.Client
// Clock is the wall-clock source (cache math). Optional — defaults
// to time.Now.
Clock func() time.Time
// Bus / Redactor let the remote source emit its SafePayload fetch
// events. Mandatory for the remote source; unused by env.
Bus events.EventBus
Redactor audit.Redactor
}
Config is the boundary type the registry passes to a source factory. It carries the union of both drivers' construction inputs; a factory reads only the fields its source needs. `BuildProviders` maps the operator YAML (`config.ToolOAuthProviderConfig`) onto this struct at the boundary so the concrete drivers never import `internal/config`.
type Factory ¶
Factory builds a Source from a Config. Drivers self-register one Factory each via init() → MustRegister.
type ProviderCredentialFetchFailedPayload ¶
type ProviderCredentialFetchFailedPayload struct {
events.SafeSealed
// Provider is the operator-facing provider name whose fetch failed.
Provider string
// Endpoint is the HOST of the configured remote `url`.
Endpoint string
// Reason is a short, redaction-safe classification of the failure
// (e.g. "unreachable", "status 503", "malformed response"). NEVER
// carries response bodies or credential bytes.
Reason string
}
ProviderCredentialFetchFailedPayload is the typed payload for a `tool.provider_credential_fetch_failed` event. SafePayload by construction — no secret bytes.
type ProviderCredentialFetchedPayload ¶
type ProviderCredentialFetchedPayload struct {
events.SafeSealed
// Provider is the operator-facing provider name whose client
// credential was fetched.
Provider string
// Endpoint is the HOST of the configured remote `url` — never the
// full URL with query material.
Endpoint string
// FormatVersion echoes the response's `format_version` — lets an
// operator confirm the coordinator contract version in the audit
// trail.
FormatVersion int
// ExpiresAt is the coordinator-advertised validity of the fetched
// credential. Zero when the coordinator advertised no expiry. The
// in-memory serve horizon never exceeds this value.
ExpiresAt time.Time
}
ProviderCredentialFetchedPayload is the typed payload for a `tool.provider_credential_fetched` event. SafePayload by construction: every field is the runtime's own bookkeeping or operator-supplied configuration metadata; NO fetched-credential bytes, NO service-token bytes.
type RemoteConfig ¶
type RemoteConfig struct {
// URL is the coordinator credential endpoint (the authenticated GET
// target). Required; validated well-formed at boot.
URL string
// AuthTokenEnv names the env var holding the runtime's own service
// token — sent as `Authorization: Bearer <token>`. Required;
// validated non-empty at boot (the token itself is read lazily at
// fetch time so a rotated token is picked up without restart).
AuthTokenEnv string
// CacheTTL caps the in-memory serve horizon. Zero = the driver's
// default cap. The effective horizon is min(response expires_in,
// CacheTTL).
CacheTTL time.Duration
// Timeout bounds a single fetch. Zero = the driver's default.
Timeout time.Duration
}
RemoteConfig is the `remote` source's operator-declared parameters, mapped from `config.ToolOAuthRemoteConfig` at the boundary.
type Source ¶
type Source interface {
// ValidateAtBoot performs the source's boot-time readiness check,
// called ONCE by `BuildProviders` while the config is still being
// assembled.
//
// - The env source RESOLVES and caches the credential now,
// reproducing the historical boot-time fail-loud (an unset env
// var crashes assembly with a message naming the field).
// - The remote source validates only the block SHAPE (url
// well-formed, auth-token env var non-empty) and DEFERS the
// network fetch to the first Resolve — so a coordinator-minted
// credential reaches an already-running runtime with zero touch.
ValidateAtBoot(ctx context.Context) error
// Resolve returns the current client credential, fetching or
// refreshing as the source requires. Called by provider drivers at
// credential-need time (the `oauth2` driver at construction; the
// `tokenexchange` driver lazily at exchange time). Implementations
// are single-flight and memory-cached where a fetch is involved.
//
// A failure returns a wrapped [ErrCredentialSourceUnavailable]
// carrying the provider name and (for remote) the endpoint — with
// ZERO secret bytes. Callers `errors.Is` against the sentinel.
Resolve(ctx context.Context) (ClientCredential, error)
}
Source is the §4.4 seam interface every credential source implements. It is a MANDATORY interface — both shipped drivers implement both methods; there is no optional-capability ceremony (§4.4).
Concurrent reuse: a Source is a compiled artifact built once at boot and shared across every provider invocation. Implementations hold immutable configuration plus at most an internally-synchronised in-memory cache; per-run identity is read from `ctx`, never from the Source.
func Resolve ¶
Resolve constructs the Source for the named credential source by dispatching to the registered Factory. Returns wrapped ErrSourceUnknown — with the registered-source list — when the name has no driver.
func Static ¶
Static returns a Source that resolves to the given, already-resolved credential and never expires. It is NOT registered in the factory (it has no config-selectable name); it exists solely so direct construction of `auth.ProviderConfig` — outside `BuildProviders` — has a Source to supply. Emptiness is the caller's responsibility; the driver validates the resolved credential at its point of use.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package credsourcetest ships the committed REFERENCE implementation of the coordinator credential-fetch contract: an httptest server that serves an OAuth provider's client credential to the `remote` credential source.
|
Package credsourcetest ships the committed REFERENCE implementation of the coordinator credential-fetch contract: an httptest server that serves an OAuth provider's client credential to the `remote` credential source. |
|
drivers
|
|
|
env
Package env ships the default credential source: the OAuth provider's client credential is read from the process environment ONCE at boot.
|
Package env ships the default credential source: the OAuth provider's client credential is read from the process environment ONCE at boot. |
|
remote
Package remote ships the coordinator-served credential source: the runtime PULLS an OAuth provider's client credential (`client_id` / `client_secret`) from an operator-configured endpoint at first need, authenticated by the runtime's own service token.
|
Package remote ships the coordinator-served credential source: the runtime PULLS an OAuth provider's client credential (`client_id` / `client_secret`) from an operator-configured endpoint at first need, authenticated by the runtime's own service token. |