Documentation
¶
Overview ¶
Package httpclient builds a secure-by-default outbound *http.Client for use by any Go component in this repo. It composes connection pooling, forward-proxy support (including mTLS tunneled through a CONNECT proxy), fine-grained TLS control (cipher suites, ECDH/curve preferences including post-quantum hybrid groups), and an optional SSRF dial-time guard, while keeping hostname verification a property that cannot be silently disabled.
Hostname verification ¶
Config never exposes a fixed tls.Config.ServerName: Go's own TLS dialing only fills that field in per-target when it is left empty, and a client built by this package is expected to be reused across many hosts. Setting TLS.InsecureSkipVerify requires also setting TLS.InsecureSkipVerifyAcknowledged — flipping one boolean is not enough to disable verification. A custom TLS.VerifyPeerCertificate or VerifyConnection may only be combined with InsecureSkipVerify == false; New returns an error otherwise, since a custom callback silently becomes the *only* check once Go's own verification is disabled.
SSRF guard composed with a forward proxy ¶
A dial-time IP guard (see the netguard package) can only validate the address this process itself dials. When a forward proxy is configured, that is the proxy's address, never the proxied origin's — the origin hostname is only ever placed in a CONNECT request line, generated after the guarded dial has already completed. Configuring both SSRF.Enabled and a non-"none" Proxy.Mode therefore requires an explicit Proxy.Egress choice; New returns an error if it is left at its zero value (ProxyEgressUnset), so that a caller can never end up believing the origin is SSRF-protected when it silently isn't.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Config ¶
type Config struct {
Pooling PoolingConfig
Timeouts TimeoutsConfig
TLS TLSConfig
Proxy ProxyConfig
SSRF SSRFConfig
}
Config describes how to build an outbound *http.Client. Use DefaultConfig to obtain sane pooling/timeout defaults, then override only the fields a caller needs to change — the TLS, Proxy, and SSRF zero values are all meaningful ("no client cert", "no proxy", "no SSRF guard") rather than placeholders that must be filled in.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns a Config with sane, non-zero pooling and timeout defaults. TLS, Proxy, and SSRF are left at their zero values (no client cert, no proxy, no SSRF guard) for the caller to opt into explicitly.
type Option ¶
type Option func(*buildState)
Option customizes client construction with a hook that cannot be expressed as plain configuration data.
func WithDialContext ¶
WithDialContext overrides the dial function New would otherwise choose (plain or netguard-guarded). Intended for tests; using it bypasses whatever SSRF policy Config.SSRF would otherwise have applied.
func WithRoundTripperWrapper ¶
func WithRoundTripperWrapper(wrap func(http.RoundTripper) http.RoundTripper) Option
WithRoundTripperWrapper wraps the built http.RoundTripper with wrap, closest to Client.Do — e.g. for attaching metrics/tracing instrumentation. Wrappers are applied in the order given.
type PoolingConfig ¶
type PoolingConfig struct {
// MaxIdleConns, MaxIdleConnsPerHost, and MaxConnsPerHost mirror the
// identically-named http.Transport fields.
MaxIdleConns int
MaxIdleConnsPerHost int
MaxConnsPerHost int
// IdleConnTimeout mirrors http.Transport.IdleConnTimeout.
IdleConnTimeout time.Duration
// KeepAlive mirrors net.Dialer.KeepAlive for the underlying TCP dialer.
KeepAlive time.Duration
// DisableKeepAlives mirrors http.Transport.DisableKeepAlives. Reusing
// pooled connections carries no SSRF/DNS-rebinding risk (see the
// netguard package doc), so this defaults to false even when the SSRF
// guard is enabled.
DisableKeepAlives bool
// EnableHTTP2 opts into HTTP/2. It defaults to false: this package
// always sets a custom DialContext and/or TLSClientConfig, which makes
// Go's own Transport conservatively disable HTTP/2 unless explicitly
// re-enabled — and HTTP/2 connection coalescing (RFC 7540 §9.1.1) can
// reuse an established connection for a different, SAN-covered hostname
// without a fresh DialContext validation. Only enable this if that
// tradeoff has been considered for the caller's use case.
EnableHTTP2 bool
}
PoolingConfig controls http.Transport's connection pooling behavior.
type ProxyConfig ¶
type ProxyConfig struct {
// Mode selects how the proxy is determined: "none" (default, no proxy),
// "environment" (use HTTP_PROXY/HTTPS_PROXY/NO_PROXY via Go's own
// http.ProxyFromEnvironment), or "url" (use URL below, with NoProxy as
// an explicit bypass list).
Mode string
// URL is the proxy URL, used when Mode == "url".
URL string
// Username and Password set basic auth credentials for the proxy
// connection (Proxy-Authorization), used when Mode == "url".
Username, Password string
// NoProxy lists hosts to bypass the proxy for, used when Mode == "url".
// Each entry is an exact host, a ".suffix" domain match, or a CIDR.
NoProxy []string
// ProxyTLS configures a SEPARATE, distinct TLS handshake to an
// https:// proxy itself (e.g. the proxy requires its own client
// certificate, different from TLS.ClientCertFile/Key used for the
// origin). Leave nil for the common case where either the proxy is
// plain (http://) or the proxy's own TLS needs no client cert.
ProxyTLS *ProxyTLSConfig
// ConnectHeader, if set, supplies additional headers on the CONNECT
// request to the proxy (e.g. a bearer-token proxy-auth scheme) —
// passed through to http.Transport.GetProxyConnectHeader.
ConnectHeader func(ctx context.Context, proxyURL *url.URL, target string) (http.Header, error)
// Egress must be set explicitly whenever Mode != "none" AND
// SSRF.Enabled — see the package doc for why a dial-time IP guard
// cannot protect the proxied origin.
Egress ProxyEgressPolicy
}
ProxyConfig configures forward-proxy use.
type ProxyEgressPolicy ¶
type ProxyEgressPolicy int
ProxyEgressPolicy states how origin-destination SSRF risk is handled when a forward proxy is also configured.
const ( // ProxyEgressUnset is the zero value. New returns an error if this is // left unset while both a proxy and the SSRF guard are configured — // this policy must always be a deliberate choice, never a default. ProxyEgressUnset ProxyEgressPolicy = iota // ProxyEgressDelegated trusts the proxy's own network egress controls // for the proxied origin. The dial-time guard still validates the // proxy's own resolved address (and CheckRedirect still applies its // scheme/host policy), but the origin itself is not validated by this // library while proxying. ProxyEgressDelegated // ProxyEgressManualCONNECT gives up http.Transport's native proxy // support and uses a hand-rolled http.RoundTripper that resolves and // validates the origin hostname itself, locally, before ever issuing a // CONNECT request. This is defense-in-depth against what this process // itself would resolve — it does not guarantee the proxy resolves or // routes the origin the same way. ProxyEgressManualCONNECT )
type ProxyTLSConfig ¶
type ProxyTLSConfig struct {
// RootCAFile loads a PEM-encoded CA bundle from disk. RootCAs, if set,
// takes precedence. Both empty uses Go's system root pool.
RootCAFile string
RootCAs *x509.CertPool
// ClientCertFile/ClientKeyFile load a PEM client certificate/key pair
// for mTLS to the proxy. GetClientCertificate, if set, is used instead
// and the two file fields are ignored.
ClientCertFile, ClientKeyFile string
GetClientCertificate func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
InsecureSkipVerify bool
InsecureSkipVerifyAcknowledged bool
}
ProxyTLSConfig configures the TLS handshake to the proxy itself, fully decoupled from TLSConfig (which always governs the origin handshake).
type SSRFConfig ¶
type SSRFConfig struct {
// Enabled turns the guard on. When true, Policy must be a non-zero
// netguard.Policy — New returns an error otherwise, rather than
// silently falling back to one preset over another.
Enabled bool
Policy netguard.Policy
// MaxRedirects bounds redirect hops. 0 uses netguard's own default (5);
// a negative value is not distinguished from zero.
MaxRedirects int
}
SSRFConfig configures the dial-time SSRF guard (see the netguard package). It is disabled by default: enabling it is always an explicit, per-caller choice, since two legitimate use cases already in this codebase disagree on which addresses should be reachable.
type TLSConfig ¶
type TLSConfig struct {
// MinVersion and MaxVersion use the tlsconfig.ParseVersion vocabulary
// ("TLS1_0".."TLS1_3"). Both empty uses Go's own defaults.
MinVersion, MaxVersion string
// CipherSuites is a comma-separated list of Go crypto/tls cipher suite
// names. Empty uses Go's own default secure set. Only affects TLS 1.2
// and below.
CipherSuites string
// CurvePreferences is a comma-separated, order-significant list of
// curve/group names, e.g. "X25519MLKEM768,X25519,P-256" to prefer the
// FIPS 203 ML-KEM-768 hybrid group while retaining classical fallbacks
// for a peer that doesn't support it yet. Empty uses Go's own defaults
// (no PQC) — enabling a hybrid group is always an explicit opt-in here,
// never this package's unconditional default.
CurvePreferences string
// RootCAFile loads a PEM-encoded CA bundle from disk. RootCAs, if set,
// takes precedence and is used as-is (e.g. for a caller that already
// manages certificate rotation itself). Both empty uses Go's system
// root pool.
RootCAFile string
RootCAs *x509.CertPool
// ClientCertFile/ClientKeyFile load a PEM client certificate/key pair
// for mTLS to the origin. GetClientCertificate, if set, is used instead
// (e.g. for rotation) and the two file fields are ignored.
ClientCertFile, ClientKeyFile string
GetClientCertificate func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
// InsecureSkipVerify disables certificate chain and hostname
// verification entirely. It is a narrow, explicitly-named, off-by-
// default escape hatch: New returns an error unless
// InsecureSkipVerifyAcknowledged is also true, and it can never be
// combined with VerifyPeerCertificate/VerifyConnection (see below).
InsecureSkipVerify bool
InsecureSkipVerifyAcknowledged bool
// VerifyPeerCertificate and VerifyConnection are run IN ADDITION TO,
// never instead of, Go's own default verification — New rejects either
// one being set alongside InsecureSkipVerify == true, since a custom
// callback would then silently become the only check performed (its
// verifiedChains argument is empty when default verification didn't
// run). Use these only to add an extra check (e.g. certificate
// pinning) on top of a chain Go has already verified.
VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
VerifyConnection func(tls.ConnectionState) error
}
TLSConfig controls the TLS handshake used for the ORIGIN connection — i.e. the server ultimately being talked to, whether reached directly or through a CONNECT-tunneling proxy. See ProxyTLSConfig for the separate, proxy-facing TLS handshake.
type TimeoutsConfig ¶
type TimeoutsConfig struct {
// Overall mirrors http.Client.Timeout — the end-to-end budget for a
// single request including any redirects.
Overall time.Duration
// Dial bounds the TCP connect phase.
Dial time.Duration
// TLSHandshake mirrors http.Transport.TLSHandshakeTimeout.
TLSHandshake time.Duration
// ResponseHeader mirrors http.Transport.ResponseHeaderTimeout.
ResponseHeader time.Duration
// ExpectContinue mirrors http.Transport.ExpectContinueTimeout.
ExpectContinue time.Duration
// MaxResponseBytes bounds how much of a response body the returned
// client will read before erroring, so an oversized or hostile response
// cannot exhaust memory. 0 uses the package default
// (defaultMaxResponseBytes); a negative value disables the bound
// entirely (opt-in, for callers that stream large trusted payloads).
MaxResponseBytes int64
}
TimeoutsConfig bounds every phase of an outbound request. Per go-network-service-hardening.md, DefaultConfig never leaves these at Go's unbounded zero values.