Documentation
¶
Overview ¶
Package vaultinjector is IronClaw's minimal in-tree reference credential injector: the SEPARATE host-side principal the egress broker forwards a vault:// request TO. It is the sole holder of a credential and the only component that attaches it, host-side — the broker injects nothing and the sandbox never sees a key (threat model §11).
Contract with the broker (internal/host/egress/vault.go):
- The broker rewrites a vault://<cred>/<path> request to target this injector, sets X-Ironclaw-Vault-Cred: <cred> (the logical NAME, never a key), strips any sandbox-supplied Authorization, and stamps X-Ironclaw-Correlation for audit.
- The injector maps <cred> to its configured upstream + secret, attaches the secret host-side, and reverse-proxies to the real upstream over its own scheme.
- An unknown credential is refused 403 (deny-by-default). The secret is read from the host environment, NEVER from the config file, and is never echoed back.
An operator may instead point the broker at an external injector (e.g. OneCLI) that honours the same contract; this package is the default, swappable reference.
Index ¶
Constants ¶
const ( // CredHeader carries the logical credential name from the broker. Mirrors // egress.VaultCredHeader (kept local so this package does not import egress). CredHeader = "X-Ironclaw-Vault-Cred" // CorrelationHeader joins the injector's audit to the broker's. Mirrors // egress.CorrelationHeader. CorrelationHeader = "X-Ironclaw-Vault-Request-Id" )
Headers the injector reads, matching the broker's vault forwarding.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AuditRecord ¶
type AuditRecord struct {
Time time.Time `json:"time"`
Action string `json:"action,omitempty"` // "inject" (default) or "rotate"
Credential string `json:"credential"`
CorrelationID string `json:"correlationId,omitempty"`
Upstream string `json:"upstream,omitempty"`
Path string `json:"path"`
Status int `json:"status"`
Allowed bool `json:"allowed"`
Duration time.Duration `json:"durationNanos"`
}
AuditRecord is one injection or rotation decision, emitted to an AuditSink. It carries the credential NAME and correlation id — never the secret value.
type AuditSink ¶
type AuditSink func(AuditRecord)
AuditSink receives one record per injection. Must be safe for concurrent use.
type Config ¶
Config is the injector's credential catalog: logical name -> spec.
func LoadConfig ¶
LoadConfig reads and validates a JSON injector config from path.
func (*Config) CredHosts ¶
CredHosts returns the cred -> upstream-host map, a convenience for wiring the broker's VaultGuard host resolution.
func (*Config) UpstreamHost ¶
UpstreamHost returns the bare upstream host a credential targets, for the control-plane's vault-policy enforcement (the host dimension of VaultPolicyStore.Allows). This is the one config fact the broker side shares with the injector: the policy maps (group, cred) -> approved upstream host.
type CredSpec ¶
type CredSpec struct {
// Upstream is the real API base URL the credential is used against
// (e.g. "https://api.github.com"). Its host is what the control-plane's vault
// policy is enforced against.
Upstream string `json:"upstream"`
// SecretEnv is the host env var holding the secret value (e.g. "VAULT_GITHUB_TOKEN").
SecretEnv string `json:"secretEnv"`
// Header is the request header the secret is attached to. Default "Authorization".
Header string `json:"header,omitempty"`
// Scheme is the value prefix (e.g. "Bearer "). Default "Bearer ".
Scheme string `json:"scheme,omitempty"`
}
CredSpec configures one logical credential. The secret itself is NEVER in the file: SecretEnv names the host environment variable that holds it.
type Injector ¶
type Injector struct {
// contains filtered or unexported fields
}
Injector attaches host-held credentials to broker-forwarded requests. It holds the secret values resolved from the environment; the config file never does. The held secrets are guarded by mu so a Rotate can swap one atomically while Handler reads it.
func New ¶
New builds an Injector from cfg, resolving each credential's secret via lookupEnv (default os.LookupEnv). A credential whose secret env var is unset is a configuration error: the injector fails closed rather than serving a credential it cannot attach.
func (*Injector) Creds ¶
Creds returns the configured credential names (sorted), for diagnostics. Never returns secrets.
func (*Injector) Handler ¶
Handler serves the injector: it reads the credential name, attaches the host-held secret, and reverse-proxies to the real upstream. Deny-by-default: an unknown or missing credential is refused 403. The secret is attached only on the upstream hop and never written to the response.
func (*Injector) Rotate ¶ added in v0.1.86
Rotate re-resolves a credential's secret from its configured secretEnv via the injector's env lookup and atomically swaps the held value. This is the injector's half of the rotation contract (IRO-144): the control plane signals a rotation only AFTER a human approves it, and the new secret is read from the host environment here — it NEVER travels through the control plane, the change body, or this call. An unknown credential, or a secret env that is now unset/empty, returns an error and KEEPS the old secret, so a botched rotation fails closed rather than blanking a live credential. The secret value is never returned or logged.
func (*Injector) RotateHandler ¶ added in v0.1.86
RotateHandler serves the injector's CONTROL surface: a request that re-resolves a credential's held secret from the host environment. It is SEPARATE from Handler (the broker-facing proxy) and MUST be bound to a channel only the control plane can reach — its own loopback addr / unix socket, never the broker's — so the sandbox can never trigger a rotation. The request carries the credential NAME in CredHeader (never a secret) and returns no secret: 200 on a successful rotation, 403 for an unknown credential, 502 when the new secret cannot be resolved. Every outcome is audited (name + correlation only).