ingress

package
v0.6.0 Latest Latest
Warning

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

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

Documentation

Overview

Package ingress is the Phase 0 spike for embedding Caddy as a Go library, driven entirely through its in-process admin API. There is no `caddy` binary anywhere in this package and no Caddyfile on disk. Config goes in as a JSON document built from typed Go structs (never a hand-written Caddyfile string), because Phase 1's real requirement is building that document programmatically from app.yaml (the declarative app spec's contract), not editing config by hand.

This package intentionally defines its own minimal structs mirroring the slice of Caddy's JSON config schema this spike needs (admin, apps.http, apps.tls), rather than importing caddy's own config types directly. Caddy's own types lean heavily on json.RawMessage for every extension point (module namespaces), which makes them awkward to construct directly from Go; a small hand-rolled struct set that marshals to the same wire shape is both easier to build from app.yaml data later and easier to table-test in isolation, without starting Caddy at all.

Index

Constants

View Source
const (

	// MaxAuthGateCopyHeaders caps copy_headers per domain.
	MaxAuthGateCopyHeaders = 16
	// MaxAuthGateTimeoutSeconds caps a domain's own timeout override.
	MaxAuthGateTimeoutSeconds = 120

	// DefaultAuthGateCheckPath is the check path when none is configured.
	DefaultAuthGateCheckPath = "/"
)
View Source
const (
	GateHeaderSecret   = "X-Gate-Secret"
	GateHeaderHost     = "X-Gate-Host"
	GateHeaderProto    = "X-Gate-Proto"
	GateHeaderClientIP = "X-Gate-Client-Ip"
)

Subrequest headers the built-in gate provider sets; the check endpoint trusts the others only when GateHeaderSecret matches.

View Source
const (
	CertEventAbilityIssued  = "cert.issued"
	CertEventAbilityRenewed = "cert.renewed"
)

Synthetic actor/caller fields for a Store-triggered audit_log row, mirroring internal/api.ClientKindCLI et al.'s "named caller surface" constants. Ability doubles as the issued-vs-renewed distinction a reader (web/src/lib/auditLabels.ts) keys its friendly label off.

View Source
const (
	EnvReadHeaderTimeout = "APP_INGRESS_READ_HEADER_TIMEOUT"
	EnvReadTimeout       = "APP_INGRESS_READ_TIMEOUT"
	EnvWriteTimeout      = "APP_INGRESS_WRITE_TIMEOUT"
	EnvIdleTimeout       = "APP_INGRESS_IDLE_TIMEOUT"
	EnvMaxHeaderBytes    = "APP_INGRESS_MAX_HEADER_BYTES"
	EnvMaxBodyBytes      = "APP_INGRESS_MAX_BODY_BYTES"
	EnvProtocols         = "APP_INGRESS_PROTOCOLS"
	EnvTrustedProxies    = "APP_INGRESS_TRUSTED_PROXIES"
	EnvClientIPHeaders   = "APP_INGRESS_CLIENT_IP_HEADERS"
	EnvRetryWindow       = "APP_INGRESS_RETRY_WINDOW"
	EnvRetryInterval     = "APP_INGRESS_RETRY_INTERVAL"
	EnvDialTimeout       = "APP_INGRESS_DIAL_TIMEOUT"
	EnvFailDuration      = "APP_INGRESS_PASSIVE_FAIL_DURATION"
	EnvRetryAfter        = "APP_INGRESS_UNAVAILABLE_RETRY_AFTER"
	EnvGracePeriod       = "APP_INGRESS_GRACE_PERIOD"
)

Environment variables that tune Hardening. Durations use Go syntax ("10s"), "0" disables a timeout or limit where noted.

View Source
const (
	SocketNameHTTPS = "https"
	SocketNameHTTP  = "http"

	// EnvSocketActivation is auto (default), true or false.
	EnvSocketActivation = "APP_INGRESS_SOCKET_ACTIVATION"
)

Socket names in the systemd .socket unit (FileDescriptorName=).

View Source
const (
	LBPolicyRoundRobin = "round_robin"
	LBPolicyLeastConn  = "least_conn"
	LBPolicyIPHash     = "ip_hash"
	LBPolicyURIHash    = "uri_hash"
	LBPolicyCookie     = "cookie"
	LBPolicyWeighted   = "weighted_round_robin"
)

Selection policy names accepted by LBRoute.Policy.

View Source
const (
	DefaultMaintenanceTitle      = "Down for maintenance"
	DefaultMaintenanceMessage    = "This site is temporarily unavailable. Please check back soon."
	DefaultMaintenanceRetryAfter = 300
	MaxMaintenanceTitle          = 120
	MaxMaintenanceMessage        = 2000
)

Maintenance page defaults and bounds.

View Source
const (
	PurgeURL    = "url"
	PurgePrefix = "prefix"
	PurgeAll    = "all"
)

Cache purge scopes.

View Source
const (
	EnvGeoIPDB            = "APP_GEOIP_DB"
	EnvGeoIPCountryHeader = "APP_GEOIP_COUNTRY_HEADER"
	EnvGeoIPSource        = "APP_GEOIP_SOURCE"
)

Geo source environment variables.

View Source
const (
	GeoSourceHeader = "header"
	GeoSourceMMDB   = "mmdb"
	GeoSourceNone   = "none"
)

Geo source names, reported by GeoStatus.

View Source
const (
	PublicHostSourceEnv      = "env"
	PublicHostSourceDetected = "detected"
	PublicHostSourceDisabled = "disabled"
	PublicHostSourceNone     = "none"
)

Public host sources reported alongside the resolved address.

View Source
const (
	RouteCheckHeader      = "X-Ingress-Route-Check"
	RouteCheckReplyHeader = "X-Ingress-Route-Reply"
)

Headers of the route check a verifier sends through a fronting proxy.

View Source
const (
	WakeTokenHeader = "X-Wake-Token" //nolint:gosec // header name, not a credential
	WakeAppHeader   = "X-Wake-App"
	WakeURIHeader   = "X-Wake-Uri"
)

Headers the wake route sends so the control plane knows which app to start.

View Source
const (

	// CacheStatusHeader reports HIT, MISS, STALE or BYPASS on cacheable paths.
	CacheStatusHeader = "X-Cache"
)
View Source
const EnvAuthGateTimeout = "APP_AUTH_GATE_TIMEOUT"

EnvAuthGateTimeout overrides the auth gate subrequest timeout for domains that do not set their own. Go duration ("3s") or whole seconds ("3").

View Source
const EnvCacheMaxBytes = "APP_INGRESS_CACHE_MAX_BYTES"

EnvCacheMaxBytes caps the whole in-process response cache.

View Source
const (
	// NoIndexHeaderValue is what a hidden domain sends on every response.
	NoIndexHeaderValue = "noindex, nofollow, noarchive, nosnippet"
)
View Source
const UpstreamHostPlaceholder = "{http.reverse_proxy.upstream.hostport}"

UpstreamHostPlaceholder sends the dialed upstream's host:port as Host.

View Source
const WakePath = "/api/v1/hooks/wake"

WakePath is the control plane endpoint a wake route proxies to.

Variables

View Source
var (
	ErrACMEContactInvalid  = errors.New("acme_email is not a valid email address")
	ErrACMEContactReserved = errors.New("acme_email uses a domain Let's Encrypt rejects (example.com, .test, .invalid, .localhost and similar); use a real, reachable address")
)

Reasons ValidateACMEContact rejects an address, so callers can tell them apart.

Functions

func ACMEIssuable added in v0.2.0

func ACMEIssuable(host string) bool

ACMEIssuable reports whether a public CA can plausibly issue for host. Wildcards are judged by their base name.

func AuthGateTimeoutFromEnv added in v0.2.0

func AuthGateTimeoutFromEnv(getenv func(string) string) (time.Duration, error)

AuthGateTimeoutFromEnv returns the default subrequest timeout.

func ClearListenEnv added in v0.2.0

func ClearListenEnv()

ClearListenEnv removes the sd_listen_fds variables so child processes do not mistake them for their own.

func DetectPublicIPs added in v0.2.0

func DetectPublicIPs(ctx context.Context) []string

DetectPublicIPs returns this host's public IPv4 and IPv6 addresses (each when it has one), so a DNS check can accept an A or an AAAA record.

func FallbackDomain added in v0.2.0

func FallbackDomain(publicHost, serviceName string) (domain string, ok bool)

FallbackDomain synthesizes the zero-config, publicly resolvable HTTPS hostname a fresh deploy with no domain configured gets routed under: "<app>.<public-ip-dash-encoded>.sslip.io". Real DNS, so Caddy's existing automatic-HTTPS path (internal/reconcile/ingress's own ACME vs internal-issuer choice, governed by store.IngressSettings. ACMEEnabled exactly like any operator-configured domain) needs no special case to issue it a certificate, as long as this control plane's own :80/:443 are genuinely reachable at publicHost from the public internet, the same precondition any custom domain already has.

ok is false, meaning "no fallback available, route nothing," when:

  • publicHost isn't a literal IP address (APP_PUBLIC_HOST as a hostname has no address to dash-encode; a hostname operator already has a resolvable name and should set a real domain instead)
  • the IP is private, loopback, link-local, or unspecified: sslip.io would still resolve it, but Let's Encrypt's HTTP-01 challenge could never reach a non-public address regardless, so producing a URL that can only ever fail to get a trusted certificate is worse than producing none
  • serviceName has no characters left after sanitizing into a valid DNS label (lowercased, non-alphanumeric runs collapsed to a single hyphen, leading/trailing hyphens trimmed)

func HTTPRedirectActive added in v0.2.0

func HTTPRedirectActive() bool

HTTPRedirectActive reports the last value the ingress reconciler set.

func ImportFileCerts added in v0.2.0

func ImportFileCerts(ctx context.Context, certStore CertStore, dir string, logger *slog.Logger) (int, error)

ImportFileCerts copies a legacy Caddy file-storage tree (dir/certificates, dir/acme) into the database-backed cert storage, skipping keys already present, so switching storage does not force every domain to re-issue. A missing dir is not an error. Returns the number of keys imported.

func IsPubliclyRoutable added in v0.2.0

func IsPubliclyRoutable(ip net.IP) bool

IsPubliclyRoutable is isPubliclyRoutable for callers outside this package.

func IsWildcardDomain

func IsWildcardDomain(domain string) bool

IsWildcardDomain reports whether domain is a wildcard hostname (e.g. "*.example.com"). A leading "*." is the whole convention: no separate schema field marks a domain wildcard-eligible.

func LegacyStorageDir added in v0.2.0

func LegacyStorageDir() string

LegacyStorageDir is where Caddy's default file storage keeps its data when no storage module is configured.

func NormalizeAuthGateCheckPath added in v0.2.0

func NormalizeAuthGateCheckPath(p string) (string, error)

NormalizeAuthGateCheckPath defaults an empty path to "/" and rejects anything that is not an absolute path with an optional query.

func NormalizeAuthGateCopyHeaders added in v0.2.0

func NormalizeAuthGateCopyHeaders(names []string) ([]string, error)

NormalizeAuthGateCopyHeaders canonicalizes and dedupes header names, rejecting non-tokens, reserved names and lists over the cap.

func PurgeCertsFromOtherIssuers added in v0.2.0

func PurgeCertsFromOtherIssuers(ctx context.Context, s CertPurgeStore, directoryURL string) (int, error)

PurgeCertsFromOtherIssuers deletes every stored certificate not issued through directoryURL's CA ("" means Let's Encrypt production). certmagic reuses a still-valid certificate from any issuer, so without this a switch from staging or the internal CA to the real CA would never re-issue until the old certificate expired. Returns the number of keys removed.

func PurgeCertsFromOtherIssuersOnce added in v0.2.0

func PurgeCertsFromOtherIssuersOnce(ctx context.Context, s CertPurgeMarkerStore, directoryURL string) (int, error)

PurgeCertsFromOtherIssuersOnce runs the purge only when the configured issuer differs from the one last purged for, so a restart never deletes valid certificates again and burns CA rate limits.

func ResolvePublicHost added in v0.2.0

func ResolvePublicHost(ctx context.Context) (host, source string)

ResolvePublicHost returns the address apps are reachable at and where it came from: APP_PUBLIC_HOST wins, APP_PUBLIC_IP_DETECT=off disables probing, otherwise the first public IPv4 literal any probe URL returns, falling back to IPv6 only when no IPv4 answer arrives. Never blocks longer than APP_PUBLIC_IP_PROBE_TIMEOUT.

func SSLIPHost added in v0.2.0

func SSLIPHost(publicHost string) (host string, ok bool)

SSLIPHost is the zero-DNS hostname for the server itself: "<dashed-ip>.sslip.io". ok is false when publicHost is not a public IP.

