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
- Variables
- func CanonicalPlan(domain, preset string) (from, to string, err error)
- func CheckRedirectLoop(rows map[string]HostRedirect, from, toHost string) error
- func Counterpart(domain string) (string, error)
- func ForwardedPath(f Forwarder, path string) string
- func IsCountryCode(c string) bool
- func IsKind(k string) bool
- func IsToken(s string) bool
- func NeverGeoBlocked(a netip.Addr) bool
- func ParseIPOrPrefix(s string) (netip.Prefix, error)
- func PrefixMatches(prefix, path string) bool
- func RedirectTarget(target string, req *url.URL) string
- func SafeHeaderValue(v string) bool
- func TargetHost(target string) string
- func ValidOrigin(o string) bool
- type CORSPreset
- type Cache
- type CacheRule
- type Context
- type EgressGuard
- type FieldError
- type Forwarder
- type Forwarders
- type Geo
- type GeoDecision
- type HeaderRule
- type Headers
- type Hop
- type HopEnv
- type HostRedirect
- type Limits
- type Match
- type Policy
- type Redirects
- type ResolvedTarget
- type Resolver
- type SecurityPreset
- type Step
- type ValidationError
Constants ¶
const ( PresetWWWToApex = "www-to-apex" PresetApexToWWW = "apex-to-www" PresetServeBoth = "both" )
Canonical host presets.
const ( ActionApp = "app" ActionURL = "url" ActionRedirect = "redirect" )
Forwarder actions.
const ( HostPreserve = "preserve" HostUpstream = "upstream" HostCustom = "custom" )
Host header modes for a forwarder.
const ( GeoAllow = "allow" GeoDeny = "deny" GeoActionBlock = "block" GeoActionRedirect = "redirect" GeoActionErrorPage = "error_page" GeoUnknownAllow = "allow" GeoUnknownBlock = "block" )
Geo modes, actions and unknown-country handling.
const ( SideRequest = "request" SideResponse = "response" OpSet = "set" OpAdd = "add" OpRemove = "remove" )
Header rule sides and operations.
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.
const ( MatchPrefix = "prefix" MatchExact = "exact" MatchRegex = "regex" )
Path match kinds.
const ( EnvForwardAllowPrivate = "APP_FORWARDER_ALLOW_PRIVATE_NETWORKS" EnvForwardAllowLoopback = "APP_FORWARDER_ALLOW_LOOPBACK" )
Environment switches that let forwarders reach non-public addresses.
const ( KindHeaders = "headers" KindForwarders = "forwarders" KindGeo = "geo" KindCache = "cache" KindRedirects = "redirects" )
Policy kinds, one stored row each per domain.
const ( SlashOff = "off" SlashAdd = "add" SlashRemove = "remove" )
Trailing slash normalisation modes.
const DefaultHSTSMaxAge = 31536000
DefaultHSTSMaxAge is one year, the preload list minimum.
const MaxHops = 8
MaxHops bounds a simulated chain; longer chains are reported as loops.
Variables ¶
AliasStatuses are the redirect codes an alias or canonical redirect may use.
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.
var Kinds = []string{KindRedirects, KindGeo, KindHeaders, KindCache, KindForwarders}
Kinds lists every policy kind in evaluation order.
Functions ¶
func CanonicalPlan ¶
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 ¶
Counterpart returns the www or apex twin of domain.
func ForwardedPath ¶
ForwardedPath is the path the upstream sees after strip and rewrite.
func IsCountryCode ¶
IsCountryCode reports whether c is an assigned ISO 3166-1 alpha-2 code.
func NeverGeoBlocked ¶
NeverGeoBlocked reports addresses geo rules never apply to: private, loopback, link-local and unspecified addresses have no country.
func ParseIPOrPrefix ¶
ParseIPOrPrefix accepts "203.0.113.7" or "203.0.113.0/24".
func PrefixMatches ¶
PrefixMatches is segment aware: /api matches /api and /api/x, not /apix.
func RedirectTarget ¶
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 ¶
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 ¶
TargetHost returns the lower case host of a redirect target URL.
func ValidOrigin ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
HostMode returns the effective Host header mode: external URLs default to the upstream's own host, apps keep the visitor's.
func (Forwarder) WebSocketEnabled ¶
WebSocketEnabled reports whether Upgrade requests pass through.
type Forwarders ¶
type Forwarders struct {
Rules []Forwarder `json:"rules"`
}
Forwarders is a domain's ordered path rules.
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 ¶
BlockStatus returns the status code for the block and error page actions.
func (Geo) Decide ¶
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 ¶
UnknownMode returns the effective unknown-country handling (allow by default).
type GeoDecision ¶
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.
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.
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 ¶
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.
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.
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 ¶
HTTPSStatus returns the effective force HTTPS status.
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.
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