trafficpolicy

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package trafficpolicy holds the per-domain traffic control model (headers, path forwarders, geolocation, caching, redirects): the wire types the API, CLI and dashboard share, their validation, and a human readable preview. It has no dependencies outside the standard library so the ingress, store and API layers can all import it.

Index

Constants

View Source
const (
	PresetWWWToApex = "www-to-apex"
	PresetApexToWWW = "apex-to-www"
	PresetServeBoth = "both"
)

Canonical host presets.

View Source
const (
	ActionApp      = "app"
	ActionURL      = "url"
	ActionRedirect = "redirect"
)

Forwarder actions.

View Source
const (
	HostPreserve = "preserve"
	HostUpstream = "upstream"
	HostCustom   = "custom"
)

Host header modes for a forwarder.

View Source
const (
	GeoAllow = "allow"
	GeoDeny  = "deny"

	GeoActionBlock     = "block"
	GeoActionRedirect  = "redirect"
	GeoActionErrorPage = "error_page"

	GeoUnknownAllow = "allow"
	GeoUnknownBlock = "block"
)

Geo modes, actions and unknown-country handling.

View Source
const (
	SideRequest  = "request"
	SideResponse = "response"

	OpSet    = "set"
	OpAdd    = "add"
	OpRemove = "remove"
)

Header rule sides and operations.

View Source
const (
	EnvMaxHeaderRules     = "APP_DOMAIN_POLICY_MAX_HEADER_RULES"
	EnvMaxForwarders      = "APP_DOMAIN_POLICY_MAX_FORWARDERS"
	EnvMaxCacheRules      = "APP_DOMAIN_POLICY_MAX_CACHE_RULES"
	EnvMaxRegexLen        = "APP_DOMAIN_POLICY_MAX_REGEX_LEN"
	EnvMaxHeaderValueLen  = "APP_DOMAIN_POLICY_MAX_HEADER_VALUE_LEN"
	EnvMaxExemptEntries   = "APP_DOMAIN_POLICY_MAX_GEO_EXEMPT"
	EnvForwardTimeout     = "APP_FORWARDER_TIMEOUT"
	EnvForwardMaxTimeout  = "APP_FORWARDER_MAX_TIMEOUT"
	EnvForwardDialTimeout = "APP_FORWARDER_DIAL_TIMEOUT"
	EnvCacheMaxObject     = "APP_INGRESS_CACHE_MAX_OBJECT_BYTES"
	EnvCacheMaxTTL        = "APP_INGRESS_CACHE_MAX_TTL"
	EnvMaxErrorBody       = "APP_DOMAIN_POLICY_MAX_BODY_BYTES"
)

Environment variables that tune the caps below. Every one is optional.

View Source
const (
	MatchPrefix = "prefix"
	MatchExact  = "exact"
	MatchRegex  = "regex"
)

Path match kinds.

View Source
const (
	EnvForwardAllowPrivate  = "APP_FORWARDER_ALLOW_PRIVATE_NETWORKS"
	EnvForwardAllowLoopback = "APP_FORWARDER_ALLOW_LOOPBACK"
)

Environment switches that let forwarders reach non-public addresses.

View Source
const (
	KindHeaders    = "headers"
	KindForwarders = "forwarders"
	KindGeo        = "geo"
	KindCache      = "cache"
	KindRedirects  = "redirects"
)

Policy kinds, one stored row each per domain.

View Source
const (
	SlashOff    = "off"
	SlashAdd    = "add"
	SlashRemove = "remove"
)

Trailing slash normalisation modes.

View Source
const DefaultHSTSMaxAge = 31536000

DefaultHSTSMaxAge is one year, the preload list minimum.

View Source
const MaxHops = 8

MaxHops bounds a simulated chain; longer chains are reported as loops.

Variables

View Source
var AliasStatuses = map[int]bool{301: true, 302: true, 307: true, 308: true}

AliasStatuses are the redirect codes an alias or canonical redirect may use.

View Source
var ErrBlockedTarget = errors.New("trafficpolicy: forward target address is not allowed")

ErrBlockedTarget is returned when a forwarder URL resolves to an address the egress guard refuses.