func SanitizeDNSLabel added in v0.2.0

func SanitizeDNSLabel(s string) string

SanitizeDNSLabel is sanitizeDNSLabel for callers outside this package.

func ServedHosts added in v0.2.0

func ServedHosts(cfg *Config) []string

ServedHosts lists every hostname cfg routes or manages a certificate for.

func SetActiveCertStorage

func SetActiveCertStorage(storage *SQLiteStorage)

SetActiveCertStorage registers storage as the backend the "sqlite" Caddy storage module resolves against (see NewSQLiteStorageRef). internal/reconcile/ingress.Controller's WithCertStore option calls this once per Controller, not once per reconcile: the whole point is that Caddy's repeated config reloads share the one already-open connection rather than each provisioning their own.

func SetDefaultGeoResolver added in v0.2.0

func SetDefaultGeoResolver(r *GeoResolver)

SetDefaultGeoResolver replaces the process-wide resolver (tests, reload).

func SetHTTPRedirectActive added in v0.2.0

func SetHTTPRedirectActive(on bool)

SetHTTPRedirectActive records whether the running ingress redirects plain HTTP to HTTPS itself, so the API can explain force HTTPS behavior.

func ValidateACMEContact added in v0.2.0

func ValidateACMEContact(email string) error

ValidateACMEContact checks the ACME account contact address a CA will accept.

func ValidateAuthGateURL added in v0.2.0

func ValidateAuthGateURL(raw string) (*url.URL, error)

ValidateAuthGateURL checks an auth service URL: http or https, a host, no userinfo, query or fragment. Private and internal hosts are allowed.

func ValidateWildcardDomain

func ValidateWildcardDomain(domain string) error

ValidateWildcardDomain rejects a malformed wildcard: a "*" anywhere but the single leading "*." label, or a base domain with no further label for a DNS-01 TXT record to attach under. Non-wildcard domains and syntactically valid wildcards both return nil.

Types

type ACMEFailure added in v0.2.0

type ACMEFailure struct {
	Identifier string
	Error      string
	Renewal    bool
	At         time.Time
}

ACMEFailure is the last failed certificate attempt for one hostname.

type ACMEFailures added in v0.2.0

type ACMEFailures struct {
	// contains filtered or unexported fields
}

ACMEFailures remembers each hostname's most recent failed obtain or renewal, so the API can show the real CA error instead of "pending". A later success clears the entry.

func DefaultACMEFailures added in v0.2.0

func DefaultACMEFailures() *ACMEFailures

DefaultACMEFailures is the process-wide tracker fed by Caddy's cert events.

func (*ACMEFailures) ACMEFailing added in v0.2.0

func (a *ACMEFailures) ACMEFailing(identifier string) (string, bool)

ACMEFailing returns the last error text for identifier; it satisfies alerting.ACMEFailureSource.

func (*ACMEFailures) Clear added in v0.2.0

func (a *ACMEFailures) Clear(identifier string)

Clear forgets identifier's failure.

func (*ACMEFailures) ClearAll added in v0.2.0

func (a *ACMEFailures) ClearAll()

ClearAll forgets every recorded failure, for when a proxy takes over TLS and earlier attempts no longer describe what visitors get.

func (*ACMEFailures) Get added in v0.2.0

func (a *ACMEFailures) Get(identifier string) (ACMEFailure, bool)

Get returns the last failure for identifier.

func (*ACMEFailures) Record added in v0.2.0

func (a *ACMEFailures) Record(f ACMEFailure)

Record stores a failure for identifier.

type ACMEIssuer

type ACMEIssuer struct {
	Module string `json:"module"`
	// Email is the ACME account contact address, Caddy's own "email"
	// field verbatim. Not technically required by the ACME protocol
	// itself, but this codebase's own settings validation
	// (internal/api) requires a non-empty, syntactically valid address
	// whenever ACME is enabled: an unreachable ACME account is exactly
	// the kind of "renewal fails silently" risk this project treats as
	// its central one to catch before it bites a real user.
	Email string `json:"email,omitempty"`
	// CA is Caddy's own "ca" field: the ACME directory endpoint URL.
	// Empty leaves Caddy's compiled-in default in place, Let's Encrypt's
	// real production directory
	// (https://acme-v02.api.letsencrypt.org/directory, per
	// caddytls.ACMEIssuer's own doc comment). An operator who wants to
	// avoid Let's Encrypt's production rate limits while testing should
	// set this explicitly to Let's Encrypt's staging directory
	// (https://acme-staging-v02.api.letsencrypt.org/directory); this
	// package does not default to staging on its own, since silently
	// issuing a staging (browser-untrusted) certificate for what an
	// operator believes is a production toggle would be a worse
	// surprise than requiring them to opt in explicitly.
	CA string `json:"ca,omitempty"`
	// Challenges is non-nil for wildcard subjects (NewDNSACMEIssuer sets
	// DNS-01 here, RFC 8555 7.1.1 forbids HTTP-01 for a wildcard
	// identifier) or when a non-default HTTP-01 port is configured
	// (NewACMEIssuer's altHTTPPort parameter).
	Challenges *ChallengesConfig `json:"challenges,omitempty"`
}

ACMEIssuer is Caddy's "acme" TLS issuer (tls.issuance.acme): real ACME issuance (Let's Encrypt or any RFC 8555-compatible CA), as opposed to InternalIssuer's fully offline, self-signed certificates. Field names and the "module" value ("acme", resolved within the tls.issuance namespace the same way InternalIssuer's "internal" is, per AutomationPolicy.Issuers' inline_key=module tag) are verified against the vendored source for the pinned caddyserver/caddy/v2 version this module uses (modules/caddytls/acmeissuer.go's ACMEIssuer struct and its CaddyModule ID "tls.issuance.acme"), not guessed: this package intentionally hand-rolls the wire-compatible subset of Caddy's own config types rather than importing them directly (see this file's package doc comment), and ACMEIssuer only needs the three fields an operator-facing settings form actually collects (email, an optional directory URL override, and the module discriminator), not the full surface (challenges config, account key, EAB, and so on) Caddy's own struct exposes for cases this codebase doesn't build a UI for yet.

ADR 005's Verified section is explicit that only the internal issuer path was proven end to end by that spike; this type exists to close that named, expected gap (see internal/api/certificates.go's own doc comment referencing the same open item), not to introduce a new architecture decision.

func NewACMEIssuer

func NewACMEIssuer(email, directoryURL string, altHTTPPort int) ACMEIssuer

NewACMEIssuer returns the JSON shape for Caddy's real ACME issuer module, used as an AutomationPolicy's Issuers entry in place of NewInternalIssuer when an operator has opted into real ACME (internal/store.IngressSettings.ACMEEnabled). directoryURL empty means "use Caddy's own default" (see ACMEIssuer.CA's doc comment); email empty is accepted by this constructor (it does not itself enforce the non-empty rule), since that validation belongs to the caller that owns the operator-facing form (internal/api), not to this low-level config builder. altHTTPPort, if non-zero and not 80, sets Challenges.HTTP.AlternatePort (see HTTPChallengeConfig's doc comment) so a non-wildcard host's HTTP-01 challenge doesn't reach for port 80 on an instance configured to run its ingress on different ports.

func NewDNSACMEIssuer added in v0.2.0

func NewDNSACMEIssuer(email, directoryURL string, provider DNS01Provider) ACMEIssuer

NewDNSACMEIssuer returns the JSON shape for Caddy's real ACME issuer configured to solve the DNS-01 challenge via provider instead of Caddy's default HTTP-01, for wildcard subjects. email and directoryURL pass straight through to the same fields NewACMEIssuer sets. This package performs no validation on provider's credentials; internal/api owns that.

type AccessFilter

type AccessFilter struct {
	MinMillis float64
	MinStatus int
	Since     time.Time
	Path      string
	Limit     int
}

AccessFilter narrows a ring read.

type AccessLog

type AccessLog struct {
	// contains filtered or unexported fields
}

AccessLog keeps the most recent requests per route key in process memory. Memory is bounded by hosts times capacity and the log is lost on restart, which is the point: it answers "what just happened", not history.

func DefaultAccessLog

func DefaultAccessLog() *AccessLog

DefaultAccessLog is the process-wide log the request_stats handler feeds.

func NewAccessLog

func NewAccessLog(capacity int) *AccessLog

NewAccessLog returns a log holding capacity records per key; capacity <= 0 disables recording.

func (*AccessLog) Query

func (l *AccessLog) Query(keys []string, f AccessFilter) []AccessRecord

Query returns matching records for the given keys, newest first.

func (*AccessLog) Record

func (l *AccessLog) Record(key string, rec AccessRecord)

Record stores one request under key.

type AccessRecord

type AccessRecord struct {
	Time      time.Time `json:"time"`
	Host      string    `json:"host"`
	Method    string    `json:"method"`
	Path      string    `json:"path"`
	Status    int       `json:"status"`
	Millis    float64   `json:"duration_ms"`
	BytesIn   int64     `json:"bytes_in"`
	BytesOut  int64     `json:"bytes_out"`
	RequestID string    `json:"request_id"`
	TraceID   string    `json:"trace_id,omitempty"`
	Upstream  bool      `json:"upstream_error,omitempty"`
}

AccessRecord is one finished edge request. The query string and client address are never captured.

type ActiveHealthChecks added in v0.2.0

type ActiveHealthChecks struct {
	URI          string `json:"uri,omitempty"`
	Interval     string `json:"interval,omitempty"`
	Timeout      string `json:"timeout,omitempty"`
	Passes       int    `json:"passes,omitempty"`
	Fails        int    `json:"fails,omitempty"`
	ExpectStatus int    `json:"expect_status,omitempty"`
}

ActiveHealthChecks is Caddy's health_checks.active block.

type AdminConfig

type AdminConfig struct {
	// Listen is the admin API bind address. Empty means Caddy's default,
	// localhost:2019.
	Listen string `json:"listen,omitempty"`
	// Disabled, if true, turns the admin listener off entirely. This
	// spike leaves it false (the real default) so the admin endpoint
	// actually comes up, since a production driver would want it
	// reachable for introspection even though this package itself talks
	// to Caddy via Go function calls (caddy.Load), not HTTP requests to
	// that listener.
	Disabled bool `json:"disabled,omitempty"`
	// Config holds admin-level config-management settings, notably
	// Persist. See AdminConfigSettings.
	Config *AdminConfigSettings `json:"config,omitempty"`
}

AdminConfig configures Caddy's admin API endpoint. Leaving Listen empty (the zero value) means "don't set this field" (omitempty), which lets Caddy apply its own default of localhost:2019 rather than us duplicating that default here.

type AdminConfigSettings

type AdminConfigSettings struct {
	// Persist controls whether Caddy keeps its own on-disk copy of
	// whatever config was last pushed to it (Caddy's default is true,
	// written to a fixed OS-level app-data path independent of the
	// storage module configured for certificates, see FileStorage's doc
	// comment). This spike found that surprising: it means an embedded
	// Caddy instance maintains a second, out-of-band record of its own
	// config on disk even when the storage module is pointed elsewhere,
	// which is exactly the "second config surface" ADR 005 argues
	// against embedding Caddy specifically to avoid. This spike's
	// builder pins it to false: the platform's own reconcile loop and
	// database are the single source of truth for desired ingress
	// state, not a file Caddy manages on its own.
	Persist *bool `json:"persist,omitempty"`
}

AdminConfigSettings mirrors Caddy's admin.config JSON field.

type Apps

type Apps struct {
	HTTP *HTTPApp `json:"http,omitempty"`
	TLS  *TLSApp  `json:"tls,omitempty"`
	PKI  *PKIApp  `json:"pki,omitempty"`
	// Layer4 carries every raw TCP stream (internal/reconcile/ingress's
	// StreamRoute), applied in the exact same Config/caddy.Load call as
	// HTTP/TLS/PKI above: see layer4.go's own package doc comment for why
	// a stream can't be a second, independently-applied Caddy config.
	Layer4 *Layer4App `json:"layer4,omitempty"`
	// Events feeds certificate success and failure events to DefaultACMEFailures.
	Events *EventsApp `json:"events,omitempty"`
}

Apps holds the top-level Caddy apps this package configures.

type AuditRecorder added in v0.2.0

type AuditRecorder interface {
	SaveAuditEntry(ctx context.Context, e store.AuditEntry) error
}

AuditRecorder is the narrow surface SQLiteStorage needs to record a certificate issuance or renewal as a system-driven audit_log row. *store.DB satisfies this via the same SaveAuditEntry internal/api's requireAbility already calls for every request-driven row; a background ACME renewal has no request behind it, so this is the one other call site audit_log ever gets written from.

type AuthGate added in v0.2.0

