vaultinjector

package
v0.1.511 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: AGPL-3.0 Imports: 10 Imported by: 0

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

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

type Config struct {
	Creds map[string]CredSpec `json:"creds"`
}

Config is the injector's credential catalog: logical name -> spec.

func LoadConfig

func LoadConfig(path string) (*Config, error)

LoadConfig reads and validates a JSON injector config from path.

func (*Config) CredHosts

func (c *Config) CredHosts() map[string]string

CredHosts returns the cred -> upstream-host map, a convenience for wiring the broker's VaultGuard host resolution.

func (*Config) UpstreamHost

func (c *Config) UpstreamHost(cred string) (string, bool)

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

func New(cfg *Config, lookupEnv func(string) (string, bool), opts ...Option) (*Injector, error)

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

func (i *Injector) Creds() []string

Creds returns the configured credential names (sorted), for diagnostics. Never returns secrets.

func (*Injector) Handler

func (i *Injector) Handler() http.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

func (i *Injector) Rotate(cred string) error

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

func (i *Injector) RotateHandler() http.Handler

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).

type Option

type Option func(*Injector)

Option configures an Injector.

func WithAudit

func WithAudit(sink AuditSink) Option

func WithTransport

func WithTransport(rt http.RoundTripper) Option

WithTransport overrides the upstream RoundTripper (tests). WithAudit sets the sink.

Jump to

Keyboard shortcuts

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