proxy

package
v0.33.1 Latest Latest
Warning

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

Go to latest
Published: Oct 21, 2026 License: MIT Imports: 53 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 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.

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 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_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 ValidateCaptureHeaders added in v0.10.0

func ValidateCaptureHeaders(headers []string) error

ValidateCaptureHeaders checks a capture headers list for validity. Rejects sensitive headers, more than 10 entries, and case-insensitive duplicates.

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 CredentialHeader

type CredentialHeader struct {
	Name  string // Header name (e.g., "Authorization", "x-api-key")
	Value string // Header value (e.g., "Bearer token", "sk-ant-...")
	Grant string // Grant name for logging (e.g., "github", "anthropic")

	// Invalidate, when non-nil, drops whatever cached state produced Value so
	// the next request re-resolves it. The proxy calls it when the destination
	// rejects the credential (401/403), or reports an exhausted allowance (429).
	// Sources are expected to rate-limit their own evictions: these statuses do
	// not reliably distinguish a stale credential from one that will resolve to
	// the same value again.
	// Nil for credentials with no cache behind them (e.g. static headers).
	Invalidate func()

	// UpgradeHint, when non-nil, is sent to the client in UpgradeHintHeader
	// when the destination answers 401 or 403.
	UpgradeHint UpgradeHint

	// AllowInsecure permits injecting this credential into a request whose
	// upstream scheme is http. The default refuses the request.
	AllowInsecure bool
}

CredentialHeader holds a header name and value for credential injection.

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 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 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 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) 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) SetCaptureHeaders added in v0.10.0

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

SetCaptureHeaders configures headers to capture in request logs and strip before forwarding upstream. Header matching is case-insensitive. Returns an error if any header is sensitive (Authorization, Proxy-Authorization, Cookie), if there are more than 10 headers, or if duplicates are present.

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) 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) 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) 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); any other value 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). 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.

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.

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.

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
	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   // the client must send this header with a non-empty value
	Paths   []string // URL path prefixes; empty = any path
	Methods []string // exact upper-case HTTP methods; empty = any method
}

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

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

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