type AuthGate struct {
	URL         string
	CheckPath   string
	CopyHeaders []string
	Timeout     time.Duration
	// SetHeaders are set on the subrequest only, overwriting client values.
	// Internal use (built-in provider); values may hold Caddy placeholders.
	SetHeaders map[string]string
}

AuthGate is one domain's forward-auth check, run before the request reaches the backend.

func (AuthGate) Validate added in v0.2.0

func (g AuthGate) Validate() error

Validate reports whether g compiles into a gate.

type AutoHTTPSConfig

type AutoHTTPSConfig struct {
	Disabled     bool `json:"disable,omitempty"`
	DisableRedir bool `json:"disable_redirects,omitempty"`
}

AutoHTTPSConfig mirrors Caddy's automatic_https server field. See the Server.AutomaticHTTPS doc comment for why this spike sets DisableRedir.

type Automation

type Automation struct {
	Policies []AutomationPolicy `json:"policies,omitempty"`
}

Automation holds the ordered list of automation policies. The first policy whose Subjects match a given hostname wins.

type AutomationPolicy

type AutomationPolicy struct {
	Subjects []string `json:"subjects,omitempty"`
	Issuers  []any    `json:"issuers,omitempty"`
}

AutomationPolicy binds a set of subjects (hostnames) to the issuer(s) allowed to obtain certificates for them.

type BasicAuthAccount

type BasicAuthAccount struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

BasicAuthAccount is one entry in HTTPBasicAuthProvider.Accounts. Password is always a bcrypt hash, never the plaintext credential: see NewBasicAuthHandler.

type BasicAuthHandler

type BasicAuthHandler struct {
	Handler   string             `json:"handler"`
	Providers BasicAuthProviders `json:"providers"`
}

BasicAuthHandler is Caddy's "authentication" handler (http.handlers.authentication) configured with the http_basic provider (http.authentication.providers.http_basic): per-route HTTP Basic Auth checked against a bcrypt hash. A Route places this ahead of a ReverseProxyHandler in Handle so Caddy's own handler chain short-circuits with 401 before the request ever reaches the backend.

func NewBasicAuthHandler

func NewBasicAuthHandler(username, bcryptHash string) BasicAuthHandler

NewBasicAuthHandler builds Caddy's http_basic authentication handler for a single account. bcryptHash must already be a bcrypt hash (bcrypt.GenerateFromPassword); this function never sees or hashes a plaintext password itself.

type BasicAuthHash

type BasicAuthHash struct {
	Algorithm string `json:"algorithm"`
}

BasicAuthHash selects the password hashing algorithm HTTPBasicAuthProvider checks Accounts against. This package only ever sets "bcrypt", the same algorithm internal/api already uses for user login passwords (golang.org/x/crypto/bcrypt).

type BasicAuthProviders

type BasicAuthProviders struct {
	HTTPBasic *HTTPBasicAuthProvider `json:"http_basic"`
}

BasicAuthProviders holds the one provider this package configures.

type CA

type CA struct {
	// InstallTrust controls whether Caddy attempts to install this CA's
	// root certificate into the local OS/browser trust store on first
	// use. Caddy's own default is true, which on this spike's dev
	// machine meant an unprompted `sudo` invocation that failed
	// noisily (no interactive terminal, no `certutil`) but did not
	// block certificate issuance. That's tolerable on a developer
	// laptop; it is the wrong default for a headless server process,
	// which has no desktop trust store to install into and should
	// never attempt an interactive sudo prompt at startup. This spike's
	// TLS builder pins it to false explicitly rather than relying on
	// Caddy's default.
	InstallTrust *bool `json:"install_trust,omitempty"`
}

CA configures a single certificate authority managed by the pki app.

type CacheBucket added in v0.2.0

type CacheBucket struct {
	Minute int64  `json:"minute"`
	Hits   uint64 `json:"hits"`
	Misses uint64 `json:"misses"`
}

CacheBucket is one minute of cache activity for the dashboard sparkline.

type CacheHandler added in v0.2.0

type CacheHandler struct {
	Handler        string                    `json:"handler"`
	Key            string                    `json:"key"`
	Rules          []trafficpolicy.CacheRule `json:"rules"`
	MaxObjectBytes int64                     `json:"max_object_bytes"`
	MaxTTLSeconds  int                       `json:"max_ttl_seconds"`
}

CacheHandler is the wire shape of the levelrail_cache handler.

type CacheStats added in v0.2.0

type CacheStats struct {
	Hits     uint64        `json:"hits"`
	Misses   uint64        `json:"misses"`
	Bypasses uint64        `json:"bypasses"`
	Stale    uint64        `json:"stale"`
	Entries  int           `json:"entries"`
	Bytes    int64         `json:"bytes"`
	Series   []CacheBucket `json:"series"`
}

CacheStats is one domain's cache activity since the process started.

type CertKeyPEMPair

type CertKeyPEMPair struct {
	CertificatePEM string   `json:"certificate"`
	KeyPEM         string   `json:"key"`
	Tags           []string `json:"tags,omitempty"`
}

CertKeyPEMPair mirrors Caddy's caddytls.CertKeyPEMPair (tls.certificates.load_pem's wire shape, verified against modules/caddytls/pemloader.go in the vendored caddyserver/caddy/v2 source): a certificate and its private key as inline PEM text, no file on disk for either. Loading a certificate this way is also what makes Caddy skip automatic ACME/internal issuance for a matching hostname on its own (caddyhttp.AutoHTTPSConfig.IgnoreLoadedCerts defaults to false): no separate "skip" list is needed in Server.AutomaticHTTPS.

type CertPurgeMarkerStore added in v0.2.0

type CertPurgeMarkerStore interface {
	CertPurgeStore
	GetCertStorageValue(ctx context.Context, key string) (*store.CertStorageValue, error)
	SaveCertStorageValue(ctx context.Context, key string, value []byte) error
}

CertPurgeMarkerStore adds the marker read and write PurgeCertsFromOtherIssuersOnce needs.

type CertPurgeStore added in v0.2.0

type CertPurgeStore interface {
	ListCertStorageKeys(ctx context.Context, prefix string, recursive bool) ([]string, error)
	DeleteCertStorageValue(ctx context.Context, key string) error
}

CertPurgeStore is the storage surface PurgeCertsFromOtherIssuers needs.

type CertStore

type CertStore interface {
	SaveCertStorageValue(ctx context.Context, key string, value []byte) error
	GetCertStorageValue(ctx context.Context, key string) (*store.CertStorageValue, error)
	DeleteCertStorageValue(ctx context.Context, key string) error
	ExistsCertStorageValue(ctx context.Context, key string) (bool, error)
	ListCertStorageKeys(ctx context.Context, prefix string, recursive bool) ([]string, error)
	StatCertStorageValue(ctx context.Context, key string) (*store.CertStorageKeyInfo, error)
	AcquireCertStorageLock(ctx context.Context, name string, staleAfter time.Duration) (bool, error)
	TouchCertStorageLock(ctx context.Context, name string) error
	ReleaseCertStorageLock(ctx context.Context, name string) error
}

CertStore is the narrow surface SQLiteStorage needs from internal/store's SQLite-backed *store.DB, so tests can fake it without a real database. *store.DB satisfies this.

type CertificatesConfig

type CertificatesConfig map[string]any

CertificatesConfig is the wire shape for TLSApp.Certificates: each key names a tls.certificates.* loader module, value is that module's own JSON shape (typed any like Route.Handle, for the same reason: Caddy's module system is inherently polymorphic).

type ChallengesConfig

type ChallengesConfig struct {
	DNS  *DNSChallengeConfig  `json:"dns,omitempty"`
	HTTP *HTTPChallengeConfig `json:"http,omitempty"`
}

ChallengesConfig mirrors Caddy's caddytls.ChallengesConfig, scoped to the two fields this package sets: DNS and HTTP.

type ClientIPMatcher

type ClientIPMatcher struct {
	Ranges []string `json:"ranges"`
}

ClientIPMatcher mirrors Caddy's http.matchers.client_ip, which honours the server's trusted proxies.

type CloudflareDNSProvider

type CloudflareDNSProvider struct {
	Name     string `json:"name"`
	APIToken string `json:"api_token"`
}

CloudflareDNSProvider is the wire shape for Caddy's Cloudflare DNS provider module (dns.providers.cloudflare, github.com/caddy-dns/cloudflare wrapping github.com/libdns/cloudflare), used as a DNSChallengeConfig's Provider. APIToken must be a scoped Cloudflare API token (Zone:DNS:Edit), never the global API key.

type Config

type Config struct {
	Admin *AdminConfig `json:"admin,omitempty"`
	// Storage selects the Caddy storage module certificates, ACME
	// account state, and the internal issuer's local CA are persisted
	// through. Typed any rather than a single concrete struct for the
	// same reason Route.Handle and AutomationPolicy.Issuers already are
	// in this file: it is a Caddy module reference, and Caddy's module
	// system is inherently polymorphic (any value marshaling to
	// {"module": "<id>", ...} is valid here). *FileStorage (the local
	// filesystem, via NewFileStorage) and SQLiteStorageRef
	// (internal/store's SQLite, via NewSQLiteStorageRef)
	// are this package's two concrete options as of this writing; a nil
	// Storage leaves Caddy's own OS-specific default in place.
	Storage any  `json:"storage,omitempty"`
	Apps    Apps `json:"apps"`
	// Logging taps certificate-issuance errors; see newACMELogTap.
	Logging *LoggingConfig `json:"logging,omitempty"`
	// contains filtered or unexported fields
}

Config is the root of a Caddy JSON config document (https://caddyserver.com/docs/json/), scoped to the subset this spike exercises: the admin endpoint, the http app, and the tls app.

func BuildProxyConfig

func BuildProxyConfig(opts ProxyOptions) (*Config, error)

BuildProxyConfig builds a Config with exactly one server and one route: reverse-proxy every matching request to opts.BackendDial. This is the pure, no-Caddy-required half of the spike, table-tested in config_test.go; driver_test.go covers the half that actually starts Caddy and proxies a real request through it.

func BuildRoutesConfig

func BuildRoutesConfig(opts RoutesOptions) (*Config, error)

BuildRoutesConfig builds a Config with one server carrying one route per entry in opts.Routes (reverse_proxy), opts.StaticRoutes (file_server), opts.MaintenanceRoutes (static_response), and opts.RedirectRoutes (static_response with a Location header), each matched by its own Hosts. All four kinds share the same listener and the same TLS automation policy; nothing about a static, maintenance, or redirect route requires a different Server or a second Caddy config document. All four being empty is valid and produces a Config with a listener but no routes and no TLS app: this is the normal shape for a reconcile pass over zero currently-routable resources (every known service or static site either declares no domains or, for a container service, has no running container yet, and no domain is in maintenance mode or redirected), not an error.

type CorazaWAFHandler added in v0.2.0

type CorazaWAFHandler struct {
	Handler string `json:"handler"`
	// Directives is a ModSecurity/Coraza directives block, evaluated
	// verbatim. NewCorazaWAFHandler always sets SecRuleEngine plus the
	// three @-prefixed OWASP CRS includes LoadOWASPCRS makes resolvable.
	Directives string `json:"directives"`
	// LoadOWASPCRS makes the coraza-coreruleset ruleset (compiled into
	// this binary via the blank import in driver.go) resolvable from
	// Directives' @-prefixed Include paths. NewCorazaWAFHandler always
	// sets this true; there is no per-domain custom-ruleset authoring in
	// this package, only the stock OWASP CRS.
	LoadOWASPCRS bool `json:"load_owasp_crs"`
}

CorazaWAFHandler is Caddy's OWASP Coraza WAF handler (http.handlers.waf, github.com/corazawaf/coraza-caddy/v2). Handler is the literal string "waf": the module's own CaddyModule ID, distinct from its Caddyfile directive name "coraza_waf".

func NewCorazaWAFHandler added in v0.2.0

func NewCorazaWAFHandler(blocking bool) CorazaWAFHandler

NewCorazaWAFHandler builds the OWASP Coraza handler for a domain that opted into the WAF. blocking selects SecRuleEngine On (reject on match) versus DetectionOnly (log on match, never reject); see docs/domains-and-ingress.md for why detection-only is this platform's recommended first-enable default.

type DNS01Provider added in v0.2.0

type DNS01Provider interface {
	// contains filtered or unexported methods
}

DNS01Provider is implemented by every ACME DNS-01 challenge provider's wire-shape type (CloudflareDNSProvider, Route53DNSProvider), letting NewDNSACMEIssuer build a wildcard automation policy from whichever provider an operator has configured without a provider-specific issuer constructor per provider. Cloudflare is this interface's first implementation, not a hardcoded special case: adding another provider is exactly what Route53DNSProvider below does.

type DNSChallengeConfig

type DNSChallengeConfig struct {
	// Provider is a dns.providers.* module reference, typed any like
	// Route.Handle. Its inline_key is "name", not "module" (per
	// caddytls.DNSChallengeConfig.ProviderRaw's struct tag).
	Provider any `json:"provider"`
}

DNSChallengeConfig mirrors Caddy's caddytls.DNSChallengeConfig, scoped to the one field this package sets: Provider.

type Driver

type Driver struct {
	// contains filtered or unexported fields
}

Driver drives an in-process Caddy instance through the same code path as its HTTP admin API: caddy.Load calls the identical config-apply logic the admin API's POST /load handler calls, without this package ever shelling out to a `caddy` binary or running one as a subprocess or sibling container. The embedded-ingress design requires this shape specifically: ingress state lives in the control plane's own process, not in something else's.

func New

func New(logger *slog.Logger) *Driver

New builds a Driver. A nil logger falls back to slog.Default(), matching the convention in internal/reconcile.Engine.

func (*Driver) Apply

func (d *Driver) Apply(ctx context.Context, cfg *Config) error

Apply marshals cfg to Caddy's JSON config format and loads it into the running (package-global) Caddy instance, starting it on first call. Caddy's config model has no notion of "the config for this Driver" versus "the config for some other caller in the process": caddy.Load replaces the entire process-wide config, which is a real constraint Phase 1 needs to design around once ingress config is built incrementally from many apps rather than handed over as one document per call.

func (*Driver) OwnsPort added in v0.2.0

func (d *Driver) OwnsPort(port int) bool

OwnsPort reports whether the most recently applied config bound an HTTP server to port. Used by GET /api/v1/system/doctor (internal/api) to tell this control plane's own ingress apart from an unrelated process holding the same port.

func (*Driver) Stop

func (d *Driver) Stop(ctx context.Context) error

Stop tears down the running Caddy instance, releasing every listener it holds. Safe to call even if Apply was never called.

type ErrorPage added in v0.2.0

type ErrorPage struct {
	StatusCode int
	Body       string
}

ErrorPage is one status-code-to-body mapping for a domain (store.DomainErrorPage), threaded through from internal/reconcile/ingress into a route's Handle chain.

type ErrorsConfig added in v0.2.0

type ErrorsConfig struct {
	Routes []Route `json:"routes,omitempty"`
}

ErrorsConfig mirrors Caddy's per-subroute "errors" field (the JSON shape behind the Caddyfile's handle_errors directive): Routes runs after the primary routes return an error, each matched by its own Matcher against {http.error.status_code}.

