Documentation
¶
Overview ¶
Package proxy implements the SPNEGO/Kerberos HTTP proxy core: client request handling, upstream proxy dialing, CONNECT tunnelling, header rewriting, NO_PROXY matching, response framing, and Kerberos token providers. It is imported by package main, which owns the CLI.
Index ¶
- Constants
- Variables
- func GenerateViaPseudonym() string
- func ParseAllowList(s string) ([]*net.IPNet, error)
- func ResolveNoProxy(flagValue string) string
- func Serve(ctx context.Context, l net.Listener, cfg Config, drainTimeout time.Duration)
- func SplitCSV(s string) []string
- type CircuitBreakerError
- type CircuitBreakerTokenProvider
- type ClientSession
- type Config
- type CredentialError
- type ForwardingConfig
- type Gokrb5TokenProvider
- type NegotiationError
- type NoProxyMatcher
- type TokenProvider
- type UpstreamTLSConfig
Constants ¶
const ( // CBConsecutiveFailures is the number of consecutive GetToken failures // before the circuit opens. Three failures avoids tripping on a single // transient error while still reacting quickly to broken credentials. CBConsecutiveFailures = 3 // CBTimeout is how long the circuit stays open before allowing a single // probe request (half-open state). 30 seconds gives the operator time // to notice logs and act (e.g. kinit) without holding the circuit open // so long that recovery is delayed. CBTimeout = 30 * time.Second )
Default circuit breaker settings.
const ConnectPortWildcard = "*"
ConnectPortWildcard is the sentinel value meaning "allow all ports" in the -connect-ports flag and connectPortAllowed logic.
Variables ¶
var LogLevel = new(slog.LevelVar) // default Info
LogLevel is the dynamic log level for the proxy's slog handler; it defaults to Info and can be raised to Debug via the -debug flag.
Functions ¶
func GenerateViaPseudonym ¶
func GenerateViaPseudonym() string
GenerateViaPseudonym returns a unique pseudonym for this proxy instance, used in the Via header to identify this specific process. The format is "spnego-proxy-<8-hex-chars>", providing 2^32 unique identifiers — sufficient for loop detection across chains of spnego-proxy instances.
func ParseAllowList ¶
ParseAllowList parses a comma-separated string of IPs and CIDR ranges into a slice of *net.IPNet entries for use with ipAllowed.
func ResolveNoProxy ¶
ResolveNoProxy returns the effective no-proxy value by applying the following precedence: explicit flag value > NO_PROXY env var > no_proxy env var. An empty string is returned when none of the sources is set.
func Serve ¶
Serve runs the accept loop until ctx is cancelled, then drains in-flight connections. On cancellation the listener is closed and the function waits up to drainTimeout for active handleClient goroutines to finish before returning. The listener is assumed to already have any concurrency limit (netutil.LimitListener) applied by the caller.
Types ¶
type CircuitBreakerError ¶
type CircuitBreakerError struct {
// contains filtered or unexported fields
}
CircuitBreakerError indicates that the circuit breaker rejected a request. The Unwrap method returns the underlying gobreaker sentinel so callers can further distinguish open vs half-open states with errors.Is if needed.
type CircuitBreakerTokenProvider ¶
type CircuitBreakerTokenProvider struct {
// contains filtered or unexported fields
}
CircuitBreakerTokenProvider wraps a TokenProvider with a circuit breaker that prevents repeated calls to a failing credential backend. This avoids account lockout from rapid authentication failures (e.g. stale Kerberos tickets triggering KDC password-attempt counters).
func NewCircuitBreakerTokenProvider ¶
func NewCircuitBreakerTokenProvider(inner TokenProvider, threshold uint32, timeout time.Duration) *CircuitBreakerTokenProvider
NewCircuitBreakerTokenProvider wraps the given TokenProvider with a circuit breaker. When threshold consecutive GetToken calls fail, the circuit opens and immediately rejects further attempts for timeout. After the timeout, a single probe request is allowed through (half-open); if it succeeds the circuit closes, otherwise it reopens.
func (*CircuitBreakerTokenProvider) Close ¶
func (p *CircuitBreakerTokenProvider) Close() error
Close delegates to the wrapped provider.
func (*CircuitBreakerTokenProvider) GetToken ¶
func (p *CircuitBreakerTokenProvider) GetToken() (string, error)
GetToken acquires a token from the wrapped provider, subject to circuit breaker policy. Returns a *CircuitBreakerError when the circuit is open or half-open, allowing callers to distinguish circuit breaker rejections from other token acquisition failures using errors.As.
type ClientSession ¶ added in v1.3.4
type ClientSession struct {
// contains filtered or unexported fields
}
ClientSession holds the per-connection state for one accepted client connection. Exactly one ClientSession is constructed per handleClient invocation and lives for the duration of that connection.
It replaces the previous convention of threading the (conn, cfg, clientAddr) tuple — plus the lazily-dialled upstream connection and its reader — through every per-connection handler as bare parameters. The per-request *http.Request is NOT session state: it varies every keep-alive iteration and stays a method parameter.
Ownership rules (enforced by structure, previously by convention):
- conn is owned by handleClient, which retains the close/closeWrite defers in their original LIFO order (issue #75).
- reqReader is created exactly once over conn and reused on every keep-alive iteration; recreating it would drop buffered bytes for pipelined requests.
- proxyConn / upstreamReader are lazily dialled on the first via-upstream plain-HTTP request and reused on subsequent ones (RFC 4559 §5); they are closed by the handleClient defer. The CONNECT-via-upstream and noproxy-direct flows use their own method-local connections and never touch these fields.
- cfg is a read-only value snapshot, identical to the previous pass-by-value Config parameter.
type Config ¶
type Config struct {
Upstream string
Provider TokenProvider
Pseudonym string
DialTimeout time.Duration
ReadTimeout time.Duration
KeepAlive time.Duration
IdleTimeout time.Duration
ConnectPorts []string
AllowedIPs []*net.IPNet
NoProxy *NoProxyMatcher
Forwarding ForwardingConfig
UpstreamTLS UpstreamTLSConfig
}
Config groups the non-connection parameters for handleClient.
type CredentialError ¶
type CredentialError struct {
// contains filtered or unexported fields
}
CredentialError indicates that token acquisition failed because Kerberos credentials are expired, missing, or otherwise unavailable. Callers can detect this with errors.As to return a targeted "refresh credentials" message. The Unwrap method preserves the original error for debugging.
type ForwardingConfig ¶
type ForwardingConfig struct {
// ForwardedEnabled enables the RFC 7239 Forwarded header (H1).
// The header uses an obfuscated identifier for the client address.
ForwardedEnabled bool
// XForwardedForEnabled enables the de-facto X-Forwarded-For header (H2)
// and, when the headers are absent, also sets X-Forwarded-Proto (H3) and
// X-Forwarded-Host (H4).
XForwardedForEnabled bool
}
ForwardingConfig controls which forwarding headers the proxy injects into outbound requests.
Both fields default to false (disabled) to preserve the existing behaviour where no forwarding headers are added. Operators opt in via CLI flags.
type Gokrb5TokenProvider ¶
type Gokrb5TokenProvider struct {
// contains filtered or unexported fields
}
Gokrb5TokenProvider uses the pure-Go gokrb5 library for SPNEGO token acquisition. This is the fallback path used on non-macOS platforms or when -user is specified.
Note: the gokrb5 client retains the password string in memory for the lifetime of the process so it can re-authenticate when the TGT expires. Go does not provide primitives to scrub string-backed memory, so the password cannot be reliably zeroed. Use a keytab instead of a password in environments where this is a concern.
func NewGokrb5TokenProvider ¶
func NewGokrb5TokenProvider(user, realm, cfgFile, passwordFile, proxy, explicitSPN string, debug bool) (*Gokrb5TokenProvider, error)
NewGokrb5TokenProvider creates a token provider using gokrb5 with password-based auth.
func (*Gokrb5TokenProvider) Close ¶
func (p *Gokrb5TokenProvider) Close() error
Close destroys the underlying gokrb5 client, releasing its session and cached credentials. It is idempotent and safe for concurrent use.
func (*Gokrb5TokenProvider) GetToken ¶
func (p *Gokrb5TokenProvider) GetToken() (string, error)
GetToken acquires a SPNEGO credential and initializes a security context via gokrb5, returning the base64-encoded SPNEGO token. It is safe for concurrent use.
type NegotiationError ¶
type NegotiationError struct {
// contains filtered or unexported fields
}
NegotiationError indicates that SPNEGO/Kerberos negotiation failed after credentials were available — typically a misconfigured SPN, unreachable KDC, or a token marshalling failure. Callers can detect this with errors.As to return a targeted "check configuration" message.
type NoProxyMatcher ¶
type NoProxyMatcher struct {
// contains filtered or unexported fields
}
NoProxyMatcher pre-parses a comma-separated list of no-proxy bypass patterns for fast matching at request time. Supported pattern forms are: exact hostnames, wildcard domains (*.x or .x), bare IP addresses, and CIDRs.
func NewNoProxyMatcher ¶
func NewNoProxyMatcher(patterns string) *NoProxyMatcher
NewNoProxyMatcher parses a comma-separated string of bypass patterns and returns a ready-to-use NoProxyMatcher. Each pattern is classified at parse time so that Match performs no allocations on the hot path.
type TokenProvider ¶
type TokenProvider interface {
// GetToken returns a base64-encoded SPNEGO token.
GetToken() (string, error)
// Close cleans up any resources.
Close() error
}
TokenProvider acquires SPNEGO tokens for proxy authentication.
func NewNativeTokenProvider ¶
func NewNativeTokenProvider(_, _ string) (TokenProvider, error)
NewNativeTokenProvider on non-darwin platforms returns an error directing the user to provide explicit credentials for gokrb5 password-based auth.
type UpstreamTLSConfig ¶
type UpstreamTLSConfig struct {
Enabled bool
CAFile string
InsecureSkipVerify bool
// TLSConfig is the pre-built *tls.Config constructed once at startup.
// dialUpstream clones it and sets ServerName per connection.
// Call buildTLSConfig to populate it from the other fields.
TLSConfig *tls.Config
// Dialer is the pre-allocated net.Dialer constructed once at startup.
Dialer *net.Dialer
}
UpstreamTLSConfig holds optional TLS settings for the upstream proxy connection.
func (*UpstreamTLSConfig) BuildTLSConfig ¶
func (c *UpstreamTLSConfig) BuildTLSConfig() error
BuildTLSConfig constructs a *tls.Config from the fields of UpstreamTLSConfig and stores it in TLSConfig. It is called once at startup (and in tests) so that dialUpstream can clone the result without re-reading the CA file per connection.