httpclient

package
v0.0.0-...-ba2e97a Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

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

func New

func New(cfg Config, opts ...Option) (*http.Client, error)

New builds an *http.Client from cfg. It validates cfg up front and fails closed on any ambiguous or unsafe combination (see the package doc) rather than silently choosing a default stance.

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

func WithDialContext(dial func(ctx context.Context, network, addr string) (net.Conn, error)) Option

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.

Jump to

Keyboard shortcuts

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