credsource

package
v1.15.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

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

View Source
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.

View Source
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

View Source
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.

View Source
var ErrCredentialSourceUnavailable = errors.New("credsource: remote credential source unavailable")

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

func MustRegister(name string, factory Factory)

MustRegister wraps Register and panics on error — the driver-side init() idiom. A duplicate-name panic signals a build misconfiguration.

func Register

func Register(name string, factory Factory) error

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

type Factory func(cfg Config) (Source, error)

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

func Resolve(name string, cfg Config) (Source, error)

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

func Static(clientID, clientSecret string) Source

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL