Documentation
¶
Overview ¶
Package allowlist implements a Caddy on-demand TLS permission module that answers from an allow-list file held in memory.
Why ¶
Caddy consults the on-demand permission module on every handshake for a name it does not already hold in memory -- including names whose certificate is already in storage, because the module gates loading from storage as well as issuance. With the stock "ask" module that makes an HTTP endpoint a hard, per-handshake dependency for TLS on every site: while the endpoint is unreachable or returning errors, even certificates you already hold cannot be served. See https://caddy.community/t/33898.
This module removes the endpoint. The list is read into memory, every decision is a map lookup, and there is no listener that can be down.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Permission ¶
type Permission struct {
// Source is the allow-list file: one hostname per line. Blank lines and
// lines beginning with '#' are ignored, names are matched
// case-insensitively, and a trailing dot is tolerated. Required.
Source string `json:"source,omitempty"`
// Snapshot keeps a copy of the last successfully loaded list in Caddy's
// storage, and loads it at startup if the source cannot be read. Enabled by
// default.
//
// The in-memory list survives a reload but not a restart, and a restart is
// exactly when the source is most likely to be disturbed as well -- an
// unattended package upgrade restarts Caddy. Without a snapshot, such a
// restart means no allow-list at all.
Snapshot *bool `json:"snapshot,omitempty"`
// OnEmpty decides what happens when NEITHER the source nor the snapshot
// could be loaded, i.e. there is no allow-list at all.
//
// deny (default) refuse every name. Safe, but a total TLS outage.
// storage allow a name only if a certificate for it is already in
// storage: everything currently being served keeps working and
// nothing new is issued. Recommended.
// allow allow every name. Dangerous -- see below.
//
// Why "allow" is dangerous: granting permission makes Caddy attempt
// ISSUANCE for every name presented to it, and a host exposed to the
// internet sees a large volume of bogus SNI. Let's Encrypt permits 300 new
// orders per account per 3 hours, and the limit is ACCOUNT-wide, so a few
// minutes of this can block renewals for real domains for hours -- long
// after the list was repaired. It converts a brief list problem into a
// longer and wider outage.
//
// "storage" gives the same protection without that risk: keep serving
// certificates already held, issue nothing new.
OnEmpty string `json:"on_empty,omitempty"`
// ReloadInterval is how often the source is re-read and hashed. The list is
// only rebuilt when the CONTENT changed. Default 2s.
//
// Deliberately a poll rather than inotify. A publisher that writes
// atomically replaces the file by rename, and an inotify watch follows the
// INODE -- a watch on the file keeps watching the old, unlinked one and
// never fires again. Correct inotify means watching the directory for
// IN_MOVED_TO, and it can still silently miss events on queue overflow, so
// a poll backstop is needed regardless.
//
// Deliberately hashing the content rather than comparing size/mtime/inode:
// every metadata scheme leaves a residue. An in-place write with an
// identical size and a restored mtime (rsync --inplace -t, or any tool that
// preserves timestamps) changes none of the three and would be missed
// forever. Hashing also avoids rebuilding the list when a publisher rewrites
// the file unconditionally with identical content.
ReloadInterval caddy.Duration `json:"reload_interval,omitempty"`
// contains filtered or unexported fields
}
Permission decides whether a certificate may be obtained or loaded for a hostname, from an allow-list file held in memory.
func (*Permission) CaddyModule ¶
func (*Permission) CaddyModule() caddy.ModuleInfo
CaddyModule returns the Caddy module information.
func (*Permission) CertificateAllowed ¶
func (p *Permission) CertificateAllowed(ctx context.Context, name string) error
CertificateAllowed implements caddytls.OnDemandPermission.
Note the two kinds of refusal. A name that is simply not on the list is an ordinary denial, wrapped in ErrPermissionDenied, which Caddy logs at debug -- so the constant background of requests for names you do not host produces no noise. Having no list at all is NOT a denial: it is a failure of this module, and is returned as a plain error, which Caddy logs at ERROR. Failing closed stays loud. An HTTP endpoint cannot make this distinction: there, every response including 500 and 503 is treated as a denial and logged at debug.
func (*Permission) Cleanup ¶
func (p *Permission) Cleanup() error
Cleanup stops the watch and withdraws this instance from the admin route.
func (*Permission) Provision ¶
func (p *Permission) Provision(ctx caddy.Context) error
Provision validates the config, loads the list and starts the watch.
func (*Permission) UnmarshalCaddyfile ¶
func (p *Permission) UnmarshalCaddyfile(d *caddyfile.Dispenser) error
UnmarshalCaddyfile parses:
permission allowlist {
source /path/to/allowlist.txt
snapshot true
on_empty deny|storage|allow
reload_interval 2s
}
type State ¶
type State struct {
// From is "primary", "stale", "snapshot" or "none". Anything but "primary"
// means decisions are being made from stale or absent data.
//
// "stale" is the last good list read from the source, still being served
// while the source itself has become unreadable or unusable for more than
// a single poll.
From string `json:"from"`
Path string `json:"path,omitempty"`
Entries int `json:"entries"`
LoadedAt int64 `json:"loaded_at,omitempty"`
// Error is why the most recent load of the source failed, if it did. It is
// kept even while serving happily from a snapshot.
Error string `json:"error,omitempty"`
// OnEmpty is echoed back so monitoring can tell whether this instance would
// fail open or closed without reading the config.
OnEmpty string `json:"on_empty,omitempty"`
}
State is the module's externally visible health, served by the admin route.