proxy

package
v0.36.0 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2026 License: MIT Imports: 58 Imported by: 1

Documentation

Overview

Package proxy provides a TLS-intercepting HTTP proxy for credential injection.

Security Model

The proxy intercepts HTTPS traffic via CONNECT tunneling with dynamic certificate generation. It injects credentials (Authorization headers, etc.) for configured hosts without exposing raw tokens to the container.

Firewall Integration

Container firewall rules (iptables) work in conjunction with the proxy:

  • Docker: Proxy binds to 127.0.0.1 (localhost only). Containers reach it via host.docker.internal or host network mode. Firewall allows proxy port only.

  • Apple containers: Proxy binds to 0.0.0.0 with per-run token authentication. Security is maintained via cryptographic tokens in HTTP_PROXY URL, not IP filtering.

The firewall rules intentionally do NOT filter by destination IP for the proxy port. This is because host.docker.internal resolves to different IPs across environments. The security boundaries are:

  1. Random high port assignment (reduces collision with other services)
  2. Token authentication for Apple containers
  3. Container isolation (other containers can't reach host ports by default)

This trade-off prioritizes reliability over defense-in-depth. The proxy validates credentials are only injected for explicitly configured hosts.

Index

Constants

View Source
const ExchangeBodyEventName = "gatekeeper.exchange.body"

ExchangeBodyEventName carries one chunk of a body larger than one chunk.

View Source
const ExchangeEventName = "gen_ai.client.inference.operation.details"

ExchangeEventName is the OpenTelemetry GenAI event written for each exchange.

View Source
const MaxBodySize = 8 * 1024

MaxBodySize is the maximum size of request/response bodies to capture (8KB). Only this much is buffered for logging; the full body is always forwarded.

View Source
const UpgradeHintHeader = "X-Gatekeeper-Upgrade"

UpgradeHintHeader carries an UpgradeHint to the client on a 401 or 403 response, as an RFC 9651 dictionary whose members are inner lists of strings. The proxy removes any value the upstream sends.

Variables

This section is empty.

Functions

func FilterHeaders

func FilterHeaders(headers http.Header, injectedHeaders map[string]bool) map[string]string

FilterHeaders creates a copy of headers with sensitive values filtered. injectedHeaders is a set of lower-cased header names whose values should be redacted (credential headers the proxy injected).

func MatchesHostPattern

func MatchesHostPattern(pattern HostPattern, host string, port int) bool

MatchesHostPattern reports whether a host:port matches a parsed host pattern. host must be canonical (ASCII, IDNA names in "xn--" form, no trailing dot).

func NewOTelHandler added in v0.27.0

func NewOTelHandler(next http.Handler, opts OTelOptions) (http.Handler, error)

NewOTelHandler wraps an http.Handler with OpenTelemetry tracing and metrics. If an instrument cannot be created, the handler still works with that instrument disabled and the error reports which ones failed.

func ProxyAuth added in v0.27.0

func ProxyAuth(r *http.Request) (user, pass string, ok bool)

ProxyAuth returns the user and password of a Basic Proxy-Authorization header. ok is false when the header is absent, is not Basic, is not valid base64, or has no ":" separator. Bearer credentials are not Basic and report ok false.

func RecordCredentialInjection

func RecordCredentialInjection(ctx context.Context, host, headerName string)

RecordCredentialInjection increments the credential injection counter with the server address and injected header name as attributes.

func RecordExchangeDrop added in v0.34.0

func RecordExchangeDrop(ctx context.Context)

RecordExchangeDrop counts a model exchange the log skipped.

func RecordPolicyDenial

func RecordPolicyDenial(ctx context.Context, scope, rule string)

RecordPolicyDenial increments the policy denial counter with the policy scope and rule as attributes.

func RegisterGrantHosts

func RegisterGrantHosts(grant string, hosts []string)

RegisterGrantHosts registers host patterns for a grant name. This is used by config-driven providers to register their hosts dynamically. Must be called during initialization only (before concurrent access).

func RequestIDFromContext added in v0.6.0

func RequestIDFromContext(ctx context.Context) string

RequestIDFromContext extracts the request ID from a context, or empty string.

func SanitizeLogValue added in v0.20.0

func SanitizeLogValue(s string) string

SanitizeLogValue prepares a client-controlled string for inclusion in a structured log line: it discards invalid UTF-8, strips control characters (newlines, carriage returns, NUL, tabs, ...) so the value cannot forge additional log lines or otherwise corrupt structured log output, and bounds the result to maxCapturedLogValueLen bytes, truncating at a valid UTF-8 boundary rather than splitting a multi-byte rune. Used for both captured HTTP header values (capture_request_headers and capture_response_headers) and the Postgres application_name startup parameter — both are free-form text supplied by the client, not constrained by any protocol grammar the way most other logged fields are.

func ValidateCaptureRequestHeaders added in v0.33.2

func ValidateCaptureRequestHeaders(headers []string) error

ValidateCaptureRequestHeaders checks a request-header capture list.

func ValidateCaptureResponseHeaders added in v0.33.2

func ValidateCaptureResponseHeaders(headers []string) error

ValidateCaptureResponseHeaders checks a response-header capture list.

func ValidateHostPattern added in v0.27.0

func ValidateHostPattern(s string) error

ValidateHostPattern reports why s is not a usable host pattern. Unlike ParseHostPattern, which treats an unparsable port as "ports 80 and 443", it rejects the pattern.

func WrapProxyProtocolListener added in v0.18.0

func WrapProxyProtocolListener(ln net.Listener) net.Listener

WrapProxyProtocolListener wraps ln with PROXY protocol v1/v2 parsing, matching HAProxy's per-bind `accept-proxy` semantics: a leading PROXY header is honored when present, and a connection that doesn't open with one falls back to its real TCP peer address (fail-open) rather than being rejected — so load balancer health checks and direct probes of the port keep working. The header-read is bounded by a 10s timeout, and a connection whose header is present but fails to parse is dropped and logged once at DEBUG (a connection with no header at all stays silent — that's the correct fail-open path, not an error).

Both gatekeeper's HTTP/CONNECT listener and its Postgres data-plane listener call this helper so their PROXY protocol handling is byte-identical; see ProxyConfig.ProxyProtocol and PostgresConfig.ProxyProtocol.

Types

type Branding added in v0.27.0

type Branding struct {
	// Name prefixes block messages ("<Name>: request blocked ..."). Default "gatekeeper".
	Name string
	// ErrorPrefix prefixes relay and MCP error bodies and the CONNECT dial
	// and plain-HTTP upstream failure bodies. Default: Name.
	ErrorPrefix string
	// MCPStubPrefix starts the placeholder value that stands in for an MCP
	// credential in client config. Default "gatekeeper-stub-".
	MCPStubPrefix string
	// PolicyScheme is the Proxy-Authenticate scheme on blocked responses.
	// Default "Gatekeeper-Policy".
	PolicyScheme string
	// NetworkHint is appended to a network-policy block message.
	NetworkHint string
	// HostHint returns text appended to a host-service block message.
	HostHint func(port int) string
	// GrantHint returns text appended when an MCP credential cannot be loaded.
	GrantHint func(grant string) string
}

Branding sets the client-visible wording of blocked responses and relay errors. The zero value is neutral; an embedder sets only what it wants to phrase differently.

type CA

type CA struct {
	// contains filtered or unexported fields
}

CA represents a certificate authority for TLS interception. The key field is a crypto.Signer so both RSA and EC CA keys are supported.

func LoadCA

func LoadCA(certPEM, keyPEM []byte) (*CA, error)

LoadCA loads a CA from PEM-encoded certificate and key bytes. Supports RSA (PKCS1, PKCS8) and EC (PKCS8, EC PRIVATE KEY) key formats.

func NewCA

func NewCA(caDir string) (*CA, error)

NewCA creates or loads a CA certificate. If the CA files exist at the given path, they are loaded. Otherwise, a new CA is generated and saved.

func NewCAWithSubject added in v0.27.0

func NewCAWithSubject(caDir, organization, commonName string) (*CA, error)

NewCAWithSubject is NewCA with the subject of a newly generated CA set to the given organization and common name. A CA loaded from caDir keeps the subject it was created with.

func (*CA) CertPEM

func (ca *CA) CertPEM() []byte

CertPEM returns the CA certificate in PEM format.

func (*CA) GenerateCert

func (ca *CA) GenerateCert(host string) (*tls.Certificate, error)

GenerateCert creates a certificate for the given host signed by the CA.

type Credential added in v0.33.2

type Credential struct {
	Host string `yaml:"host"`
	// Header is the configured name for the credential output header.
	Header string `yaml:"header,omitempty"`
	// Name and Value hold the resolved HTTP header and value.
	Name  string `yaml:"-"`
	Value string `yaml:"-"`
	// Prefix and Format control how a source value becomes an HTTP header value.
	Prefix   string                    `yaml:"prefix,omitempty"`
	Format   string                    `yaml:"format,omitempty"`
	Source   SourceConfig              `yaml:"source"`
	Grant    string                    `yaml:"grant,omitempty"`
	Postgres *PostgresCredentialConfig `yaml:"postgres,omitempty"`
	// Match selects the credential by request header presence, path prefixes, and methods.
	Match *RequestMatch `yaml:"match,omitempty"`
	// AddHeaders append tokens after RemoveHeaders when this credential is injected.
	AddHeaders map[string]string `yaml:"add_headers,omitempty"`
	// RemoveHeaders are removed when this credential is injected unless credentials write them.
	RemoveHeaders []string `yaml:"remove_headers,omitempty"`
	// AllowInsecure permits injection into plain HTTP requests.
	AllowInsecure bool `yaml:"allow_insecure,omitempty"`
	// Resolver provides a runtime credential instead of a Source value.
	Resolver CredentialResolver `yaml:"-"`

	// Invalidate drops cached state that produced Value when the destination
	// rejects the credential or reports an exhausted allowance.
	Invalidate func() `yaml:"-"`

	UpgradeHint UpgradeHint `yaml:"-"`
}

Credential carries YAML policy and the resolved header or resolver for one credential.

func (Credential) ValidateAgainst added in v0.33.2

func (credential Credential) ValidateAgainst(earlier []Credential) error

ValidateAgainst rejects a credential whose request match is identical to or fully covered by an earlier match.

func (Credential) ValidateRequestPolicy added in v0.33.2

func (credential Credential) ValidateRequestPolicy() error

ValidateRequestPolicy checks the credential's request matching and header transforms.

type CredentialBundle added in v0.23.0

type CredentialBundle struct {
	ID           string
	Grant        string
	Scope        CredentialScope
	Replacements []HeaderReplacement
	RequireAll   bool
}

CredentialBundle groups headers that must be selected and replaced as one atomic credential.

type CredentialHeader

type CredentialHeader = Credential

CredentialHeader is the resolved-header name for Credential.

type CredentialRequiredError added in v0.30.0

type CredentialRequiredError struct {
	Grant  string
	Host   string
	Reason string
}

CredentialRequiredError reports a request that an explicit-only decline left with no credential. The proxy answers it with 403.

func (*CredentialRequiredError) Error added in v0.30.0

func (e *CredentialRequiredError) Error() string

type CredentialResolver added in v0.5.0

type CredentialResolver func(ctx context.Context, proxyReq, innerReq *http.Request, host string) ([]CredentialHeader, error)

CredentialResolver resolves credentials dynamically per-request. Unlike static credentials (pre-resolved at startup), resolvers may inspect and modify the request (e.g., strip a subject identity header) and may make external calls (e.g., RFC 8693 token exchange).

proxyReq is the original proxy-level request (CONNECT for intercepted connections, or the same as innerReq for plain HTTP/relay). It carries Proxy-Authorization and other proxy-hop headers. innerReq is the application-level request the resolver may inspect and modify.

Returns nil with no error when the resolver has no credentials to offer.

type CredentialScope added in v0.23.0

type CredentialScope struct {
	RequireTLS   bool
	Origins      []string
	Methods      []string
	PathPrefixes []string
}

CredentialScope limits where a credential bundle may be injected.

type Demux added in v0.19.0

type Demux struct {
	// contains filtered or unexported fields
}

Demux owns a single real listener carrying both HTTP/CONNECT proxy traffic and Postgres data-plane traffic. It classifies each accepted connection by its first bytes (see classifyPrefix) in its own goroutine — never in the accept loop — and routes it to one of two virtual listeners, so a silent or slow client can never stall Accept for every other pending connection (the same discipline WrapProxyProtocolListener's Accept follows, and for the same reason: see proxyproto.go).

func NewDemux added in v0.19.0

func NewDemux(ln net.Listener) *Demux

NewDemux begins accepting connections on ln — which may already be wrapped with WrapProxyProtocolListener, in which case its PROXY header (if any) is consumed lazily on the demux's own sniff Read, ahead of classification — and routes each one to HTTPListener or PostgresListener based on its first bytes.

func (*Demux) Close added in v0.19.0

func (d *Demux) Close() error

Close closes the real listener and both virtual listeners. It's meant for a Demux used standalone (e.g. tests): an embedder that separately owns graceful shutdown of the two downstream servers — as gatekeeper.go does — should call StopAccepting instead and let each server close its own virtual listener as part of its own Shutdown/Stop.

func (*Demux) HTTPListener added in v0.19.0

func (d *Demux) HTTPListener() net.Listener

HTTPListener returns the virtual listener carrying HTTP/CONNECT proxy traffic. Pass it to http.Server.Serve unmodified.

func (*Demux) PostgresListener added in v0.19.0

func (d *Demux) PostgresListener() net.Listener

PostgresListener returns the virtual listener carrying Postgres data-plane traffic. Pass it to PostgresServer.StartListener unmodified.

func (*Demux) StopAccepting added in v0.19.0

func (d *Demux) StopAccepting() error

StopAccepting closes only the real listener, so no further connections are accepted and classified. It deliberately leaves both virtual listeners open: PostgresServer.Shutdown/Stop and http.Server.Shutdown each close the virtual listener they were started on as part of their own graceful drain, and closing it out from under them here first would race their own closed-flag bookkeeping — for PostgresServer that would surface as a spurious "postgres accept loop exited" error log on an entirely ordinary shutdown.

type ExtraHeader

type ExtraHeader struct {
	Name  string
	Value string
}

ExtraHeader holds an additional header to inject for a host.

type HeaderReplacement added in v0.23.0

type HeaderReplacement struct {
	Name        string
	Placeholder string
	Value       string
}

HeaderReplacement describes one exact placeholder-to-secret replacement. A bundle is validated in full before any replacement is applied.

type HostPattern

type HostPattern struct {
	// contains filtered or unexported fields
}

HostPattern represents a parsed host pattern for matching.

func ParseHostPattern

func ParseHostPattern(pattern string) HostPattern

ParseHostPattern is the exported wrapper for parseHostPattern.

type MCPAuthConfig

type MCPAuthConfig struct {
	Grant  string
	Header string

	// AllowInsecure permits injecting the credential into a request to an
	// http:// MCP server. The default refuses the request.
	AllowInsecure bool
}

MCPAuthConfig defines authentication for an MCP server.

type MCPServerConfig

type MCPServerConfig struct {
	Name string
	URL  string
	Auth *MCPAuthConfig
}

MCPServerConfig holds the MCP server configuration needed by the proxy.

type OTelOptions added in v0.27.0

type OTelOptions struct {
	TracerProvider trace.TracerProvider
	MeterProvider  metric.MeterProvider
}

OTelOptions selects the OpenTelemetry providers for NewOTelHandler. A nil provider falls back to the corresponding global provider.

type PolicyLogData

type PolicyLogData struct {
	RunID     string
	Scope     string
	Operation string
	Rule      string
	Message   string
	Ctx       context.Context // Request context (for OTel span extraction, may be nil)

	// NonBlocking marks an entry that policy noticed but did not stop.
	// Consumers alert and count on denials, so an observation reported as one
	// would page an operator for traffic nothing blocked.
	//
	// The sense is negative on purpose: the zero value is a denial. This struct
	// is built as a literal in several places, and a field defaulting to
	// "observation" would silently drop any denial whose author did not know to
	// set it — the failure is silence in an alerting path, which is strictly
	// worse than an extra warning. Getting it wrong now over-reports.
	NonBlocking bool
}

PolicyLogData contains data for a policy denial event.

type PostgresCredentialConfig added in v0.33.2

type PostgresCredentialConfig struct {
	Resolver   string `yaml:"resolver"`
	Project    string `yaml:"project,omitempty"`
	ProjectEnv string `yaml:"project_env,omitempty"`
}

PostgresCredentialConfig configures a Postgres credential resolver.

type PostgresCredentialResolver added in v0.12.0

type PostgresCredentialResolver interface {
	// ResolvePassword returns the password the proxy should present to the
	// upstream server for the given host, user, and database.
	ResolvePassword(ctx context.Context, host, user, database string) (string, error)
	// InvalidatePassword drops any cached password for the tuple. The proxy
	// calls it after an upstream authentication failure, then retries once.
	InvalidatePassword(host, user, database string)
}

PostgresCredentialResolver resolves a Postgres password for a specific upstream host, role, and database at connection time. Implementations must never log password values.

func NewStaticPostgresResolver added in v0.12.0

func NewStaticPostgresResolver(password string) PostgresCredentialResolver

NewStaticPostgresResolver creates a resolver that always returns password.

type PostgresServer added in v0.12.0

type PostgresServer struct {
	// contains filtered or unexported fields
}

PostgresServer is the client-facing Postgres listener. It terminates TLS with a certificate minted from the proxy's CA for the SNI hostname, requires the client to present a cleartext password (the run token, which is safe inside the TLS tunnel), and resolves that token to a per-run context. Plaintext connections are refused before any credential is requested.

func NewPostgresServer added in v0.12.0

func NewPostgresServer(p *Proxy) *PostgresServer

NewPostgresServer creates a Postgres listener backed by the given proxy. The proxy supplies the CA (for TLS termination), the context resolver / auth token (for run-token authentication), and the Postgres credential resolvers (for upstream credentials).

func (*PostgresServer) Addr added in v0.12.0

func (s *PostgresServer) Addr() string

Addr returns the listener's address, or "" if the server is not listening.

func (*PostgresServer) Shutdown added in v0.12.0

func (s *PostgresServer) Shutdown(ctx context.Context) error

Shutdown stops accepting new connections and waits for active relays to drain. If ctx expires first, it force-closes the remaining connections (interrupting in-flight queries) and returns ctx.Err().

A connection that is mid-credential-resolution when shutdown begins blocks the drain until that resolution returns, since a singleflight-shared Neon API call cannot be interrupted by closing the client socket. The standalone server bounds this by closing credential resolvers before calling Shutdown (see NeonResolver.Close), which cancels in-flight API calls; an embedder that wires Shutdown directly should close its resolvers first for the same reason.

func (*PostgresServer) Start added in v0.12.0

func (s *PostgresServer) Start(addr string) error

Start binds the listener on addr and begins accepting connections. It returns an error if the proxy has no CA, since the listener cannot terminate TLS without one.

func (*PostgresServer) StartListener added in v0.18.0

func (s *PostgresServer) StartListener(ln net.Listener) error

StartListener begins accepting connections on a pre-created listener instead of binding one itself, so the caller can wrap it first — e.g. with WrapProxyProtocolListener, to add PROXY protocol support (see PostgresConfig.ProxyProtocol). It performs the same CA check as Start.

func (*PostgresServer) Stop added in v0.12.0

func (s *PostgresServer) Stop()

Stop closes the listener immediately and force-closes active connections without waiting for relays to finish. Prefer Shutdown for graceful drain.

type Proxy

type Proxy struct {
	// contains filtered or unexported fields
}

Proxy is an HTTP proxy that injects credentials into outgoing requests.

Security Model

The proxy handles two distinct security concerns:

  1. Credential injection: The proxy injects credential headers for configured hosts (e.g., api.github.com, api.anthropic.com). When CA is set, it performs TLS interception (MITM) to inject headers into HTTPS requests. Supports custom header names (Authorization, x-api-key, etc).

  2. Proxy authentication: When authToken is set, clients must authenticate to the proxy itself via Proxy-Authorization header. This prevents unauthorized access when the proxy binds to all interfaces (0.0.0.0), which is required for Apple containers that access the host via gateway IP rather than localhost.

For Docker containers, the proxy binds to localhost (127.0.0.1) and authentication is not required. For Apple containers, the proxy binds to all interfaces with a cryptographically secure token for authentication.

func NewProxy

func NewProxy() *Proxy

NewProxy creates a new auth proxy.

func (*Proxy) AddExtraHeader

func (p *Proxy) AddExtraHeader(host, headerName, headerValue string)

AddExtraHeader adds an additional header to inject for a host. This is used for headers beyond the main credential header, such as beta feature flags or API version headers. The host must be a valid hostname (not empty, no path components).

func (*Proxy) AddRelay

func (p *Proxy) AddRelay(name, targetURL string) error

AddRelay registers a named relay endpoint. Requests to /relay/{name}/{path...} are forwarded to the target URL with credential injection. This is used when the target host would be in NO_PROXY (e.g., a host-side proxy reachable via the same address as the proxy), which would cause direct connections to bypass credential injection.

AddRelay must be called before the proxy starts serving.

func (*Proxy) AddResponseTransformer

func (p *Proxy) AddResponseTransformer(host string, transformer ResponseTransformer)

AddResponseTransformer registers a response transformer for a host. Transformers are called in registration order after receiving the upstream response. Each transformer can inspect and optionally modify the response. The host must be a valid hostname (not empty, no path components).

func (*Proxy) CloseConnections added in v0.27.0

func (p *Proxy) CloseConnections(ctx context.Context) error

CloseConnections refuses new CONNECT tunnels and intercepted connections, closes the open ones, and waits for their goroutines to exit or ctx to end.

func (*Proxy) CloseExchangeLog added in v0.34.0

func (p *Proxy) CloseExchangeLog(ctx context.Context)

CloseExchangeLog stops accepting exchanges and waits for the queued ones to be written, until ctx ends.

func (*Proxy) CloseIdleConnections added in v0.25.1

func (p *Proxy) CloseIdleConnections()

CloseIdleConnections closes idle connections in the shared upstream pools. Active requests and per-tunnel HTTP/1.1 transports are unaffected.

func (*Proxy) RemoveRequestHeader

func (p *Proxy) RemoveRequestHeader(host, headerName string)

RemoveRequestHeader removes a client-sent header before forwarding.

func (*Proxy) ResolveContext

func (p *Proxy) ResolveContext(token string) (*RunContextData, bool)

ResolveContext looks up per-run context data by auth token. Returns nil, false when no resolver is set or the token is not found.

func (*Proxy) ServeHTTP

func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP handles proxy requests.

func (*Proxy) SetAWSHandler

func (p *Proxy) SetAWSHandler(h http.Handler)

SetAWSHandler sets the handler for AWS credential requests.

func (*Proxy) SetAuthToken

func (p *Proxy) SetAuthToken(token string)

SetAuthToken sets the required authentication token for proxy access.

func (*Proxy) SetBranding added in v0.27.0

func (p *Proxy) SetBranding(b Branding)

SetBranding replaces the proxy's client-visible wording. Call it before serving.

func (*Proxy) SetCA

func (p *Proxy) SetCA(ca *CA)

SetCA sets the CA for TLS interception.

func (*Proxy) SetCaptureRequestHeaders added in v0.33.2

func (p *Proxy) SetCaptureRequestHeaders(headers []string) error

SetCaptureRequestHeaders configures request headers to capture and strip.

func (*Proxy) SetCaptureResponseHeaders added in v0.33.2

func (p *Proxy) SetCaptureResponseHeaders(headers []string) error

SetCaptureResponseHeaders configures response headers to capture in logs.

func (*Proxy) SetContextResolver

func (p *Proxy) SetContextResolver(resolver func(token string) (*RunContextData, bool))

SetContextResolver sets the per-run context resolver for multi-tenant proxy use. When set, the proxy can resolve auth tokens to per-run credential data.

func (*Proxy) SetCredential

func (p *Proxy) SetCredential(host, authHeader string)

SetCredential sets the credential for a host using the Authorization header.

func (*Proxy) SetCredentialEntry added in v0.33.2

func (p *Proxy) SetCredentialEntry(credential CredentialHeader) error

SetCredentialEntry registers a credential with optional request matching and header transforms.

func (*Proxy) SetCredentialHeader

func (p *Proxy) SetCredentialHeader(host, headerName, headerValue string)

SetCredentialHeader sets a custom credential header for a host. Use this for APIs that use non-standard header names like "x-api-key". The host must be a valid hostname (not empty, no path components).

func (*Proxy) SetCredentialResolver added in v0.5.0

func (p *Proxy) SetCredentialResolver(host string, resolver CredentialResolver)

SetCredentialResolver registers a dynamic credential resolver for a host. Unlike static credentials, resolvers are called per-request and may make external calls (e.g., RFC 8693 token exchange). Only one resolver per host is supported; calling again for the same host replaces the previous resolver. Passing a nil resolver disables resolution for that host: the nil entry matches and terminates the lookup, so a broader (bare-host or wildcard) resolver does not apply.

A resolver that removes request headers (e.g. a subject-identity header it consumes) should be registered with SetCredentialResolverWithStripHeaders instead, so the removal still happens when the proxy skips the resolver because a better-matched static credential exists.

func (*Proxy) SetCredentialResolverEntry added in v0.33.2

func (p *Proxy) SetCredentialResolverEntry(credential CredentialHeader, stripHeaders ...string) error

SetCredentialResolverEntry registers Credential.Resolver with the request policy.

func (*Proxy) SetCredentialResolverWithStripHeaders added in v0.15.3

func (p *Proxy) SetCredentialResolverWithStripHeaders(host string, resolver CredentialResolver, stripHeaders ...string)

SetCredentialResolverWithStripHeaders registers a dynamic credential resolver together with the request headers it consumes and removes. When a better-matched static credential outranks the resolver, the resolver is not called — its external round trip must not stall requests whose credential is already decided — and the proxy removes the declared headers itself so they never leak upstream.

func (*Proxy) SetCredentialStore

func (p *Proxy) SetCredentialStore(store interface {
	GetToken(provider string) (string, error)
})

SetCredentialStore sets the credential store for MCP credential retrieval.

func (*Proxy) SetCredentialWithGrant

func (p *Proxy) SetCredentialWithGrant(host, headerName, headerValue, grant string)

SetCredentialWithGrant sets a credential header with grant info for logging. Grant is used for structured logging to identify the credential source. If a credential with the same grant and header name already exists for the host, it is updated in place (upsert). Otherwise, a new entry is appended. Matching on both grant and header name prevents empty-grant collisions when SetCredentialHeader is called multiple times with different headers.

func (*Proxy) SetDelegateAuth added in v0.5.2

func (p *Proxy) SetDelegateAuth(delegate bool) error

SetDelegateAuth makes ServeHTTP require a non-empty proxy auth password and leave its validation to credential resolvers. With authToken empty, no static check applies to the password; with authToken set, ServeHTTP still requires a presented password to equal it before any resolver runs.

Delegation without an auth token would accept any non-empty password, so SetDelegateAuth(true) with no token returns an error, and ServeHTTP refuses every request until SetAuthToken supplies one.

func (*Proxy) SetExchangeLog added in v0.34.0

func (p *Proxy) SetExchangeLog(logger *slog.Logger, hosts []string) error

SetExchangeLog logs the request and response of every inference request (OpenAI Responses and Anthropic Messages) to one of hosts, as a GenAI event on logger. Traffic to any other host is never captured. Call it once, before the proxy serves traffic.

func (*Proxy) SetInsecureCredentialWithGrant added in v0.27.0

func (p *Proxy) SetInsecureCredentialWithGrant(host, headerName, headerValue, grant string)

SetInsecureCredentialWithGrant is SetCredentialWithGrant for a credential that may also be injected into plain-HTTP requests.

func (*Proxy) SetInsecureTokenSubstitution added in v0.27.0

func (p *Proxy) SetInsecureTokenSubstitution(host, placeholder, realToken string)

SetInsecureTokenSubstitution is SetTokenSubstitution for a substitution that may also apply to plain-HTTP requests.

func (*Proxy) SetLogger

func (p *Proxy) SetLogger(logger func(data RequestLogData))

SetLogger sets the request logger.

func (*Proxy) SetMCPServers

func (p *Proxy) SetMCPServers(servers []MCPServerConfig)

SetMCPServers configures MCP servers for credential injection.

func (*Proxy) SetMatchedCredentialResolver added in v0.32.0

func (p *Proxy) SetMatchedCredentialResolver(host string, m RequestMatch, resolver CredentialResolver, stripHeaders ...string)

SetMatchedCredentialResolver appends a resolver for host that applies only to requests matching m. Entries on one host are tried in registration order; the first match wins. SetCredentialResolver and SetCredentialResolverWithStripHeaders replace every entry on the host, including matched ones. A nil resolver or an empty match (no header, paths, or methods) is ignored.

func (*Proxy) SetNetworkPolicy

func (p *Proxy) SetNetworkPolicy(policy string, allows []string, grants []string) error

SetNetworkPolicy sets the network policy and allowed hosts. policy must be "permissive" or "strict" (empty means permissive), and every entry of allows must pass ValidateHostPattern; otherwise it returns an error and changes nothing. allows is a list of host patterns like "api.example.com" or "*.example.com". grants is a list of grant names like "github" that will be expanded to host patterns.

func (*Proxy) SetNetworkPolicyWithRules

func (p *Proxy) SetNetworkPolicyWithRules(policy string, allows []string, grants []string, checker func(host string, port int, method, path string) bool, pathChecker func(host string, port int) bool) error

SetNetworkPolicyWithRules sets the network policy with per-host request rules. The allows list should include hosts from rules (the caller extracts them); as in SetNetworkPolicy, each entry must pass ValidateHostPattern. checker evaluates per-request rules; pathChecker reports if path-level rules exist.

func (*Proxy) SetPolicyLogger

func (p *Proxy) SetPolicyLogger(logger func(data PolicyLogData))

SetPolicyLogger sets the policy decision logger.

func (*Proxy) SetPostgresResolver added in v0.12.0

func (p *Proxy) SetPostgresResolver(pattern string, r PostgresCredentialResolver)

SetPostgresResolver registers a proxy-level Postgres credential resolver for hosts matching pattern. Calling again with the same pattern replaces the previous resolver. Patterns match hostname only; a pattern with an explicit port other than 5432 will never match. An invalid pattern (see ValidateHostPattern) is ignored and logged at debug level.

func (*Proxy) SetTokenSubstitution

func (p *Proxy) SetTokenSubstitution(host, placeholder, realToken string)

SetTokenSubstitution replaces placeholder tokens with real tokens in both Authorization headers and request bodies for a specific host. host is a host pattern or an exact IP literal, optionally pinned to a port; an invalid one is ignored and logged at debug level.

func (*Proxy) SetUpstreamCAs

func (p *Proxy) SetUpstreamCAs(pool *x509.CertPool)

SetUpstreamCAs sets a custom CA pool for verifying upstream (origin server) TLS certificates during CONNECT interception. When nil (the default), the system root certificates are used. This is useful for environments with private PKI or for testing.

func (*Proxy) UpdateCredentialEntry added in v0.33.2

func (p *Proxy) UpdateCredentialEntry(credential CredentialHeader) error

UpdateCredentialEntry replaces a registered static credential with the same host, grant, header, and match. It rejects resolver-backed credentials.

type RequestLogData

type RequestLogData struct {
	RequestID       string // Unique request identifier (from X-Request-Id or generated)
	Method          string
	URL             string
	Host            string // Target hostname (extracted from URL or CONNECT)
	Path            string // Request path (empty for CONNECT tunnel-level logs)
	RequestType     string // "http", "connect", "mcp", "relay", "postgres"
	StatusCode      int
	Duration        time.Duration
	Err             error
	RequestHeaders  http.Header
	ResponseHeaders http.Header
	// CapturedResponseHeaders contains configured response headers for logs.
	CapturedResponseHeaders http.Header
	RequestBody             []byte
	ResponseBody            []byte
	RequestSize             int64           // Content-Length of the request body, -1 if unknown. Always -1 for postgres connections (see RequestMessages).
	ResponseSize            int64           // For the streaming relay/MCP paths, the actual bytes delivered to the client; for other paths, the response Content-Length (-1 if unknown). Always -1 for postgres connections (see ResponseMessages).
	RequestMessages         int64           // Postgres protocol messages relayed client→upstream; 0 for non-postgres connections.
	ResponseMessages        int64           // Postgres protocol messages relayed upstream→client; 0 for non-postgres connections.
	AuthInjected            bool            // True if any credential header was injected for this host
	InjectedHeaders         map[string]bool // Lower-cased header names that were injected
	Grants                  []string        // Credential grant names that were injected
	Denied                  bool            // True if request was denied by network/keep policy
	DenyReason              string          // Why the request was denied (e.g., "network_policy", "keep_policy")
	RunID                   string          // Run ID from per-run context (daemon mode)
	UserID                  string          // User ID from proxy auth username. For postgres connections this carries the Postgres role, not the proxy auth user.
	Ctx                     context.Context // Request context (for OTel span extraction, may be nil)

	// ClientAddr is the client's network address ("ip:port") as seen by the
	// listener. For intercepted CONNECT traffic it is the address of the
	// client that opened the tunnel, not the address of the (hijacked,
	// TLS-terminated) connection carrying the individual inner requests. For
	// postgres connections it is the TCP peer of the data-plane listener.
	ClientAddr string

	// ApplicationName is the client-supplied Postgres "application_name"
	// startup parameter, captured for request tracing. It is free-form,
	// client-controlled text (sanitized before being placed here — see
	// SanitizeLogValue) and, unlike RunID, is a correlation slug rather than
	// a trusted identity: a client can set it to anything, including another
	// caller's label. Empty for non-postgres connections and for postgres
	// connections whose client did not set it.
	ApplicationName string
}

RequestLogData contains all data for a logged request. Designed for canonical log lines: one wide structured entry per request.

type RequestMatch added in v0.32.0

type RequestMatch struct {
	Header  string   `yaml:"header"`            // non-empty request header required for a match
	Paths   []string `yaml:"paths,omitempty"`   // path prefixes; any prefix matches
	Methods []string `yaml:"methods,omitempty"` // exact upper-case methods; any method matches
}

RequestMatch selects a credential for a request. A zero field matches anything.

func (RequestMatch) Equal added in v0.33.2

func (m RequestMatch) Equal(o RequestMatch) bool

Equal reports whether two request predicates have the same fields.

type ResolverDeclined added in v0.29.0

type ResolverDeclined struct {
	// ExplicitOnly limits the static credentials to those whose header the
	// client sent. With none sent, the proxy refuses the request with 403,
	// naming Grant.
	ExplicitOnly bool
	Grant        string
	// Reason, when set, explains the refusal to the client. It must be
	// printable ASCII.
	Reason      string
	UpgradeHint UpgradeHint
	// Invalidate, when non-nil, drops the cached decline. The proxy calls it
	// on the same statuses as CredentialHeader.Invalidate, so a credential
	// granted since the decline applies to the client's retry.
	Invalidate func()
}

ResolverDeclined is the error a CredentialResolver returns when it has no credential for the request and the host's static credentials apply.

func (*ResolverDeclined) Error added in v0.29.0

func (*ResolverDeclined) Error() string

type ResponseTransformer

type ResponseTransformer func(req, resp any) (any, bool)

ResponseTransformer transforms HTTP responses before body capture. Cast to *http.Request and *http.Response in the transformer implementation. Returns the modified response and true if transformed, or original and false.

type RunContextData

type RunContextData struct {
	RunID             string
	Credentials       map[string][]CredentialHeader
	CredentialBundles []CredentialBundle

	ExtraHeaders         map[string][]ExtraHeader
	RemoveHeaders        map[string][]string
	TokenSubstitutions   map[string]*TokenSubstitution
	ResponseTransformers map[string][]ResponseTransformer
	MCPServers           []MCPServerConfig
	Policy               string
	AllowedHosts         []HostPattern
	RequestCheck         func(host string, port int, method, path string) bool
	PathRulesCheck       func(host string, port int) bool
	AWSHandler           http.Handler
	CredStore            interface {
		GetToken(provider string) (string, error)
	}
	KeepEngines      map[string]*keeplib.Engine
	HostGateway      string
	HostGatewayIP    string // actual IP to forward allowed host-gateway requests to
	AllowedHostPorts []int
	// contains filtered or unexported fields
}

RunContextData holds per-run credential data resolved by the context resolver. The host-keyed maps are read without synchronization on every request — including linear scans for wildcard and case-fold matching — so they must not be mutated after the RunContextData is registered with the proxy.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server wraps a Proxy in an HTTP server.

func NewServer

func NewServer(proxy *Proxy) *Server

NewServer creates a new proxy server.

func (*Server) Addr

func (s *Server) Addr() string

Addr returns the proxy server address (host:port).

func (*Server) Port

func (s *Server) Port() string

Port returns just the port number the proxy is listening on.

func (*Server) Proxy

func (s *Server) Proxy() *Proxy

Proxy returns the underlying proxy.

func (*Server) SetBindAddr

func (s *Server) SetBindAddr(addr string)

SetBindAddr sets the address to bind to. Use "0.0.0.0" to bind to all interfaces (needed for Apple containers which access host via gateway IP). Must be called before Start().

func (*Server) SetPort

func (s *Server) SetPort(port int)

SetPort sets the port to bind to. Use 0 (default) for an OS-assigned port. Must be called before Start().

func (*Server) Start

func (s *Server) Start() error

Start starts the proxy server on an available port. By default binds to localhost only to prevent credential exposure to other hosts on the network. Use SetBindAddr("0.0.0.0") before Start() to bind to all interfaces (needed for Apple containers).

func (*Server) Stop

func (s *Server) Stop(ctx context.Context) error

Stop stops the proxy server.

type SourceConfig added in v0.33.2

type SourceConfig struct {
	Type    string `yaml:"type"`
	Var     string `yaml:"var,omitempty"`
	Value   string `yaml:"value,omitempty"`
	Command string `yaml:"command,omitempty"`
	TTL     string `yaml:"ttl,omitempty"`
	Secret  string `yaml:"secret,omitempty"`
	Region  string `yaml:"region,omitempty"`
	Project string `yaml:"project,omitempty"`
	Version string `yaml:"version,omitempty"`

	AppID                string `yaml:"app_id,omitempty"`
	InstallationID       string `yaml:"installation_id,omitempty"`
	PrivateKeyPath       string `yaml:"private_key_path,omitempty"`
	PrivateKeyEnv        string `yaml:"private_key_env,omitempty"`
	Scopes               string `yaml:"scopes,omitempty"`
	ADC                  bool   `yaml:"adc,omitempty"`
	TargetServiceAccount string `yaml:"target_service_account,omitempty"`
	Endpoint             string `yaml:"endpoint,omitempty"`
	ClientID             string `yaml:"client_id,omitempty"`
	ClientSecret         string `yaml:"client_secret,omitempty"`
	ClientSecretEnv      string `yaml:"client_secret_env,omitempty"`
	SubjectHeader        string `yaml:"subject_header,omitempty"`
	SubjectFrom          string `yaml:"subject_from,omitempty"`
	SubjectTokenType     string `yaml:"subject_token_type,omitempty"`
	Resource             string `yaml:"resource,omitempty"`
	ActorTokenFrom       string `yaml:"actor_token_from,omitempty"`
	ActorTokenType       string `yaml:"actor_token_type,omitempty"`
	BotSubject           string `yaml:"bot_subject,omitempty"`
	StaticFallback       string `yaml:"static_fallback,omitempty"`
}

SourceConfig describes where to read a credential value from. It is comparable because Gatekeeper deduplicates identical sources.

type TokenSubstitution

type TokenSubstitution struct {

	// AllowInsecure permits substituting the token into a request whose
	// upstream scheme is http. The default refuses the request.
	AllowInsecure bool
	// contains filtered or unexported fields
}

TokenSubstitution maps a placeholder string to the real token for a host.

func NewTokenSubstitution

func NewTokenSubstitution(placeholder, realToken string) *TokenSubstitution

NewTokenSubstitution creates a TokenSubstitution with the given placeholder and real token.

type UpgradeHint added in v0.29.0

type UpgradeHint map[string][]string

UpgradeHint is what a credential resolver reports the client could request beyond the credential the request got. Each key maps to one or more values.

Jump to

Keyboard shortcuts

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