Documentation
¶
Overview ¶
Package statute is a config-as-code reverse proxy framework.
Configurations are written as Go values, validated and resolved at startup, then executed by the runtime. There is no runtime config file, no hot reload, and no module loader — the binary IS the configuration.
See the examples directory for canonical usage.
Index ¶
- Variables
- func AllowIPs(cidrs ...string) *allowIPsMW
- func BasicAuth(realm string, users map[string]string) *basicAuthMW
- func BodyLimit(size string) *bodyLimitMW
- func CORS() *corsMW
- func Cache(ttl string) *cacheMW
- func Compress(algos ...CompressAlgo) *compressMW
- func DenyIPs(cidrs ...string) *denyIPsMW
- func ETag() *etagMW
- func Export(cfg Config, w io.Writer) error
- func GraphDOT(cfg Config, w io.Writer) error
- func JSONLog(dest LogWriter) *jsonLog
- func Main(cfg Config)
- func OTLP(endpoint string) *otlpTracing
- func RateLimit(rate string) *rateLimitMW
- func RequestID() *requestIDMW
- func Resolve(cfg Config) (*resolved.Config, error)
- func Retry(max int, opts ...RetryOption) *retryMW
- func Run(cfg Config)
- func SecurityHeaders() *securityHeadersMW
- func Timeout(dur string) *timeoutMW
- type AccessLog
- type AutoTLSConfig
- type Backend
- type CompressAlgo
- type Config
- type Defaults
- type Finding
- type HealthCheck
- type Listener
- type ListenerOption
- type Listeners
- type LogWriter
- type Metrics
- type Middleware
- type Observability
- type Pool
- type RateLimitKey
- type RetryOption
- type Route
- type Routes
- type Severity
- type Shutdown
- type StaticTLSConfig
- type Strategy
- type Tracing
- type Transport
- type Upstreams
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var Stderr = LogWriter{/* contains filtered or unexported fields */}
Stderr writes logs to process stderr.
var Stdout = LogWriter{/* contains filtered or unexported fields */}
Stdout writes logs to process stdout.
Functions ¶
func AllowIPs ¶
func AllowIPs(cidrs ...string) *allowIPsMW
AllowIPs returns a middleware that admits only requests whose client IP falls within at least one of the configured CIDR ranges. Other requests are answered with 403 Forbidden.
CIDRs are parsed at resolve time via net/netip; both IPv4 and IPv6 are supported. Examples: "10.0.0.0/8", "2001:db8::/32", "203.0.113.5/32".
The client IP comes from clientIP(), which respects BehindCloudflare() (CF-Connecting-IP) when configured on the listener.
Example ¶
ExampleAllowIPs shows the IP allow-list middleware. CIDRs are parsed once at startup; the runtime match is O(prefixes).
_ = statute.AllowIPs("10.0.0.0/8", "192.168.0.0/16")
func BasicAuth ¶
BasicAuth returns an HTTP Basic Auth middleware. The users map keys are usernames; the values must be bcrypt hashes (the kind produced by `bcrypt.GenerateFromPassword`, prefixed with $2a$ / $2b$ / $2y$). At resolve time, every value is validated to be a recognisable bcrypt hash; non-bcrypt values are rejected loudly.
Realm is the value sent in the WWW-Authenticate header on unauthorized responses; browsers display it in the password prompt.
Important: bcrypt is intentionally slow. Each unauthorized request costs one bcrypt verification (~80ms at cost 10). For unauthenticated requests the cost is per attempt. Combine with RateLimit on the same route so a brute-force attacker cannot trivially DoS the proxy by repeatedly sending invalid credentials.
Note: BasicAuth over plain HTTP transmits credentials in clear-text base64. The lint check AUTH001 flags this configuration.
Example ¶
ExampleBasicAuth shows BasicAuth with bcrypt password hashes. Generate hashes with bcrypt.GenerateFromPassword (cost >= 10).
users := map[string]string{
"alice": "$2a$10$HwrzUQtDrRX0/09su3BahezCIqD.f4HjCkYD5b9w8gl4eUkPJzCyu", // password: "hunter2"
}
_ = statute.BasicAuth("Admin", users)
func BodyLimit ¶
func BodyLimit(size string) *bodyLimitMW
BodyLimit returns a middleware that caps the request body at the given size. Sizes are strings like "1MB", "512KiB", or "10485760". Requests with a body larger than the limit receive a 413 Request Entity Too Large response; the upstream handler never sees them.
The cap is enforced via http.MaxBytesReader on r.Body; calls to Read past the limit return an error, which the wrapped handler is expected to surface as 413. For upstream proxies the reverse-proxy transport handles this transparently.
func CORS ¶
func CORS() *corsMW
CORS returns a Cross-Origin Resource Sharing middleware. Pass at least Origins(...) to enable; without explicit origins the middleware does nothing.
Preflight (OPTIONS + Access-Control-Request-Method) is handled directly and responds 204 with the configured headers. Non-preflight requests are passed through after Access-Control-Allow-* headers are set; the upstream handler still sees the request.
A wildcard origin ("*") combined with Credentials() is rejected at resolve time — the CORS spec forbids credentialed wildcards.
Example ¶
ExampleCORS shows a credentialed CORS policy bound to a specific origin.
_ = statute.CORS().
Origins("https://app.example.com").
Methods("GET", "POST", "PUT", "DELETE").
Headers("Authorization", "Content-Type").
Credentials().
MaxAge("1h")
func Cache ¶
func Cache(ttl string) *cacheMW
Cache returns a response-cache middleware with the given TTL.
func Compress ¶
func Compress(algos ...CompressAlgo) *compressMW
Compress returns a response-compression middleware that negotiates one of the listed algorithms based on the request's Accept-Encoding header.
func DenyIPs ¶
func DenyIPs(cidrs ...string) *denyIPsMW
DenyIPs returns a middleware that rejects requests whose client IP falls within at least one of the configured CIDR ranges. Other requests pass through.
DenyIPs is checked independently of AllowIPs; if both are configured on a route, the order they were added to With(...) determines precedence.
func ETag ¶
func ETag() *etagMW
ETag returns a middleware that adds ETag headers to static file responses and serves 304 Not Modified for matching If-None-Match requests.
func Export ¶
Export validates and resolves the surface configuration, then writes the canonical resolved schema as JSON. Useful for diffing deployments and snapshotting in CI without starting a server.
func GraphDOT ¶
GraphDOT writes a Graphviz DOT representation of the resolved config to w. Render with `dot -Tsvg < input.dot > topology.svg` (or any DOT-capable renderer).
The graph has four kinds of nodes:
- Listeners (Mrecord, blue) — one per HTTP/HTTPS listener.
- Routes (rectangle, light yellow) — one per declared route.
- Upstream pools (ellipse, green) — one per named pool.
- Backends (circle, gray) — one per backend in each pool, dashed if Backup.
Edges:
- Listener → Listener for redirect-to-https arrows.
- Listener → Route for the matching relation (every content listener reaches every route; the host filter is on the route node).
- Route → Pool for ProxyTo.
- Pool → Backend for membership; weighted edges show Weight.
The output is intentionally minimal — no fancy layout, no colour palette. Pipe it through dot with your preferred styling.
func JSONLog ¶
func JSONLog(dest LogWriter) *jsonLog
JSONLog returns a structured (JSON) access log writing to the given destination. By default every request is logged; use Sample to record only a fraction of requests at high traffic volumes.
func Main ¶
func Main(cfg Config)
Main is a thin CLI wrapper around Run, Export, Lint, and Graph. It parses the standard process arguments and dispatches:
-export Write the resolved configuration as JSON to stdout and exit.
-validate Validate the configuration and exit. Prints "ok" on success.
-graph Write the resolved topology as Graphviz DOT to stdout and exit.
-lint Audit the resolved configuration against the production-readiness
rule set; exit non-zero if any error-severity finding fires.
(no flag) Equivalent to Run.
The four operation flags are mutually exclusive. Programs that want a clean entry point without flag handling can call Run directly.
func OTLP ¶
func OTLP(endpoint string) *otlpTracing
OTLP configures distributed tracing via OTLP/gRPC to the given collector endpoint (for example "otel-collector:4317"). Spans are produced for every incoming request with HTTP semantic conventions, and W3C trace context is propagated to upstream backends.
func RateLimit ¶
func RateLimit(rate string) *rateLimitMW
RateLimit returns a rate-limit middleware. The rate string is of the form "N/unit" where unit is one of s, min, h. For example "100/min".
Example ¶
ExampleRateLimit shows the rate limiter keyed on client IP.
_ = statute.RateLimit("100/s").Per(statute.ClientIP)
func RequestID ¶
func RequestID() *requestIDMW
RequestID returns a middleware that ensures every request carries a stable identifier — useful for tracing through logs and propagating to upstream backends. The default response header is defaultRequestIDHeader; override with Header.
If an inbound header (configured via From) is present, its value is used verbatim. Otherwise a new ID is generated from 16 bytes of crypto/rand, hex-encoded.
When the access log is configured, the request ID surfaces as a "request_id" field on log lines.
func Resolve ¶
Resolve validates the surface configuration, fills defaults, and produces the canonical resolved schema. Resolve is pure: it does not touch the network, the filesystem, or process state.
func Retry ¶
func Retry(max int, opts ...RetryOption) *retryMW
Retry returns a retry middleware with the given maximum attempts and options.
func Run ¶
func Run(cfg Config)
Run validates, resolves, and runs the configuration. It blocks until the process receives SIGINT or SIGTERM, then performs a graceful shutdown.
Any validation or startup error is fatal: Run logs and exits non-zero.
func SecurityHeaders ¶
func SecurityHeaders() *securityHeadersMW
SecurityHeaders returns a middleware that emits common HTTP security response headers. Defaults are conservative for a public-facing edge proxy:
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- Referrer-Policy: strict-origin-when-cross-origin
HSTS, CSP, and Permissions-Policy are strictly opt-in via the builder methods. Override or disable an individual header by passing "" to its setter (or by not calling the setter, for HSTS/CSP/Permissions-Policy).
The CSP string is passed through verbatim — no parsing, no validation. The framework cannot know what your application needs.
Example ¶
ExampleSecurityHeaders shows the recommended baseline for a public-facing origin: HSTS, CSP, and the default conservative headers.
_ = statute.SecurityHeaders().
HSTS("365d").
CSP("default-src 'self'; img-src 'self' data:")
Types ¶
type AccessLog ¶
type AccessLog interface {
// contains filtered or unexported methods
}
AccessLog is a marker for an access log destination.
type AutoTLSConfig ¶
type AutoTLSConfig struct {
Domains []string
// contains filtered or unexported fields
}
AutoTLSConfig declares ACME-managed TLS material.
func AutoTLS ¶
func AutoTLS(domains ...string) *AutoTLSConfig
AutoTLS configures ACME auto-provisioning for the given domains.
func (*AutoTLSConfig) CloudflareDNS01 ¶
func (a *AutoTLSConfig) CloudflareDNS01(apiToken string) *AutoTLSConfig
CloudflareDNS01 switches the ACME challenge from HTTP-01 to DNS-01 using Cloudflare's DNS API. Required for wildcard certificates and useful when :80 is not reachable from the public internet (private networks, Cloudflare-only origins, etc.).
The token must be a Cloudflare API Token (not the legacy Global API Key) with the Zone.DNS:Edit permission for the zone(s) covering the listener's domains. Generate one at https://dash.cloudflare.com/profile/api-tokens.
The zone is auto-discovered from each domain by walking the DNS labels against the account's zone list. Use Zone() to pin a specific zone ID and skip discovery.
Returns the parent AutoTLSConfig so the call chain remains a single ListenerOption value usable as an argument to HTTPS.
func (*AutoTLSConfig) Email ¶
func (a *AutoTLSConfig) Email(email string) *AutoTLSConfig
Email sets the contact email registered with the ACME directory.
func (*AutoTLSConfig) Storage ¶
func (a *AutoTLSConfig) Storage(path string) *AutoTLSConfig
Storage sets the on-disk path where issued certificates and ACME state are persisted. Required for production use.
func (*AutoTLSConfig) Zone ¶
func (a *AutoTLSConfig) Zone(id string) *AutoTLSConfig
Zone pins the Cloudflare zone ID for DNS-01 challenges. Must be called after CloudflareDNS01. When unset the zone is discovered by querying Cloudflare for the zone whose name is a suffix of each domain.
type Backend ¶
type Backend struct {
// Address is the host:port of the backend.
Address string
// Weight is the relative weight for weighted strategies. Defaults to 1.
Weight int
// Backup is true for failover-only backends; they receive traffic only
// when all primary backends are unhealthy.
Backup bool
}
Backend is a single upstream target.
type CompressAlgo ¶
type CompressAlgo int
CompressAlgo identifies a content-encoding algorithm.
const ( // Gzip compression. Gzip CompressAlgo = iota // Brotli compression. Brotli )
func (CompressAlgo) String ¶
func (a CompressAlgo) String() string
String returns the canonical name of the algorithm.
type Config ¶
type Config struct {
Listeners Listeners
Upstreams Upstreams
Routes Routes
Defaults Defaults
Observability Observability
Shutdown Shutdown
}
Config is the top-level surface configuration.
type Defaults ¶
type Defaults struct {
// ReadHeaderTimeout caps how long a client may take to send the request
// headers. The Go standard library has no default; setting this is the
// primary mitigation for Slowloris denial-of-service.
ReadHeaderTimeout string
// ReadTimeout caps the entire request read, including body. Use with care
// for streaming and long-poll endpoints.
ReadTimeout string
// WriteTimeout caps how long the server may take to write the response.
WriteTimeout string
// IdleTimeout caps how long an idle keep-alive connection may sit between
// requests before being closed.
IdleTimeout string
// MaxHeaderBytes caps the size of the request header block. Defaults to
// the Go standard library default of 1MB when unset.
MaxHeaderBytes int
}
Defaults sets the conservative production baseline for all listeners. Routes may override individual values via middleware.
type Finding ¶
type Finding struct {
// Severity is "warning" or "error".
Severity Severity
// Code is the stable rule identifier (e.g. "RHT001"). Use this in
// suppress directives once those exist.
Code string
// Message is a one-line human-readable description.
Message string
// Path is a config-pointer-style string identifying the offending
// element (e.g. `listeners[0]`, `upstreams["api"]`).
Path string
}
Finding describes a single rule hit during a Lint pass.
func Lint ¶
Lint validates the surface config (via Resolve) and then runs the production-readiness rule set against the resolved schema. Returns the findings in declaration order; structural validation errors come back as the error return.
The rule set is intentionally small and stable for v0.2.0. Additional rules will be added in later releases.
type HealthCheck ¶
type HealthCheck struct {
Path string // HTTP path to probe; empty disables active health checks
Interval string // how often to probe; e.g. "10s"
Timeout string // probe timeout; e.g. "2s"
Healthy int // consecutive successes to mark healthy; defaults to 2
Unhealthy int // consecutive failures to mark unhealthy; defaults to 3
}
HealthCheck configures active health checks against backends.
type Listener ¶
type Listener struct {
// contains filtered or unexported fields
}
Listener is a surface listener declaration. Construct via HTTP or HTTPS.
func HTTP ¶
HTTP starts an HTTP/1.1 listener declaration on the given address.
Example ¶
ExampleHTTP shows the minimal config: one HTTP listener proxying to an upstream pool. Compile and run as a standalone binary.
statute.Main(statute.Config{
Listeners: statute.Listeners{
statute.HTTP(":8080"),
},
Upstreams: statute.Upstreams{
"api": statute.Pool{
Backends: []statute.Backend{{Address: "127.0.0.1:9001"}},
},
},
Routes: statute.Routes{
statute.Match("/*").ProxyTo("api"),
},
})
func HTTPS ¶
func HTTPS(addr string, opts ...ListenerOption) *Listener
HTTPS starts an HTTPS listener declaration on the given address. Options configure TLS material, HTTP/2, and HTTP/3.
Example (AutoTLS) ¶
ExampleHTTPS_autoTLS shows AutoTLS with Let's Encrypt HTTP-01. The :80 listener serves the ACME challenge automatically because AutoTLS is configured elsewhere in the config; in production it should also redirect non-challenge traffic to HTTPS.
statute.Main(statute.Config{
Listeners: statute.Listeners{
statute.HTTP(":80").RedirectTo("https"),
statute.HTTPS(":443",
statute.AutoTLS("example.com").
Email("ops@example.com").
Storage("/var/lib/statute/certs"),
statute.HTTP2(),
),
},
Upstreams: statute.Upstreams{
"api": statute.Pool{
Backends: []statute.Backend{{Address: "10.0.0.1:8080"}},
},
},
Routes: statute.Routes{
statute.Match("/*").ProxyTo("api"),
},
Defaults: statute.Defaults{ReadHeaderTimeout: "5s"},
})
func (*Listener) RedirectTo ¶
RedirectTo turns this listener into a permanent redirect to the named scheme. The listener will not serve content beyond the redirect.
type ListenerOption ¶
type ListenerOption interface {
// contains filtered or unexported methods
}
ListenerOption configures an HTTPS listener.
func BehindCloudflare ¶
func BehindCloudflare() ListenerOption
BehindCloudflare marks the listener as sitting behind a Cloudflare proxy. This affects two things:
First, when AutoTLS is configured on the listener, the TLS-ALPN-01 challenge is suppressed (the "acme-tls/1" entry is dropped from ALPN). Cloudflare terminates TLS at its edge and does not forward custom ALPN protocols, so TLS-ALPN-01 cannot succeed. Provisioning falls back to HTTP-01, which is served by the redirect listener on :80 — Cloudflare proxies that path transparently provided "Always Use HTTPS" is disabled for /.well-known/acme-challenge/*.
Second, the request handling path trusts the CF-Connecting-IP and True-Client-IP headers as the originating client address. Other proxy headers (X-Forwarded-For) remain available but Cloudflare's are preferred because they are populated by the proxy and not user-controllable.
func HTTP2 ¶
func HTTP2() ListenerOption
HTTP2 enables HTTP/2 on the listener. Required for h2 ALPN negotiation.
func HTTP3 ¶
func HTTP3(addr string) ListenerOption
HTTP3 enables HTTP/3 (QUIC) on the listener at the given UDP address. The addr should typically match the HTTPS port suffixed with /udp, for example ":443/udp".
type LogWriter ¶
type LogWriter struct {
// contains filtered or unexported fields
}
LogWriter identifies a destination for structured logs.
type Metrics ¶
type Metrics interface {
// contains filtered or unexported methods
}
Metrics is a marker for a metrics endpoint configuration.
func Prometheus ¶
Prometheus exposes process and request metrics on the given address and path, formatted in the Prometheus exposition format.
type Middleware ¶
type Middleware interface {
// contains filtered or unexported methods
}
Middleware is a marker interface for surface middleware values. Concrete middleware constructors return values that satisfy this interface.
type Observability ¶
Observability bundles the logging, metrics, and tracing configuration.
type Pool ¶
type Pool struct {
Backends []Backend
Strategy Strategy
HealthCheck HealthCheck
Transport Transport
}
Pool is the surface upstream pool definition.
type RateLimitKey ¶
type RateLimitKey int
RateLimitKey selects what attribute of the request a rate limit is keyed on.
const ( // ClientIP keys the rate limiter on the client's IP address. ClientIP RateLimitKey = iota // HostHeader keys the rate limiter on the Host header. HostHeader )
func (RateLimitKey) String ¶
func (k RateLimitKey) String() string
String returns the canonical name of the key.
type RetryOption ¶
type RetryOption interface {
// contains filtered or unexported methods
}
RetryOption configures the Retry middleware.
func OnStatus ¶
func OnStatus(codes ...int) RetryOption
OnStatus retries when the upstream returns any of the given status codes.
type Route ¶
type Route struct {
// contains filtered or unexported fields
}
Route is a surface route declaration. Construct via Match.
func Match ¶
Match begins a route declaration matching the given path pattern. Patterns support a trailing /* wildcard.
Example (ProxyTo) ¶
ExampleMatch_proxyTo shows a host-scoped proxy route.
_ = statute.Routes{
statute.Match("/api/v1/*").Host("api.example.com").ProxyTo("api").
With(
statute.Timeout("30s"),
statute.RateLimit("100/min").Per(statute.ClientIP),
),
}
func (*Route) With ¶
func (r *Route) With(mws ...Middleware) *Route
With attaches middleware to the route. Middleware runs in declaration order before the upstream proxy or static file handler.
type Routes ¶
type Routes []*Route
Routes is the list of route declarations, matched in declaration order.
type Shutdown ¶
type Shutdown struct {
// GracePeriod is the maximum time the server will wait for in-flight
// requests to finish before forcibly closing connections. e.g. "30s".
GracePeriod string
// DrainListeners closes listeners (stops accepting new connections)
// before waiting for in-flight requests. Recommended for production.
DrainListeners bool
}
Shutdown configures graceful shutdown behaviour.
type StaticTLSConfig ¶
StaticTLSConfig declares pre-provisioned TLS material.
func StaticTLS ¶
func StaticTLS(certFile, keyFile string) *StaticTLSConfig
StaticTLS configures TLS using a static certificate and key on disk.
type Strategy ¶
type Strategy int
Strategy selects how a request is routed across the backends in a pool.
const ( // RoundRobin distributes requests evenly across backends in declaration order. RoundRobin Strategy = iota // LeastConnections sends each request to the backend with the fewest in-flight requests. LeastConnections // IPHash routes requests from the same client IP to the same backend (consistent hash). IPHash // Weighted distributes requests proportionally to each backend's Weight. Weighted )
type Tracing ¶
type Tracing interface {
// contains filtered or unexported methods
}
Tracing is a marker for a distributed-tracing exporter configuration.
Source Files
¶
- accesslog.go
- autotls.go
- basicauth.go
- bodylimit.go
- cache.go
- cloudflare.go
- cloudflare_api.go
- compress.go
- cors.go
- defaults.go
- dns01.go
- etag.go
- export.go
- graph.go
- healthcheck.go
- http3.go
- internal_constants.go
- iplist.go
- lint.go
- listeners.go
- metrics.go
- middleware.go
- observability.go
- otel.go
- pprof.go
- ratelimit.go
- requestid.go
- resolve.go
- responsebuffer.go
- retry.go
- routes.go
- security_headers.go
- server.go
- shutdown.go
- statute.go
- strategies.go
- upstreams.go
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
basic
command
|
|
|
cloudflare
command
Example: a statute deployment fronted by Cloudflare with origin AutoTLS.
|
Example: a statute deployment fronted by Cloudflare with origin AutoTLS. |
|
cloudflare-wildcard
command
Example: wildcard certificate via Cloudflare DNS-01.
|
Example: wildcard certificate via Cloudflare DNS-01. |
|
dev
command
dev is a runnable example demonstrating round-robin load balancing across three echo backends with active health checks.
|
dev is a runnable example demonstrating round-robin load balancing across three echo backends with active health checks. |
|
http-only
command
|
|
|
Package resolved is the canonical, fully-validated schema that the statute runtime operates on.
|
Package resolved is the canonical, fully-validated schema that the statute runtime operates on. |