Kinds lists every policy kind in evaluation order.

Functions

func CanonicalPlan

func CanonicalPlan(domain, preset string) (from, to string, err error)

CanonicalPlan returns the redirect a preset installs: requests to from go to to. Serve both returns two empty strings.

func CheckRedirectLoop

func CheckRedirectLoop(rows map[string]HostRedirect, from, toHost string) error

CheckRedirectLoop reports whether adding from -> toHost to rows would make a chain that returns to from, or exceeds MaxHops.

func Counterpart

func Counterpart(domain string) (string, error)

Counterpart returns the www or apex twin of domain.

func ForwardedPath

func ForwardedPath(f Forwarder, path string) string

ForwardedPath is the path the upstream sees after strip and rewrite.

func IsCountryCode

func IsCountryCode(c string) bool

IsCountryCode reports whether c is an assigned ISO 3166-1 alpha-2 code.

func IsKind

func IsKind(k string) bool

IsKind reports whether k names a policy kind.

func IsToken

func IsToken(s string) bool

IsToken reports whether s is an RFC 9110 token, the grammar for a header name.

func NeverGeoBlocked

func NeverGeoBlocked(a netip.Addr) bool

NeverGeoBlocked reports addresses geo rules never apply to: private, loopback, link-local and unspecified addresses have no country.

func ParseIPOrPrefix

func ParseIPOrPrefix(s string) (netip.Prefix, error)

ParseIPOrPrefix accepts "203.0.113.7" or "203.0.113.0/24".

func PrefixMatches

func PrefixMatches(prefix, path string) bool

PrefixMatches is segment aware: /api matches /api and /api/x, not /apix.

func RedirectTarget

func RedirectTarget(target string, req *url.URL) string

RedirectTarget mirrors the ingress: a bare origin target keeps the request's path and query, a target with its own path is used as is.

func SafeHeaderValue

func SafeHeaderValue(v string) bool

SafeHeaderValue reports whether v can be written into a header without splitting it: no CR, LF or NUL, and no other control byte except tab.

func TargetHost

func TargetHost(target string) string

TargetHost returns the lower case host of a redirect target URL.

func ValidOrigin

func ValidOrigin(o string) bool

ValidOrigin reports whether o is a bare http(s) origin.

Types

type CORSPreset

type CORSPreset struct {
	Origins       []string `json:"origins"`
	Methods       []string `json:"methods,omitempty"`
	Headers       []string `json:"headers,omitempty"`
	ExposeHeaders []string `json:"expose_headers,omitempty"`
	Credentials   bool     `json:"credentials,omitempty"`
	MaxAgeSeconds int      `json:"max_age_seconds,omitempty"`
	// Preflight answers OPTIONS preflight requests at the ingress instead of
	// passing them to the app.
	Preflight bool `json:"preflight"`
}

CORSPreset answers cross origin requests from Origins at the ingress.

type Cache

type Cache struct {
	Enabled        bool        `json:"enabled"`
	Rules          []CacheRule `json:"rules"`
	MaxObjectBytes int64       `json:"max_object_bytes,omitempty"`
}

Cache is a domain's cache policy.

func (*Cache) Validate

func (c *Cache) Validate(l Limits) error

Validate checks every rule against the instance caps.

type CacheRule

type CacheRule struct {
	Match Match `json:"match"`
	// TTLSeconds applies when the app sends no max-age or s-maxage, or
	// always when OverrideUpstream is set.
	TTLSeconds       int  `json:"ttl_seconds"`
	OverrideUpstream bool `json:"override_upstream,omitempty"`
	// StaleWhileRevalidateSeconds serves an expired entry while one
	// background request refreshes it.
	StaleWhileRevalidateSeconds int `json:"stale_while_revalidate_seconds,omitempty"`
	// StatusCodes cached; empty means 200 only.
	StatusCodes []int `json:"status_codes,omitempty"`
	// Vary lists request headers that split the cache key.
	Vary []string `json:"vary,omitempty"`
	// CacheWithCookies keeps caching when the request carries a Cookie.
	// Off by default: a cookie usually means a personalised page.
	CacheWithCookies bool `json:"cache_with_cookies,omitempty"`
}