type EventSubscription added in v0.2.0

type EventSubscription struct {
	Events   []string `json:"events"`
	Handlers []any    `json:"handlers"`
}

EventSubscription binds handlers to named events.

type EventsApp added in v0.2.0

type EventsApp struct {
	Subscriptions []EventSubscription `json:"subscriptions"`
}

EventsApp is Caddy's "events" app, used only to observe certificate events.

type ExpressionMatcher added in v0.2.0

type ExpressionMatcher struct {
	Expr string `json:"expr"`
	// Name is always emitted: Caddy 2.11's MatchExpression.UnmarshalJSON
	// type-asserts it and panics the whole process when it is absent.
	Name string `json:"name"`
}

ExpressionMatcher mirrors Caddy's http.matchers.expression module: a CEL expression evaluated with Caddy's placeholders expanded first.

type FileServerHandler

type FileServerHandler struct {
	Handler string `json:"handler"`
	// Root is the local filesystem directory to serve, e.g.
	// "/var/lib/levelrail-data/static/docs/abc1234". Caddy resolves
	// requests underneath it directly; there is no upstream to dial and
	// nothing here to route through Docker.
	Root string `json:"root,omitempty"`
}

FileServerHandler is Caddy's "file_server" handler (http.handlers.file_server): serves files directly from Root, no backend, no container. This is the whole of what this design means by "static sites get served by the embedded Caddy directly with no container": a route whose Handle is this instead of a ReverseProxyHandler. Registered via the same modules/standard blank import driver.go already pulls in for reverse_proxy and the TLS issuers, no separate module wiring needed.

func NewFileServerHandler

func NewFileServerHandler(root string) FileServerHandler

NewFileServerHandler builds the one handler shape a static route needs: serve files from root for everything matched by the route.

type FileStorage

type FileStorage struct {
	Module string `json:"module"`
	Root   string `json:"root,omitempty"`
}

FileStorage is Caddy's default storage module (caddy.storage.file_system): certificates, ACME account state, and the internal issuer's local CA all live under Root. Caddy's own default, when Storage is left nil, is an OS-specific application-data directory outside the project entirely (e.g. ~/Library/Application Support/Caddy on macOS) which is the right thing for the `caddy` CLI on a developer's own machine, and the wrong thing for an embedded control plane: it scatters state outside the platform's own data directory and, worse, is shared and reused across every unrelated process on the machine that also happens to embed Caddy with default settings.

func NewFileStorage

func NewFileStorage(dir string) *FileStorage

NewFileStorage returns the JSON shape for Caddy's file-system storage module rooted at dir. Module is "file_system", not the fully-qualified "caddy.storage.file_system": the "storage" field resolves modules within the caddy.storage namespace already (see StorageRaw's caddy struct tag in caddy's own Config type), so the namespace prefix here would be doubled up if included, a subtle enough detail to call out explicitly.

type GeoHandler added in v0.2.0

type GeoHandler struct {
	Handler string            `json:"handler"`
	Rule    trafficpolicy.Geo `json:"rule"`
}

GeoHandler is the wire shape of the levelrail_geo handler.

type GeoResolver added in v0.2.0

type GeoResolver struct {
	// contains filtered or unexported fields
}

GeoResolver maps a client address to an ISO country code. Safe for concurrent use; the MMDB reader is read only after Open.

func DefaultGeoResolver added in v0.2.0

func DefaultGeoResolver() *GeoResolver

DefaultGeoResolver is the process-wide resolver the geo handler uses; Caddy builds handlers from JSON and cannot receive one any other way.

func NewGeoResolver added in v0.2.0

func NewGeoResolver(getenv func(string) string) *GeoResolver

NewGeoResolver builds a resolver from the environment. source is auto (header when trusted proxies are configured, MMDB when APP_GEOIP_DB loads), header, mmdb or none.

func (*GeoResolver) Country added in v0.2.0

func (g *GeoResolver) Country(addr netip.Addr, headerValue string, trustedPeer bool) (country, source string)

Country resolves addr. headerValue is the country header's value and trustedPeer whether the connection came from a trusted proxy; the header is ignored otherwise, since any client can send it.

func (*GeoResolver) HeaderName added in v0.2.0

func (g *GeoResolver) HeaderName() string

HeaderName is the country header honoured from trusted proxies.

func (*GeoResolver) Status added in v0.2.0

func (g *GeoResolver) Status() GeoStatus

Status reports the active sources.

type GeoStatus added in v0.2.0

type GeoStatus struct {
	Active       bool     `json:"active"`
	Sources      []string `json:"sources"`
	Header       string   `json:"header,omitempty"`
	HeaderNeeds  string   `json:"header_needs,omitempty"`
	DBPath       string   `json:"db_path,omitempty"`
	DBType       string   `json:"db_type,omitempty"`
	DBBuildEpoch uint     `json:"db_build_epoch,omitempty"`
	Error        string   `json:"error,omitempty"`
}

GeoStatus says which country sources are active and why.

type HTTPApp

type HTTPApp struct {
	Servers map[string]*Server `json:"servers,omitempty"`
	// HTTPPort mirrors Caddy's own top-level "http_port" field: the port
	// automatic-HTTPS features (an enabled redirect, or an ACME HTTP-01
	// challenge with no AlternatePort set) fall back to. 0 (omitempty)
	// keeps Caddy's compiled-in default, port 80.
	HTTPPort int `json:"http_port,omitempty"`
	// GracePeriod mirrors Caddy's grace_period: how long in-flight requests
	// get to finish on shutdown or config replacement.
	GracePeriod string `json:"grace_period,omitempty"`
}

HTTPApp is Caddy's "http" app: one or more named servers, each with its own listeners and routes.

type HTTPBasicAuthProvider

type HTTPBasicAuthProvider struct {
	Accounts []BasicAuthAccount `json:"accounts"`
	Hash     BasicAuthHash      `json:"hash"`
}

HTTPBasicAuthProvider mirrors Caddy's caddyauth.HTTPBasicAuth: a fixed account list checked against Hash.

type HTTPChallengeConfig added in v0.2.0

type HTTPChallengeConfig struct {
	AlternatePort int  `json:"alternate_port,omitempty"`
	Disabled      bool `json:"disabled,omitempty"`
}

