proxy

package
v1.3.5 Latest Latest
Warning

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

Go to latest
Published: May 30, 2026 License: MIT Imports: 28 Imported by: 0

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

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

View Source
const ConnectPortWildcard = "*"

ConnectPortWildcard is the sentinel value meaning "allow all ports" in the -connect-ports flag and connectPortAllowed logic.

Variables

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

func ParseAllowList(s string) ([]*net.IPNet, error)

ParseAllowList parses a comma-separated string of IPs and CIDR ranges into a slice of *net.IPNet entries for use with ipAllowed.

func ResolveNoProxy

func ResolveNoProxy(flagValue string) string

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

func Serve(ctx context.Context, l net.Listener, cfg Config, drainTimeout time.Duration)

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.

func SplitCSV

func SplitCSV(s string) []string

SplitCSV splits a comma-separated string into trimmed, non-empty tokens.

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.

func (*CircuitBreakerError) Error

func (e *CircuitBreakerError) Error() string

Error returns the human-readable message for the authentication error.

func (*CircuitBreakerError) Unwrap

func (e *CircuitBreakerError) Unwrap() error

Unwrap returns the wrapped cause, if any, for use with errors.Is/As.

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

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.

func (*CredentialError) Error

func (e *CredentialError) Error() string

Error returns the human-readable message for the authentication error.

func (*CredentialError) Unwrap

func (e *CredentialError) Unwrap() error

Unwrap returns the wrapped cause, if any, for use with errors.Is/As.

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.

func (*NegotiationError) Error

func (e *NegotiationError) Error() string

Error returns the human-readable message for the authentication error.

func (*NegotiationError) Unwrap

func (e *NegotiationError) Unwrap() error

Unwrap returns the wrapped cause, if any, for use with errors.Is/As.

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.

func (*NoProxyMatcher) Match

func (m *NoProxyMatcher) Match(host string) (matched bool, pattern string)

Match reports whether host (with or without a port) is covered by any bypass pattern. On a match it also returns the original pattern string so callers can include it in debug log messages.

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.

Jump to

Keyboard shortcuts

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