CacheRule selects responses to keep in the ingress cache.

func (CacheRule) EffectiveStatusCodes

func (r CacheRule) EffectiveStatusCodes() []int

EffectiveStatusCodes returns the statuses the rule stores.

type Context

type Context struct {
	// Domain is the host the policy belongs to, lower case.
	Domain string
	// TLSReal is true when browsers see a publicly trusted certificate for
	// Domain (ACME, an uploaded certificate, or TLS terminated upstream).
	TLSReal bool
	// AppExists reports whether a forwarder's target app exists. Nil skips
	// the check.
	AppExists func(name string) bool
}

Context is what validation needs to know about the domain and instance.

type EgressGuard

type EgressGuard struct {
	AllowPrivate  bool
	AllowLoopback bool
}

EgressGuard decides which addresses an external forwarder may reach. Metadata and link-local ranges are refused even when private is allowed, mirroring platformimport.NetworkPolicy.

func EgressGuardFromEnv

func EgressGuardFromEnv(getenv func(string) string) EgressGuard

EgressGuardFromEnv reads the two switches above.

func (EgressGuard) Check

func (g EgressGuard) Check(addr netip.Addr) error

Check reports whether addr may be dialed.

func (EgressGuard) CheckTarget

func (g EgressGuard) CheckTarget(ctx context.Context, r Resolver, rawURL string) (ResolvedTarget, error)

CheckTarget resolves rawURL and checks every address it resolves to, so a name with one public and one private record is refused as a whole.

type FieldError

type FieldError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

FieldError is one validation failure, addressed by a dotted field path (for example "rules[2].name") so the dashboard can show it inline.

type Forwarder

type Forwarder struct {
	Name           string `json:"name,omitempty"`
	Match          Match  `json:"match"`
	Action         string `json:"action"`
	App            string `json:"app,omitempty"`
	URL            string `json:"url,omitempty"`
	RedirectStatus int    `json:"redirect_status,omitempty"`
	// StripPrefix removes Match.Path before forwarding (prefix rules only).
	StripPrefix bool `json:"strip_prefix,omitempty"`
	// RewritePrefix is prepended to the (possibly stripped) path.
	RewritePrefix string `json:"rewrite_prefix,omitempty"`
	Host          string `json:"host,omitempty"`
	HostValue     string `json:"host_value,omitempty"`
	// WebSocket nil or true passes Upgrade requests through.
	WebSocket      *bool `json:"websocket,omitempty"`
	TimeoutSeconds int   `json:"timeout_seconds,omitempty"`
}

Forwarder is one ordered path rule. The first matching rule wins; a request no rule matches goes to the domain's own app.

func (Forwarder) HostMode

func (f Forwarder) HostMode() string

HostMode returns the effective Host header mode: external URLs default to the upstream's own host, apps keep the visitor's.

func (Forwarder) WebSocketEnabled

func (f Forwarder) WebSocketEnabled() bool

WebSocketEnabled reports whether Upgrade requests pass through.

type Forwarders

type Forwarders struct {
	Rules []Forwarder `json:"rules"`
}

Forwarders is a domain's ordered path rules.

func (*Forwarders) Validate

func (fw *Forwarders) Validate(l Limits, c Context) error

Validate checks every rule. External URL targets get a structural check here; the address check (SSRF guard) needs DNS and runs in CheckTarget.

type Geo

type Geo struct {
	Mode        string   `json:"mode"`
	Countries   []string `json:"countries"`
	Action      string   `json:"action"`
	RedirectURL string   `json:"redirect_url,omitempty"`
	StatusCode  int      `json:"status_code,omitempty"`
	Body        string   `json:"body,omitempty"`
	// Exempt addresses and ranges are never blocked by country.
	Exempt []string `json:"exempt,omitempty"`
	// Unknown decides requests whose country cannot be determined.
	Unknown string `json:"unknown,omitempty"`
}

Geo is a domain's country access rule.

func (Geo) BlockStatus

func (g Geo) BlockStatus() int

BlockStatus returns the status code for the block and error page actions.

func (Geo) Decide

func (g Geo) Decide(client netip.Addr, country string, exempt []netip.Prefix) GeoDecision