HTTPChallengeConfig mirrors Caddy's caddytls.HTTPChallengeConfig, scoped to the one field this package sets: AlternatePort. Without it, certmagic's HTTP-01 solver binds port 80 directly (github.com/caddyserver/certmagic's own default), independent of HTTPApp.HTTPPort, whenever Caddy's http app has no server already listening on port 80 to serve the challenge through (true here: see Server.AutomaticHTTPS's DisableRedir comment, this package never stands up that server). AlternatePort redirects that bind to whatever port this control plane's own ingress is actually configured for.

type HTTPTransport added in v0.2.0

type HTTPTransport struct {
	Protocol              string             `json:"protocol"`
	TLS                   *UpstreamTLSConfig `json:"tls,omitempty"`
	DialTimeout           string             `json:"dial_timeout,omitempty"`
	ResponseHeaderTimeout string             `json:"response_header_timeout,omitempty"`
}

HTTPTransport is Caddy's reverse_proxy http transport.

type Hardening added in v0.2.0

type Hardening struct {
	ReadHeaderTimeout time.Duration
	ReadTimeout       time.Duration
	WriteTimeout      time.Duration
	IdleTimeout       time.Duration
	MaxHeaderBytes    int
	MaxBodyBytes      int64
	Protocols         []string
	TrustedProxies    []string
	ClientIPHeaders   []string
	RetryWindow       time.Duration
	RetryInterval     time.Duration
	DialTimeout       time.Duration
	FailDuration      time.Duration
	RetryAfterSeconds int
	// GracePeriod lets in-flight requests finish when the config is replaced or
	// the process stops. Zero closes them at once.
	GracePeriod time.Duration
}

Hardening is the edge policy applied to every proxied route and to the shared HTTP server: slowloris and size limits, protocols, trusted proxies, and the failover behaviour that replaces a raw 502 with a friendly 503.

func DefaultHardening added in v0.2.0

func DefaultHardening() Hardening

DefaultHardening returns defaults that are safe for general web apps: no whole-request read or write deadline (uploads and SSE must keep working), but bounded headers, idle connections and dial time.

func HardeningFromEnv added in v0.2.0

func HardeningFromEnv(getenv func(string) string) (Hardening, error)

HardeningFromEnv reads every APP_INGRESS_* tuning variable over the defaults.

func (Hardening) UnavailableResponse added in v0.2.0

func (h Hardening) UnavailableResponse(title, message string) StaticResponseHandler

UnavailableResponse is the friendly 503 served when no backend can answer right now: it carries Retry-After, is never cached, and refreshes itself.

type HeaderOps added in v0.2.0

type HeaderOps struct {
	Set    map[string][]string `json:"set,omitempty"`
	Delete []string            `json:"delete,omitempty"`
}

HeaderOps is Caddy's headers.HeaderOps.

type HeaderResponse added in v0.2.0

type HeaderResponse struct {
	Set      map[string][]string `json:"set,omitempty"`
	Deferred bool                `json:"deferred,omitempty"`
}

HeaderResponse sets response headers; Deferred applies them after the upstream answered, so they win over the backend's own value.

type HeadersHandler added in v0.2.0

type HeadersHandler struct {
	Handler  string          `json:"handler"`
	Request  *HeaderOps      `json:"request,omitempty"`
	Response *HeaderResponse `json:"response,omitempty"`
}

HeadersHandler mirrors Caddy's headers handler, response side only.

func NewNoIndexHeadersHandler added in v0.2.0

func NewNoIndexHeadersHandler() HeadersHandler

NewNoIndexHeadersHandler sets X-Robots-Tag on every response.

type HealthChecks added in v0.2.0

type HealthChecks struct {
	Active  *ActiveHealthChecks  `json:"active,omitempty"`
	Passive *PassiveHealthChecks `json:"passive,omitempty"`
}

HealthChecks is Caddy's reverse_proxy health_checks block.

type HoldRoute added in v0.2.0

type HoldRoute struct {
	Hosts []string
}

HoldRoute is a domain whose backend is momentarily absent (mid-deploy or restarting): it answers the friendly 503 and keeps the host in the TLS automation set so the handshake still succeeds.

type HoldTracker added in v0.2.0

type HoldTracker struct {
	// contains filtered or unexported fields
}

HoldTracker remembers when each host last had a live backend, so a host that loses its backend mid-deploy keeps answering a friendly 503 (and keeps its certificate) for a bounded window instead of vanishing from the config. It must outlive per-pass controllers.

func NewHoldTracker added in v0.2.0

func NewHoldTracker(window time.Duration) *HoldTracker

NewHoldTracker builds a tracker; a window of zero or less disables holding.

func (*HoldTracker) Held added in v0.2.0

func (t *HoldTracker) Held(host string, now time.Time) bool

Held reports whether host had a backend within the window, and forgets hosts whose window has lapsed.

func (*HoldTracker) Routed added in v0.2.0

func (t *HoldTracker) Routed(host string, now time.Time)

Routed records that host had a live backend at now.

type InheritedSockets added in v0.2.0

type InheritedSockets struct {
	HTTPS     int
	HTTP      int
	HTTPSPort int
	HTTPPort  int
}

InheritedSockets are listener file descriptors handed over by systemd. The kernel keeps accepting into their backlog while the control plane restarts, so a restart delays connections instead of refusing them.

func InheritSockets added in v0.2.0

func InheritSockets(getenv func(string) string, pid int, mode string) (*InheritedSockets, error)

InheritSockets reads the sd_listen_fds protocol. It returns nil with no error when nothing was passed. mode is the EnvSocketActivation value: "false" ignores inherited sockets, "true" makes their absence an error.

func (*InheritedSockets) Active added in v0.2.0

func (s *InheritedSockets) Active() bool

Active reports whether the HTTPS listener was inherited.

type InternalIssuer

type InternalIssuer struct {
	Module string `json:"module"`
	// Lifetime bounds the certificate it signs, so a fallback certificate
	// is replaced (and the public issuer retried) soon after it is issued.
	Lifetime string `json:"lifetime,omitempty"`
}

InternalIssuer is Caddy's "internal" TLS issuer (tls.issuance.internal): a locally-generated, self-signed CA, meant for exactly the case this spike is in, no public domain and no inbound port 80/443 reachable from the internet for real ACME. It works fully offline, standing in for real ACME here; verifying this against a real domain is still needed.

func NewInternalIssuer

func NewInternalIssuer() InternalIssuer

NewInternalIssuer returns the JSON shape for Caddy's internal issuer module, used as an AutomationPolicy's Issuers entry.

type LBRoute added in v0.2.0

type LBRoute struct {
	Upstreams []string
	// Weights aligns with Upstreams and is only emitted for the weighted policy.
	Weights []int
	Policy  string
	// CookieName is the sticky-session cookie for the cookie policy.
	CookieName            string
	Retries               int
	TryDuration           string
	TryInterval           string
	ActiveHealth          *ActiveHealthChecks
	PassiveHealth         *PassiveHealthChecks
	StreamCloseDelay      string
	DialTimeout           string
	ResponseHeaderTimeout string
	UpstreamTLS           *UpstreamTLSConfig
	RateLimitRPS          int
	RateLimitBurst        int
}

LBRoute is one route's upstream pool and balancing settings. A ProxyRoute with LB set uses these upstreams instead of BackendDial.

func NewLBRoute added in v0.2.0

func NewLBRoute(cfg loadbalancer.Config, ups []loadbalancer.Upstream, weights []int) *LBRoute

NewLBRoute translates a load balancer config and its discovered upstreams into Caddy route settings. It returns nil when there are no upstreams.

type Layer4App added in v0.2.0

type Layer4App struct {
	Servers map[string]*Layer4Server `json:"servers,omitempty"`
}

Layer4App is Caddy's "layer4" app (github.com/mholt/caddy-l4/layer4), mirrored here the same way HTTPApp/TLSApp hand-roll the wire-compatible subset of Caddy's own config types rather than importing them directly (see config.go's package doc comment).

type Layer4ProxyHandler added in v0.2.0

type Layer4ProxyHandler struct {
	Handler   string               `json:"handler"`
	Upstreams []Layer4UpstreamDial `json:"upstreams,omitempty"`
}

Layer4ProxyHandler is caddy-l4's "proxy" handler (layer4.handlers.proxy, github.com/mholt/caddy-l4/modules/l4proxy): plain TCP passthrough to Upstreams, no TLS termination or re-encryption. Handler is always the literal string "proxy": the module's own CaddyModule ID, mirroring ReverseProxyHandler's identical "fixed discriminator as a plain field" shape.

func NewLayer4ProxyHandler added in v0.2.0

func NewLayer4ProxyHandler(backendDial string) Layer4ProxyHandler

NewLayer4ProxyHandler builds the one handler shape a stream needs: forward every byte to backendDial, unconditionally.

type Layer4Route added in v0.2.0

type Layer4Route struct {
	Handle []any `json:"handle"`
}

Layer4Route is one layer4 route: Match is always left empty (omitted), which caddy-l4's own MatcherSets.AnyMatch treats as "always match", the same convention Route.Match's absence has in Caddy's HTTP app.

type Layer4Server added in v0.2.0

type Layer4Server struct {
	Listen []string      `json:"listen,omitempty"`
	Routes []Layer4Route `json:"routes,omitempty"`
}

Layer4Server is one layer4 listener plus the routes it serves. Unlike HTTPApp's Server (one shared listener, many Host-matched routes), one StreamRoute gets its own Layer4Server keyed by its own listen address, because a stream has nothing else to match requests on.

type Layer4UpstreamDial added in v0.2.0

type Layer4UpstreamDial struct {
	Dial []string `json:"dial,omitempty"`
}

Layer4UpstreamDial is one caddy-l4 proxy backend. Dial is a list for caddy-l4's own failover support; this package only ever sets one address, since a stream has exactly one backend (no load balancing, out of scope for this first slice, see RoutesOptions.Streams's own doc comment).

type LoadBalancing added in v0.2.0

type LoadBalancing struct {
	SelectionPolicy *SelectionPolicy `json:"selection_policy,omitempty"`
	Retries         int              `json:"retries,omitempty"`
	TryDuration     string           `json:"try_duration,omitempty"`
	TryInterval     string           `json:"try_interval,omitempty"`
}

LoadBalancing is Caddy's reverse_proxy load_balancing block.

type LogConfig added in v0.2.0

type LogConfig struct {
	Writer  map[string]string `json:"writer"`
	Encoder map[string]string `json:"encoder"`
	Level   string            `json:"level,omitempty"`
	Include []string          `json:"include"`
}

LogConfig is one named Caddy log.

type LoggingConfig added in v0.2.0

type LoggingConfig struct {
	Logs map[string]*LogConfig `json:"logs"`
}

LoggingConfig is Caddy's logging section, used only to tap ACME errors.

type MaintenanceGate

type MaintenanceGate struct {
	Page        MaintenancePage
	BypassCIDRs []string
	BypassPaths []string
}

MaintenanceGate puts a proxied route behind the maintenance page while letting bypass clients and paths through to the full route.

type MaintenancePage

type MaintenancePage struct {
	Title             string
	Message           string
	RetryAfterSeconds int
}

MaintenancePage is what visitors see while a domain is in maintenance. Empty fields fall back to the defaults.

type MaintenanceRoute

type MaintenanceRoute struct {
	// Hosts are the Host header values that get the fixed maintenance
	// response. Also contributes to the Subjects list used for TLS
	// automation when RoutesOptions.TLS is true, the same as Hosts on
	// ProxyRoute/StaticRoute: a domain in maintenance mode still needs
	// a valid certificate, an operator flips this on and off far more
	// often than they'd want to also manage TLS for.
	Hosts []string
	Page  MaintenancePage
}

MaintenanceRoute is one domain currently in maintenance mode: every request gets NewMaintenanceResponseHandler's fixed response instead of ever reaching a backend, container or otherwise. Unlike ProxyRoute and StaticRoute, a MaintenanceRoute needs no backend address or root directory to have ever existed: "this domain is intentionally unavailable right now" is true independent of whether the service behind it has a running container this pass.

type Matcher

type Matcher struct {
	Host       []string            `json:"host,omitempty"`
	Path       []string            `json:"path,omitempty"`
	Expression *ExpressionMatcher  `json:"expression,omitempty"`
	Not        []Matcher           `json:"not,omitempty"`
	Vars       map[string][]string `json:"vars,omitempty"`
	Header     map[string][]string `json:"header,omitempty"`
	ClientIP   *ClientIPMatcher    `json:"client_ip,omitempty"`
}

Matcher is a single Caddy request matcher set. Host is used by every route this package builds; Expression (a CEL matcher, http.matchers.expression) is used only by error-page routes to match on the {http.error.status_code} placeholder, which no other matcher kind here can express.

type PKIApp

type PKIApp struct {
	CertificateAuthorities map[string]*CA `json:"certificate_authorities,omitempty"`
}

PKIApp is Caddy's "pki" app: the certificate authorities available to issuers like InternalIssuer.

type PassiveHealthChecks added in v0.2.0

type PassiveHealthChecks struct {
	FailDuration    string `json:"fail_duration,omitempty"`
	MaxFails        int    `json:"max_fails,omitempty"`
	UnhealthyStatus []int  `json:"unhealthy_status,omitempty"`
}

PassiveHealthChecks is Caddy's health_checks.passive block.

type ProxyHeaders added in v0.2.0

type ProxyHeaders struct {
	Request *HeaderOps `json:"request,omitempty"`
}

ProxyHeaders is reverse_proxy's headers block.

type ProxyOptions

type ProxyOptions struct {
	// ServerName keys the server within apps.http.servers. Arbitrary,
	// used only for logging/introspection.
	ServerName string
	// ListenAddr is a Caddy network address, e.g. ":8080" or
	// "127.0.0.1:8443".
	ListenAddr string
	// BackendDial is the reverse-proxy target, e.g. "127.0.0.1:9090".
	BackendDial string
	// Hosts, if non-empty, restricts the route to these Host header
	// values and is also the Subjects list used for TLS automation when
	// TLS is enabled. Empty means "match every request on this
	// listener", which is only valid for the plain-HTTP case: Caddy's
	// automatic HTTPS needs at least one qualifying hostname to know
	// what to issue a certificate for.
	Hosts []string
	// TLS, if true, adds a tls app automation policy scoping the
	// internal issuer to Hosts, and disables the HTTP->HTTPS redirect
	// (see Server.AutomaticHTTPS).
	TLS bool
	// AdminListen overrides Caddy's admin API bind address. Empty keeps
	// Caddy's own default (localhost:2019).
	AdminListen string
	// StorageDir overrides Caddy's storage root (certificates, ACME
	// account state, the internal issuer's local CA). Empty keeps
	// Caddy's own OS-specific default. See FileStorage's doc comment for
	// why Phase 1 should always set this rather than rely on the
	// default.
	StorageDir string
}

ProxyOptions is the input to BuildProxyConfig: everything needed to stand up one reverse-proxy route, with or without TLS.

type ProxyRewrite added in v0.2.0

type ProxyRewrite struct {
	Method string `json:"method,omitempty"`
	URI    string `json:"uri,omitempty"`
}

ProxyRewrite is reverse_proxy's rewrite block: it changes only the request sent upstream, never the one later handlers see.

type ProxyRoute

type ProxyRoute struct {
	// Hosts are the Host header values routed to BackendDial. Also
	// contributes to the Subjects list used for TLS automation when
	// RoutesOptions.TLS is true.
	Hosts []string
	// BackendDial is the reverse-proxy target, e.g. "127.0.0.1:9090".
	BackendDial string
	// BasicAuth, if non-nil, adds a BasicAuthHandler ahead of the
	// reverse-proxy handler so every host in this route requires HTTP
	// Basic Auth. Nil (the default) reproduces this package's prior
	// behavior exactly: a plain reverse-proxy route with no
	// authentication handler.
	BasicAuth *BasicAuthAccount
	// WAF, if non-nil, adds a WAF and/or rate-limit handler ahead of
	// BasicAuth and the reverse-proxy handler. Nil (the default)
	// reproduces this package's prior behavior exactly: no such handler
	// at all.
	WAF *WAFConfig
	// AuthGate, if non-nil, sends every request to an auth service first.
	AuthGate *AuthGate
	// ErrorPages, if non-empty, replaces the response for each listed
	// status code with the operator's own HTML, covering both a status
	// the backend itself returns (via ReverseProxyHandler.HandleResponse)
	// and a real proxy failure Caddy generates on this route's behalf
	// (via a wrapping SubrouteHandler.Errors). Empty (the default)
	// reproduces this package's prior behavior exactly.
	ErrorPages []ErrorPage
	// Hidden asks search engines to stay away: X-Robots-Tag on every
	// response and an ingress-served /robots.txt that disallows everything.
	Hidden bool
	// LB, if non-nil, replaces BackendDial with a multi-upstream pool.
	LB *LBRoute
	// Policy, if non-nil, adds the domain's traffic controls (policy_*.go).
	Policy *RoutePolicy
	// Upstream, if non-nil, shapes a standalone domain's single backend.
	// Ignored when LB is set.
	Upstream *UpstreamOptions
	// Maintenance, if non-nil, serves the maintenance page to every request
	// outside its bypass list, ahead of every other control.
	Maintenance *MaintenanceGate
}

ProxyRoute is one reverse-proxy backend routed by hostname within a Server potentially shared with other routes. Unlike ProxyOptions (BuildProxyConfig), where Hosts is optional (an empty Hosts list means "match everything on this listener," valid for a single-backend listener), a Route here always requires host matching: hostname is the only thing that disambiguates two different backends sharing one listener.

type RateLimitHandler added in v0.2.0

type RateLimitHandler struct {
	Handler    string                   `json:"handler"`
	RateLimits map[string]RateLimitZone `json:"rate_limits,omitempty"`
}

RateLimitHandler is Caddy's rate_limit handler (http.handlers.rate_limit, github.com/mholt/caddy-ratelimit).

func NewRateLimitHandler added in v0.2.0

func NewRateLimitHandler(zonePrefix string, rps, burst int) RateLimitHandler

NewRateLimitHandler builds the rate-limit handler for a domain that opted into rate limiting. zonePrefix must be globally unique across every domain's rate_limit handler in the same Caddy config (the underlying module keys its limiter state by zone name across the whole process, not just within one handler instance); callers pass the domain itself, which service_domains already enforces as unique.

Two zones approximate a token-bucket's sustained-rate-plus-burst shape on top of caddy-ratelimit's sliding-window primitive: a 10s window bounds the sustained average at rps, and (only when burst exceeds rps) a 1s window separately allows up to burst requests in any single second. burst at or below rps adds no second zone, since it would never be the tighter constraint.

type RateLimitZone added in v0.2.0

type RateLimitZone struct {
	Key       string `json:"key,omitempty"`
	MaxEvents int    `json:"max_events,omitempty"`
	Window    string `json:"window,omitempty"`
}

RateLimitZone is one named zone within a RateLimitHandler (github.com/mholt/caddy-ratelimit's own RateLimit struct, mirrored here the same way this package hand-rolls every other Caddy module's wire shape rather than importing it directly). Window is a Caddy duration string (e.g. "1s", "10s"), not a number of nanoseconds: caddy.Duration's UnmarshalJSON accepts either, this package always emits the string form for readability.

type RedirectRoute added in v0.2.0

type RedirectRoute struct {
	// Hosts are the Host header values that get redirected to TargetURL.
	// Also contributes to the Subjects list used for TLS automation when
	// RoutesOptions.TLS is true, the same as MaintenanceRoute.Hosts: a
	// redirected domain still needs a valid certificate to redirect a
	// browser that arrives over HTTPS in the first place.
	Hosts []string
	// TargetURL is the absolute URL every matched request is redirected
	// to, e.g. "https://example.com" or "https://newapp.example.com/promo".
	// Validated by the caller (internal/api); this package performs no
	// validation of its own.
	TargetURL string
	// StatusCode is the redirect's HTTP status: store.DomainRedirectPermanent
	// (301, the default) or store.DomainRedirectTemporary (302).
	StatusCode int
}

RedirectRoute is one domain currently configured to redirect to an arbitrary target URL: every request gets NewRedirectResponseHandler's fixed response instead of ever reaching a backend, container or otherwise. Mirrors MaintenanceRoute's shape exactly (no backend address needed to have ever existed) for a different per-domain behavior: "this domain has moved" rather than "this domain is intentionally unavailable right now".

type RedirectsHandler added in v0.2.0

type RedirectsHandler struct {
	Handler string `json:"handler"`
	// contains filtered or unexported fields
}

RedirectsHandler is the wire shape of redirectsModule.

func (RedirectsHandler) CaddyModule added in v0.2.0

func (RedirectsHandler) CaddyModule() caddy.ModuleInfo

func (RedirectsHandler) ServeHTTP added in v0.2.0

func (m RedirectsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error

type RequestBodyHandler added in v0.2.0

type RequestBodyHandler struct {
	Handler string `json:"handler"`
	MaxSize int64  `json:"max_size"`
}

RequestBodyHandler is Caddy's request_body handler: it caps how much of a request body the proxy will read.

type RequestObservation

type RequestObservation struct {
	Method      string
	Path        string
	Status      int
	Duration    time.Duration
	BytesIn     int64
	BytesOut    int64
	UpstreamErr bool
	Upstream    string
	RequestID   string
	At          time.Time
}

RequestObservation is one finished request as the stats see it. Upstream is the replica host:port; no query string or client address is ever included.

type RequestStats added in v0.2.0

type RequestStats struct {
	// contains filtered or unexported fields
}

RequestStats aggregates request activity per route key in process memory. Memory is bounded by the number of configured hosts: one fixed-size counter set per key, never per path or client.

func DefaultRequestStats added in v0.2.0

func DefaultRequestStats() *RequestStats

DefaultRequestStats is the process-wide aggregator the request_stats handler feeds; Caddy modules are constructed from JSON and cannot receive a live value any other way.

func NewRequestStats added in v0.2.0

func NewRequestStats() *RequestStats

NewRequestStats returns an empty aggregator.

func (*RequestStats) DrainByApp added in v0.2.0

func (s *RequestStats) DrainByApp() map[string]telemetry.RequestWindow

DrainByApp returns and resets activity since the last call, merged per app. Keys with no owner are dropped.

func (*RequestStats) KeysForApp

func (s *RequestStats) KeysForApp(app string) []string

KeysForApp returns the route keys owned by app.

func (*RequestStats) Observe added in v0.2.0

func (s *RequestStats) Observe(key string, status int, d time.Duration, bytesIn, bytesOut int64, upstreamErr bool)

Observe records one finished request.

func (*RequestStats) ObservePath added in v0.2.0

func (s *RequestStats) ObservePath(key, path string, status int, d time.Duration, bytesIn, bytesOut int64, upstreamErr bool)

ObservePath records one finished request and, when route counting is on and path is non-empty, its normalized route template. Only the path is read, never the query string, and ids are collapsed before anything is stored.

func (*RequestStats) ObserveRequest

func (s *RequestStats) ObserveRequest(key string, o RequestObservation)

ObserveRequest records o under key: counters, route, replica histogram and, for slow or failed requests, a bounded sample.

func (*RequestStats) ObserveShield

func (s *RequestStats) ObserveShield(key string, reason trafficpolicy.ShieldReason)

ObserveShield counts one request the abuse shield turned away under key.

func (*RequestStats) SetHostOwners added in v0.2.0

func (s *RequestStats) SetHostOwners(owners map[string]string)

SetHostOwners replaces the host-to-app map used to attribute traffic. Keys are configured route hosts, values are app names.

type RequestStatsHandler added in v0.2.0

type RequestStatsHandler struct {
	Handler string `json:"handler"`
	Key     string `json:"key"`
}

RequestStatsHandler is the wire shape of the request_stats handler (http.handlers.request_stats): it times the rest of the route and records status class, latency bucket and byte counts under Key, the route's first configured host. Query strings and client addresses are never read.

func NewRequestStatsHandler added in v0.2.0

func NewRequestStatsHandler(key string) RequestStatsHandler

NewRequestStatsHandler builds the handler recording under key.

type ResponseCache added in v0.2.0

type ResponseCache struct {
	// contains filtered or unexported fields
}

ResponseCache is a bounded LRU of whole responses shared by every cache handler in the process. Memory stays under MaxBytes: inserting evicts least recently used entries until the new one fits.

func DefaultResponseCache added in v0.2.0

func DefaultResponseCache() *ResponseCache

DefaultResponseCache is the process-wide cache the cache handler uses, sized from APP_INGRESS_CACHE_MAX_BYTES.

func NewResponseCache added in v0.2.0

func NewResponseCache(maxBytes int64) *ResponseCache

NewResponseCache returns an empty cache capped at maxBytes.

func (*ResponseCache) MaxBytes added in v0.2.0

func (c *ResponseCache) MaxBytes() int64

MaxBytes is the global byte cap.

func (*ResponseCache) Purge added in v0.2.0

func (c *ResponseCache) Purge(host, scope, value string) int

Purge removes host's entries: one URL (path and query, every Vary variant), every path under a prefix, or all of them. Returns the count.

func (*ResponseCache) Stats added in v0.2.0

func (c *ResponseCache) Stats(host string) CacheStats

Stats returns host's counters and the last 30 minutes of activity, oldest first.

func (*ResponseCache) UsedBytes added in v0.2.0

func (c *ResponseCache) UsedBytes() int64

UsedBytes is the current total size of every entry.

type ResponseHandler added in v0.2.0

type ResponseHandler struct {
	Match  *ResponseMatcher `json:"match,omitempty"`
	Routes []Route          `json:"routes,omitempty"`
}

ResponseHandler mirrors Caddy's http.ResponseHandler: when Match matches the backend's response, Routes runs instead of passing the original response through.

type ResponseMatcher added in v0.2.0

type ResponseMatcher struct {
	StatusCode []int `json:"status_code,omitempty"`
}

ResponseMatcher mirrors Caddy's reverse_proxy response matcher: a backend response matches when its status code is in StatusCode.

type ReverseProxyHandler

type ReverseProxyHandler struct {
	Handler   string     `json:"handler"`
	Upstreams []Upstream `json:"upstreams"`
	// HandleResponse mirrors reverse_proxy's own handle_response field:
	// routes evaluated against the backend's actual response, ahead of
	// it being written to the client. Used by error-page routes to
	// replace a backend-returned status like 404 or 500 with a custom
	// body while keeping the same status code.
	HandleResponse []ResponseHandler `json:"handle_response,omitempty"`

	LoadBalancing    *LoadBalancing `json:"load_balancing,omitempty"`
	HealthChecks     *HealthChecks  `json:"health_checks,omitempty"`
	Transport        *HTTPTransport `json:"transport,omitempty"`
	StreamCloseDelay string         `json:"stream_close_delay,omitempty"`
	Headers          *ProxyHeaders  `json:"headers,omitempty"`
	Rewrite          *ProxyRewrite  `json:"rewrite,omitempty"`
}

ReverseProxyHandler is Caddy's "reverse_proxy" handler (http.handlers.reverse_proxy). Handler is always the literal string "reverse_proxy"; it is a field rather than a constant embedded via MarshalJSON so the struct stays a plain, table-testable value.

func NewLBReverseProxyHandler added in v0.2.0

func NewLBReverseProxyHandler(lb *LBRoute) ReverseProxyHandler

NewLBReverseProxyHandler renders lb as a reverse_proxy handler with a multi-upstream pool.

func NewReverseProxyHandler

func NewReverseProxyHandler(backendDial string) ReverseProxyHandler

NewReverseProxyHandler builds the one handler shape this spike needs: proxy everything matched by the route to a single backend.

type RewriteHandler added in v0.2.0

type RewriteHandler struct {
	Handler string `json:"handler"`
	URI     string `json:"uri"`
	Method  string `json:"method,omitempty"`
}

RewriteHandler is Caddy's "rewrite" handler: it replaces the request URI and, when set, the method. The wake hook is GET-only whatever the client sent.

type Route

type Route struct {
	Match  []Matcher `json:"match,omitempty"`
	Handle []any     `json:"handle"`
}

Route is one matcher-set-plus-handlers pair, in Caddy's route shape.

type Route53DNSProvider added in v0.2.0

type Route53DNSProvider struct {
	Name            string `json:"name"`
	AccessKeyID     string `json:"access_key_id,omitempty"`
	SecretAccessKey string `json:"secret_access_key,omitempty"`
	Region          string `json:"region,omitempty"`
	HostedZoneID    string `json:"hosted_zone_id,omitempty"`
}

Route53DNSProvider is the wire shape for Caddy's Route53 DNS provider module (dns.providers.route53, github.com/caddy-dns/route53 wrapping github.com/libdns/route53), used as a DNSChallengeConfig's Provider. AccessKeyID and SecretAccessKey must be an IAM credential scoped to route53:ChangeResourceRecordSets/route53:ListResourceRecordSets/ route53:GetChange on the target hosted zone. Region and HostedZoneID are optional: empty lets libdns/route53 resolve them itself (region from the AWS SDK's own default chain, hosted zone by matching the domain against the account's own zones).

type RouteCheck

type RouteCheck struct {
	Token string
	Reply string
}

RouteCheck proves a request reached this ingress: a request carrying Token in RouteCheckHeader gets 204 with Reply, which no app behind it can know.

func NewRouteCheck

func NewRouteCheck() (RouteCheck, error)

NewRouteCheck returns a random token pair, valid for this process.

func (RouteCheck) Valid

func (c RouteCheck) Valid() bool

Valid reports whether both values are well formed and distinct.

type RoutePolicy added in v0.2.0

type RoutePolicy struct {
	Policy trafficpolicy.Policy
	// AppDials maps an app name to its reverse proxy dial address.
	AppDials map[string]string
	// Targets maps an external forwarder URL to its checked address. A URL
	// missing here (blocked or unresolvable) compiles to a 502 response.
	Targets map[string]trafficpolicy.ResolvedTarget
	// TLSTerminatedUpstream means a proxy in front terminates TLS.
	TLSTerminatedUpstream bool
	// TLSReal means visitors see a publicly trusted certificate.
	TLSReal bool
	// HTTPSPort is the public HTTPS port for force HTTPS redirects.
	HTTPSPort int
	// GeoActive is false when no country source is configured; geo rules
	// are then skipped and reported as disabled.
	GeoActive bool
	Limits    trafficpolicy.Limits
}

RoutePolicy is one domain's traffic controls (internal/trafficpolicy), plus what the reconciler resolved for this pass: upstream dials for app forwarders and checked addresses for external ones.

type RoutesOptions

type RoutesOptions struct {
	// ServerName keys the server within apps.http.servers. Arbitrary,
	// used only for logging/introspection.
	ServerName string
	// ListenAddr is a Caddy network address shared by every route, e.g.
	// ":443".
	ListenAddr string
	// HTTPPort is this control plane's own configured HTTP/port-80
	// equivalent (see internal/reconcile/ingress.WithHTTPListenAddr).
	// Threaded to HTTPApp.HTTPPort and, when ACME is enabled for a
	// non-wildcard host, to the ACME issuer's Challenges.HTTP.
	// AlternatePort (see NewACMEIssuer), so an instance running its
	// ingress on non-default ports never reaches for a literal port 80.
	// 0 keeps Caddy's own default (80) in both places.
	HTTPPort int
	// HTTPRedirect, when true, lets Caddy bind the HTTP port and 308
	// redirect every routed host to https. Off keeps the port unbound
	// outside ACME challenges (non-root dev runs cannot bind port 80).
	HTTPRedirect bool
	// FallbackCertLifetime is how long the internal fallback certificate
	// served while public ACME issuance fails lives, so the public CA is
	// retried on that cadence. Empty keeps Caddy's default (12h).
	FallbackCertLifetime string
	// PlainHTTP serves the same routes over unencrypted HTTP on HTTPPort,
	// for a proxy in front that terminates TLS and dials this port.
	PlainHTTP bool
	// Routes is every reverse-proxy backend to route on this listener.
	// Empty is valid: it produces a listener with no routes and no TLS
	// automation policy, the normal shape for a reconcile pass over zero
	// currently-routable services, not an error condition.
	Routes []ProxyRoute
	// StaticRoutes is every static site to serve directly on this same
	// listener, alongside Routes: one shared Caddy server can carry both
	// reverse-proxy and file_server routes, disambiguated by Host the
	// same way two ProxyRoutes already are. Empty (the default) is valid
	// for exactly the same reason an empty Routes is.
	StaticRoutes []StaticRoute
	// MaintenanceRoutes is every domain currently in maintenance mode,
	// sharing this same listener the same way StaticRoutes does. A
	// caller must never put the same host in both Routes/StaticRoutes
	// and MaintenanceRoutes in one call: BuildRoutesConfig builds
	// whatever it's given, it does not itself resolve that conflict
	// (the ingress reconciler's job, splitting a service's hosts before
	// ever calling this function).
	MaintenanceRoutes []MaintenanceRoute
	// RedirectRoutes is every domain currently configured to redirect to
	// a target URL, sharing this same listener the same way
	// MaintenanceRoutes does. A caller must never put the same host in
	// both another route kind and RedirectRoutes in one call, the same
	// caller-resolves-conflicts contract MaintenanceRoutes documents:
	// internal/reconcile/ingress resolves maintenance mode ahead of a
	// redirect for any host with both configured (a domain deliberately
	// taken down for maintenance stays down, even if it also has a
	// stale redirect configured from an earlier migration), so a host
	// never reaches this field and MaintenanceRoutes at once.
	RedirectRoutes []RedirectRoute

	// RouteCheck, when set, answers a verifier's marker request on every
	// host, ahead of every other route.
	RouteCheck *RouteCheck
	// TLS, if true, adds a tls app automation policy scoped to every
	// route's Hosts, both Routes and StaticRoutes (skipped if both are
	// empty, since automatic HTTPS needs at least one subject to issue a
	// certificate for), and disables the HTTP->HTTPS redirect (see
	// Server.AutomaticHTTPS).
	TLS bool
	// ACMEEnabled, if true (and TLS is true and there's at least one
	// host), scopes every routed host's single automation policy to a
	// real ACME issuer (NewACMEIssuer) instead of Caddy's offline
	// internal issuer. This is the operator-facing toggle
	// internal/store.IngressSettings.ACMEEnabled drives
	// (internal/reconcile/ingress reads that row fresh every reconcile
	// pass and threads it straight through). False, the default,
	// reproduces this package's original, ADR-005-verified behavior
	// exactly: every route gets the internal issuer, byte-identical to
	// before this field existed. There is deliberately no per-host mix
	// of ACME and internal issuers in this pass: every currently-routed
	// host shares one automation policy, either all-internal or
	// all-ACME. Real per-domain granularity is genuine, existing Caddy
	// capability (AutomationPolicy already supports an ordered list of
	// policies, "the first policy whose Subjects match a given hostname
	// wins," see that type's own doc comment) but is an explicit,
	// deliberately out-of-scope v1 gap, not something this field's
	// absence of a per-route override quietly forecloses.
	ACMEEnabled bool
	// ACMEEmail is the ACME account contact address, passed straight
	// through to NewACMEIssuer when ACMEEnabled is true. This package
	// performs no validation on it (empty is accepted structurally);
	// internal/api's PUT /api/v1/settings/ingress is where "required and
	// syntactically valid whenever ACME is enabled" is actually
	// enforced, before a value ever reaches this builder.
	ACMEEmail string
	// ACMEDirectoryURL overrides the ACME CA's directory endpoint,
	// passed straight through to NewACMEIssuer when ACMEEnabled is true.
	// Empty keeps Caddy's own compiled-in default (Let's Encrypt
	// production). See ACMEIssuer.CA's own doc comment for why this
	// package never silently substitutes Let's Encrypt's staging
	// directory on an operator's behalf.
	ACMEDirectoryURL string
	// DNSProvider, if non-nil (and ACMEEnabled is true and at least one
	// route's host is a wildcard, see IsWildcardDomain), scopes every
	// wildcard subject to a separate automation policy using DNS-01 via
	// this provider (NewDNSACMEIssuer) instead of the plain ACME policy
	// every non-wildcard host still gets: HTTP-01 cannot solve a
	// wildcard identifier. Nil reproduces this package's prior behavior
	// exactly, wildcard hosts included, byte-identical to before this
	// field existed. Exactly one provider is ever active per reconcile
	// pass; internal/reconcile/ingress resolves which one (and its
	// credentials) from the operator-facing settings and internal/secrets.
	DNSProvider DNS01Provider
	// AdminListen overrides Caddy's admin API bind address. Empty keeps
	// Caddy's own default (localhost:2019).
	AdminListen string
	// StorageDir overrides Caddy's storage root. Empty keeps Caddy's own
	// OS-specific default. See FileStorage's doc comment. Ignored when
	// CertStorage is set.
	StorageDir string
	// TLSCertificates is every domain currently configured with a BYO TLS
	// certificate (internal/store.DomainTLSCert), sourced fresh from the
	// store and internal/secrets every reconcile pass by the caller (no
	// caching here, matching every other RoutesOptions field). Each
	// entry's Host must already be present in Routes/StaticRoutes/
	// MaintenanceRoutes' combined host list for Caddy to ever select the
	// loaded certificate for a real connection; a host with no route at
	// all still loads harmlessly, it's just never selected. Empty (the
	// default) reproduces this package's prior behavior exactly: every
	// host gets Caddy's automatic ACME/internal issuance as before this
	// field existed.
	TLSCertificates []TLSCertificateOverride
	// CertStorage, if non-nil, overrides StorageDir with an arbitrary
	// Caddy storage module reference (e.g. NewSQLiteStorageRef()),
	// letting the caller point Caddy's certificate/ACME-account storage
	// at internal/store's SQLite instead of the local
	// filesystem, so certificate storage lives in the database and
	// multi-node deployments can share cert state. Takes precedence over
	// StorageDir when both are set, rather than being an error, so a
	// caller migrating from one to the other doesn't need a separate
	// code path just to clear the old field.
	CertStorage any
	// RequestStats, if true, wraps every proxy and static route in the
	// request_stats handler (per-app request rate, errors, latency).
	RequestStats bool
	// Hardening, if non-nil, applies server limits, failover defaults and the
	// friendly 503 page. Nil keeps the legacy bare config.
	Hardening *Hardening
	// Inherited, when active, replaces the listen addresses with systemd
	// passed sockets and serves the HTTP to HTTPS redirect from its own server.
	Inherited *InheritedSockets
	// HoldRoutes answer 503 for hosts whose backend is momentarily absent.
	HoldRoutes []HoldRoute
	// WakeRoutes serve a "waking up" page for sleeping apps and ask the
	// control plane to start them.
	WakeRoutes []WakeRoute
	// Streams is every raw TCP port forward to proxy, each on its own
	// dedicated listener (apps.layer4.servers), applied in this same
	// Config/caddy.Load call: see layer4.go's package doc comment for
	// why this can't be a second, independently-applied Caddy config.
	// Empty (the default) adds no layer4 app at all, reproducing this
	// package's behavior before this field existed exactly.
	Streams []StreamRoute
}

RoutesOptions is the input to BuildRoutesConfig: everything needed to stand up one Caddy server carrying many independently host-routed backends on a single shared listener. This is the shape a real ingress controller needs: ADR 005's Verified section found that caddy.Load replaces Caddy's entire process-wide config on every call, so a controller tracking many services builds one complete Config from every currently routable service and applies it whole on every reconcile, rather than incrementally updating a single service's route. BuildProxyConfig, by contrast, only ever builds a single route to a single backend and stays as-is for that narrower spike use case.

type SQLiteStorage

type SQLiteStorage struct {
	// contains filtered or unexported fields
}

SQLiteStorage implements certmagic.Storage over internal/store's embedded SQLite, replacing Caddy's default FileStorage module for certificates and ACME account state. This is the concrete shape internal/reconcile/ingress/controller.go's own package doc comment already names as the real requirement: certificate storage lives in the database so multi-node deployments share cert state, and every ingress-driving process pointed at the same database file sees the same certificates and issuance locks through this type, so two of them never independently re-obtain a certificate for a domain the other already has one for.

func NewSQLiteStorage

func NewSQLiteStorage(certStore CertStore, logger *slog.Logger) *SQLiteStorage

NewSQLiteStorage builds a SQLiteStorage backed by certStore. A nil logger falls back to slog.Default(), matching this package's Driver convention (see driver.go's New).

func (*SQLiteStorage) Delete

func (s *SQLiteStorage) Delete(ctx context.Context, key string) error

Delete implements certmagic.Storage.

func (*SQLiteStorage) Exists

func (s *SQLiteStorage) Exists(ctx context.Context, key string) bool

Exists implements certmagic.Storage. The interface has no error return, so a lookup failure is treated as "doesn't exist" rather than panicking, logged so it's never silently swallowed: a false negative here just makes certmagic re-store/re-issue, safe, if wasteful, unlike a false positive, which could skip issuing a certificate that's actually missing.

func (*SQLiteStorage) List

func (s *SQLiteStorage) List(ctx context.Context, prefix string, recursive bool) ([]string, error)

List implements certmagic.Storage. Mirrors certmagic.FileStorage.List: listing a prefix that matches nothing returns fs.ErrNotExist, the same error a filepath.Walk over a missing directory would surface.

func (*SQLiteStorage) Load

func (s *SQLiteStorage) Load(ctx context.Context, key string) ([]byte, error)

Load implements certmagic.Storage.

func (*SQLiteStorage) Lock

func (s *SQLiteStorage) Lock(ctx context.Context, name string) error

Lock implements certmagic.Locker (embedded in certmagic.Storage). It blocks, polling every lockPollInterval, until it claims name's lock or ctx is cancelled. While held, a background goroutine refreshes the lock row every lockRefreshEvery so a live holder's lock never looks stale to a competitor; a holder that crashes without calling Unlock stops refreshing, and the row is treated as abandoned (and may be taken over) once it is older than lockStaleAfter, mirroring certmagic's own FileStorage doc comment on stale-lock handling.

func (*SQLiteStorage) Stat

func (s *SQLiteStorage) Stat(ctx context.Context, key string) (certmagic.KeyInfo, error)

Stat implements certmagic.Storage.

func (*SQLiteStorage) Store

func (s *SQLiteStorage) Store(ctx context.Context, key string, value []byte) error

Store implements certmagic.Storage. A ".crt" key write is the one unambiguous "a certificate was (re)issued" signal regardless of which caller triggered it (Caddy's own ACME timer, or a reconciler nudge), so the audit hook lives here, the single choke point, not in any one caller. existedBefore is checked ahead of the real write so it always reflects state right before this write lands.

func (*SQLiteStorage) Unlock

func (s *SQLiteStorage) Unlock(ctx context.Context, name string) error

Unlock implements certmagic.Locker.

func (*SQLiteStorage) WithAuditRecorder added in v0.2.0

func (s *SQLiteStorage) WithAuditRecorder(ar AuditRecorder) *SQLiteStorage

WithAuditRecorder sets the audit log destination for certificate issuance/renewal events this storage detects. Returns s so it can be chained onto NewSQLiteStorage's result at the construction site.

type SQLiteStorageRef

type SQLiteStorageRef struct {
	Module string `json:"module"`
}

SQLiteStorageRef is the JSON shape assigned to Config.Storage (or RoutesOptions.CertStorage) to select the "sqlite" storage module. See NewSQLiteStorageRef.

func NewSQLiteStorageRef

func NewSQLiteStorageRef() SQLiteStorageRef

NewSQLiteStorageRef returns the JSON shape for the sqlite storage module. Pair with SetActiveCertStorage (directly, or via internal/reconcile/ingress.WithCertStore) so CertMagicStorage has a real backend to resolve to; a Config referencing this without an active backend registered fails to apply with a clear error rather than silently falling back to Caddy's file storage default.

type SelectionPolicy added in v0.2.0

type SelectionPolicy struct {
	Policy   string           `json:"policy"`
	Weights  []int            `json:"weights,omitempty"`
	Name     string           `json:"name,omitempty"`
	MaxAge   string           `json:"max_age,omitempty"`
	Fallback *SelectionPolicy `json:"fallback,omitempty"`
}

SelectionPolicy is Caddy's load_balancing.selection_policy.

type Server

type Server struct {
	Listen []string `json:"listen,omitempty"`
	Routes []Route  `json:"routes,omitempty"`

	// AutomaticHTTPS controls Caddy's automatic-HTTPS behavior for this
	// server specifically. This spike disables the HTTP->HTTPS redirect
	// (DisableRedir) for the TLS server: automatic redirects bind Caddy's
	// default HTTP port (80), which needs root and would surprise a local
	// spike run under a normal user, so the standalone TLS demo makes
	// that trade-off explicit instead of silently trying to grab port 80.
	AutomaticHTTPS *AutoHTTPSConfig `json:"automatic_https,omitempty"`

	ReadHeaderTimeout    string                `json:"read_header_timeout,omitempty"`
	ReadTimeout          string                `json:"read_timeout,omitempty"`
	WriteTimeout         string                `json:"write_timeout,omitempty"`
	IdleTimeout          string                `json:"idle_timeout,omitempty"`
	MaxHeaderBytes       int                   `json:"max_header_bytes,omitempty"`
	Protocols            []string              `json:"protocols,omitempty"`
	TrustedProxies       *TrustedProxiesConfig `json:"trusted_proxies,omitempty"`
	TrustedProxiesStrict int                   `json:"trusted_proxies_strict,omitempty"`
	ClientIPHeaders      []string              `json:"client_ip_headers,omitempty"`
	Errors               *ErrorsConfig         `json:"errors,omitempty"`
}

Server is a single HTTP(S) listener plus the routes it serves.

type ShieldHandler

type ShieldHandler struct {
	Handler string               `json:"handler"`
	Key     string               `json:"key"`
	Rule    trafficpolicy.Shield `json:"rule"`
}

ShieldHandler is the wire shape of the levelrail_shield handler. Key is the route's first host, the same key request stats use.

type StaticResponseHandler

type StaticResponseHandler struct {
	Handler string `json:"handler"`
	// StatusCode is an int, not caddyhttp.WeakString's own
	// string-or-number wire shape: Caddy accepts a plain JSON number
	// here for a fixed, non-placeholder status code, which is all this
	// package ever needs.
	StatusCode int                 `json:"status_code,omitempty"`
	Body       string              `json:"body,omitempty"`
	Headers    map[string][]string `json:"headers,omitempty"`
}

StaticResponseHandler is Caddy's "static_response" handler (http.handlers.static_response): a fixed response with no upstream, no container dial, and no filesystem read. This is the handler maintenance-mode routes use (routes.go's MaintenanceRoute): every request to a domain in maintenance mode gets this same fixed response instead of ever reaching a ReverseProxyHandler, so it works identically whether or not the service behind it currently has any running container at all.

func NewAssetUnavailableHandler

func NewAssetUnavailableHandler(retryAfter int) StaticResponseHandler

NewAssetUnavailableHandler answers an asset request with an empty, never cached 503 so no client ever stores HTML under an asset URL.

func NewErrorPageResponse added in v0.2.0

func NewErrorPageResponse(statusCode int, body string) StaticResponseHandler

NewErrorPageResponse builds the fixed response for one custom error page: statusCode preserved verbatim (so a custom 404 page still reads as a 404, not a 200), body replaced with the operator's own HTML.

func NewHiddenRobotsResponse added in v0.2.0

func NewHiddenRobotsResponse() StaticResponseHandler

NewHiddenRobotsResponse answers /robots.txt for a hidden domain.

func NewMaintenancePageHandler

func NewMaintenancePageHandler(p MaintenancePage) StaticResponseHandler

NewMaintenancePageHandler builds the 503 maintenance response. Operator text is HTML escaped, and Caddy placeholders are escaped so a title can never expand into request or environment values.

func NewMaintenanceResponseHandler

func NewMaintenanceResponseHandler() StaticResponseHandler

NewMaintenanceResponseHandler is the default maintenance page.

func NewRedirectResponseHandler added in v0.2.0

func NewRedirectResponseHandler(targetURL string, statusCode int) StaticResponseHandler

NewRedirectResponseHandler builds a fixed HTTP redirect: the same StaticResponseHandler shape NewMaintenanceResponseHandler uses, with a Location header and a 3xx status code instead of a fixed body. statusCode is the caller's responsibility to validate (store. DomainRedirectPermanent or store.DomainRedirectTemporary); this function does not itself enforce it is a redirect code.

type StaticRoute

type StaticRoute struct {
	// Hosts are the Host header values served from RootDir. Also
	// contributes to the Subjects list used for TLS automation when
	// RoutesOptions.TLS is true.
	Hosts []string
	// RootDir is the local filesystem directory to serve.
	RootDir string
}

StaticRoute is one static site routed by hostname within a Server potentially shared with other routes and with ProxyRoutes: a static site and a container service can be routed by the very same Caddy server, on different hostnames, because both ultimately produce a Route in the same Routes slice, just with a different Handle (FileServerHandler instead of ReverseProxyHandler). This is the shape that lets a static site bypass the container reconciler entirely, not a parallel ingress mechanism.

type StreamRoute added in v0.2.0

type StreamRoute struct {
	// ListenAddr is a Caddy network address, e.g. ":9000". Always a
	// single stream's own dedicated port; never shared with HTTP's
	// listener or another stream's.
	ListenAddr string
	// BackendDial is the TCP proxy target, e.g. "127.0.0.1:5432".
	BackendDial string
}

StreamRoute is one raw TCP port forward (store.AppStream): every byte received on ListenAddr is proxied unconditionally to BackendDial, no Host-based matching, no TLS termination/re-encryption, no access list. Unlike ProxyRoute, a StreamRoute never shares a listener with anything else: ListenAddr is the operator-chosen host port that disambiguates it, the same role Host plays for a ProxyRoute.

type SubrouteHandler added in v0.2.0

type SubrouteHandler struct {
	Handler string        `json:"handler"`
	Routes  []Route       `json:"routes,omitempty"`
	Errors  *ErrorsConfig `json:"errors,omitempty"`
}

SubrouteHandler is Caddy's "subroute" handler (http.handlers.subroute): runs Routes, then Errors.Routes if Routes returns an error. Error-page routes use this to catch a real proxy failure (e.g. the container is unreachable, which Caddy itself turns into a 502/504) in addition to ReverseProxyHandler.HandleResponse's own coverage of a status the backend actually returned.

type TLSApp

type TLSApp struct {
	Automation *Automation `json:"automation,omitempty"`
	// Certificates mirrors Caddy's tls.certificates field (a
	// caddy.ModuleMap keyed by loader module name within the
	// tls.certificates namespace). This package only ever sets the
	// "load_pem" key (see CertKeyPEMPair), for BYO TLS certificates.
	Certificates CertificatesConfig `json:"certificates,omitempty"`
}

TLSApp is Caddy's "tls" app: certificate automation policy plus any manually loaded certificates.

type TLSCertificateOverride

type TLSCertificateOverride struct {
	Host    string
	CertPEM string
	KeyPEM  string
}

TLSCertificateOverride is one domain with an operator-supplied certificate and key (internal/store.DomainTLSCert, BYO TLS), used in place of Caddy's automatic ACME/internal issuance for that host.

type TrustedProxiesConfig added in v0.2.0

type TrustedProxiesConfig struct {
	Source string   `json:"source"`
	Ranges []string `json:"ranges"`
}

TrustedProxiesConfig is Caddy's static trusted_proxies source.

type Upstream

type Upstream struct {
	// Dial is a host:port address, e.g. "127.0.0.1:8080". Caddy's own
	// docs note this must resolve to exactly one socket, no port ranges.
	Dial string `json:"dial"`
}

Upstream is a single reverse-proxy backend target.

type UpstreamOptions added in v0.2.0

type UpstreamOptions struct {
	TLS                bool
	InsecureSkipVerify bool
	ServerName         string
	// HostHeader is a literal Host value, escaped before use; empty keeps
	// the client's Host. HostFromUpstream wins over it.
	HostHeader            string
	HostFromUpstream      bool
	DisableWebSocket      bool
	DialTimeout           time.Duration
	ResponseHeaderTimeout time.Duration
	StripPrefix           string
	// BasePath is prepended to every forwarded path (a URL target's path).
	BasePath string
}

UpstreamOptions shape one proxy route's single backend: TLS toward it, the Host it sees, timeouts, websockets and path rewriting.

type UpstreamTLSConfig added in v0.2.0

type UpstreamTLSConfig struct {
	InsecureSkipVerify bool   `json:"insecure_skip_verify,omitempty"`
	ServerName         string `json:"server_name,omitempty"`
}

UpstreamTLSConfig is the transport-level tls block toward upstreams.

type VarsHandler added in v0.2.0

type VarsHandler struct {
	Handler string `json:"handler"`
}

VarsHandler is Caddy's no-op "vars" handler; handle_response skips a response handler whose routes are empty, so the 2xx branch carries one.

type WAFConfig added in v0.2.0

type WAFConfig struct {
	// Enabled turns on the OWASP Coraza handler (NewCorazaWAFHandler).
	Enabled bool
	// Blocking selects Coraza's SecRuleEngine mode: true is "On" (CRS
	// matches reject the request), false is "DetectionOnly" (CRS runs
	// and logs but never rejects). Only meaningful when Enabled is true.
	Blocking bool
	// RateLimitRPS, if greater than zero, adds a rate-limit handler
	// (NewRateLimitHandler) independent of Enabled: an operator can rate
	// limit a domain without also enabling the WAF, or vice versa.
	RateLimitRPS int
	// RateLimitBurst is the short-window (1s) cap; values at or below
	// RateLimitRPS are treated as "no extra burst allowance" by
	// NewRateLimitHandler.
	RateLimitBurst int
}

WAFConfig is one domain's opt-in Web Application Firewall and rate limiting settings (store.DomainWAF), threaded through from internal/reconcile/ingress into a route's Handle chain.

type WakeRoute added in v0.2.0

type WakeRoute struct {
	Hosts []string
	// Dial is the control plane's own listener, Token its shared wake secret.
	Dial  string
	Token string
	App   string
}

WakeRoute is a sleeping app's domains: any request starts the app and gets the control plane's waking response instead of an error.

Jump to

Keyboard shortcuts

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