Decide evaluates g for a client address and its resolved country ("" when unknown). exempt is g.Exempt parsed once by the caller.

func (Geo) UnknownMode

func (g Geo) UnknownMode() string

UnknownMode returns the effective unknown-country handling (allow by default).

func (*Geo) Validate

func (g *Geo) Validate(l Limits) error

Validate checks the rule.

type GeoDecision

type GeoDecision struct {
	Blocked bool
	Reason  string
}

GeoDecision is the outcome of evaluating a Geo rule for one request.

type HeaderRule

type HeaderRule struct {
	Side  string `json:"side"`
	Op    string `json:"op"`
	Name  string `json:"name"`
	Value string `json:"value,omitempty"`
}

HeaderRule is one ordered header change. Response rules are applied after the app answered, so they win over the app's own value.

type Headers

type Headers struct {
	Rules    []HeaderRule    `json:"rules"`
	Security *SecurityPreset `json:"security,omitempty"`
	CORS     *CORSPreset     `json:"cors,omitempty"`
	// HideServer removes Server and X-Powered-By from every response.
	HideServer bool `json:"hide_server,omitempty"`
	// ForwardedPrefix sets X-Forwarded-Prefix on requests, the only
	// X-Forwarded-* header an operator may set.
	ForwardedPrefix string `json:"forwarded_prefix,omitempty"`
}

Headers is a domain's header policy.

func (*Headers) Validate

func (h *Headers) Validate(l Limits, c Context) error

Validate checks every rule and preset.

type Hop

type Hop struct {
	URL      string `json:"url"`
	Status   int    `json:"status"`
	Location string `json:"location,omitempty"`
	Note     string `json:"note"`
}

Hop is one response in a redirect chain.

func SimulateHops

func SimulateHops(env HopEnv, rawURL string) ([]Hop, error)

SimulateHops follows rawURL through the ingress's redirect behavior.

type HopEnv

type HopEnv struct {
	// Hosts routed by this instance, lower case.
	Hosts map[string]bool
	// Redirects maps a host to its normalisation settings.
	Redirects map[string]Redirects
	// HostRedirects maps a host to its whole-host redirect.
	HostRedirects map[string]HostRedirect
	// Maintenance hosts answer 503 before any redirect.
	Maintenance map[string]bool
	// AutoHTTPS is true when the ingress itself redirects every http
	// request to https (the HTTP listener is bound and TLS ends here).
	AutoHTTPS bool
	// Terminated is true when a proxy in front terminates TLS; force HTTPS
	// then trusts its X-Forwarded-Proto.
	Terminated bool
	// HTTPSPort is the public HTTPS port; 0 or 443 is omitted from URLs.
	HTTPSPort int
}

HopEnv is everything the redirect simulation needs about the instance.

type HostRedirect

type HostRedirect struct {
	TargetURL  string `json:"target_url"`
	StatusCode int    `json:"status_code"`
}

HostRedirect is one domain_redirect row: every request to the host is sent to TargetURL, keeping path and query when TargetURL is a bare origin.

type Limits

type Limits struct {
	MaxHeaderRules     int
	MaxForwarders      int
	MaxCacheRules      int
	MaxRegexLen        int
	MaxHeaderValueLen  int
	MaxListEntries     int
	MaxExemptEntries   int
	MaxBodyBytes       int
	ForwardTimeout     time.Duration
	ForwardMaxTimeout  time.Duration
	ForwardDialTimeout time.Duration
	CacheMaxObject     int64
	CacheMaxTTL        time.Duration
}

Limits caps the size of every policy section. Built from the environment with LimitsFromEnv; DefaultLimits is the zero-config value.

func DefaultLimits

func DefaultLimits() Limits

DefaultLimits are the caps used when no environment override is set.

func LimitsFromEnv

func LimitsFromEnv(getenv func(string) string) Limits

LimitsFromEnv overlays any set environment variable on DefaultLimits. Unparseable or non-positive values keep the default.

type Match

type Match struct {
	Kind    string   `json:"kind"`
	Path    string   `json:"path"`
	Methods []string `json:"methods,omitempty"`
}

Match selects requests by path and, optionally, method.

func (Match) Describe

func (m Match) Describe() string

Describe renders m for humans, e.g. "GET, HEAD paths under /api".

func (Match) Matches

func (m Match) Matches(method, path string) bool

Matches reports whether method and path satisfy m. Used by previews and the cache handler; the Caddy config uses native matchers instead.

type Policy

type Policy struct {
	Headers    *Headers    `json:"headers,omitempty"`
	Forwarders *Forwarders `json:"forwarders,omitempty"`
	Geo        *Geo        `json:"geo,omitempty"`
	Cache      *Cache      `json:"cache,omitempty"`
	Redirects  *Redirects  `json:"redirects,omitempty"`
}

Policy is every traffic control configured for one domain. A nil section means the domain uses the default behavior for it.

func (*Policy) Decode

func (p *Policy) Decode(kind string, raw []byte) error

Decode parses the stored JSON for kind into the matching section of p. On error p is left unchanged.

func (Policy) Empty

func (p Policy) Empty() bool

Empty reports whether no section is configured.

func (Policy) Validate

func (p Policy) Validate(l Limits, c Context) error

Validate checks every configured section.

type Redirects

type Redirects struct {
	ForceHTTPS bool `json:"force_https"`
	// ForceHTTPSStatus is 308 (default, keeps the method) or 301.
	ForceHTTPSStatus int    `json:"force_https_status,omitempty"`
	TrailingSlash    string `json:"trailing_slash,omitempty"`
	LowercaseHost    bool   `json:"lowercase_host,omitempty"`
}

Redirects is a domain's request normalisation: force HTTPS, trailing slash and lower case host. Canonical host and alias redirects live in the domain_redirect table, one row per redirected host.

func (Redirects) HTTPSStatus

func (r Redirects) HTTPSStatus() int

HTTPSStatus returns the effective force HTTPS status.

func (Redirects) SlashMode

func (r Redirects) SlashMode() string

SlashMode returns the effective trailing slash mode.

func (*Redirects) Validate

func (r *Redirects) Validate() error

Validate checks the settings.

type ResolvedTarget

type ResolvedTarget struct {
	Scheme     string
	Host       string
	Port       string
	Addr       netip.Addr
	BasePath   string
	ServerName string
}

ResolvedTarget is an external forwarder URL pinned to one checked address.

func (ResolvedTarget) Dial

func (t ResolvedTarget) Dial() string

Dial is the ip:port Caddy should connect to.

type Resolver

type Resolver interface {
	LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error)
}

Resolver is the DNS surface CheckTarget needs; *net.Resolver satisfies it.

type SecurityPreset

type SecurityPreset struct {
	// HSTS needs a publicly trusted certificate; validation refuses it otherwise.
	HSTS                  bool   `json:"hsts"`
	HSTSMaxAge            int    `json:"hsts_max_age,omitempty"`
	HSTSIncludeSubdomains bool   `json:"hsts_include_subdomains,omitempty"`
	ContentTypeOptions    bool   `json:"content_type_options"`
	FrameOptions          string `json:"frame_options,omitempty"`
	ReferrerPolicy        string `json:"referrer_policy,omitempty"`
	PermissionsPolicy     string `json:"permissions_policy,omitempty"`
}

SecurityPreset is the one click set of browser hardening headers.

func DefaultSecurityPreset

func DefaultSecurityPreset(tlsReal bool) SecurityPreset

DefaultSecurityPreset is what the dashboard's "Security headers" button applies.

type Step

type Step struct {
	Stage   string `json:"stage"`
	Outcome string `json:"outcome"`
	Final   bool   `json:"final,omitempty"`
}

Step is one stage of the human readable "what happens to this request" preview, in the order the ingress evaluates them.

func Explain

func Explain(p Policy, method, path string) []Step

Explain walks method and path through p in ingress order.

type ValidationError

type ValidationError struct {
	Kind   string       `json:"kind"`
	Fields []FieldError `json:"fields"`
}

ValidationError collects every FieldError found in one section.

func AsValidationError

func AsValidationError(err error) (*ValidationError, bool)

AsValidationError unwraps err into a ValidationError when it is one.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Jump to

Keyboard shortcuts

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