Documentation
¶
Overview ¶
Package autoexclude is the adaptive decryption-exclusion cache: a bounded, TTL-bounded, in-memory learned set of hosts that could not be SSL-inspected, so that subsequent CONNECTs to them can fail open (bypass decryption) instead of breaking. It is the ADAPTIVE half of decryption exclusion; the MANUAL half (operator-authored bypass patterns) lives in internal/sslbypass.
This is PAN-OS's "local SSL decryption exclusion cache" modeled for a multi-tenant forward proxy. Four design choices make it safe to auto-disable inspection for a host based on a runtime signal:
SCOPED KEY. Every entry is keyed by (scope, host), where scope is an explicit policy boundary — the matched decryption profile's identity. A host learned under one fail-open profile is consulted ONLY for sessions matched to that same profile, so one fail-open rule/profile/tenant can never create a bypass consumed by another rule targeting the same host. Host-only keying is NOT policy isolation; the scope is.
CONFIRM-COUNT over distinct CLIENT-EVIDENCE tokens. A host is not excluded on the first failure. The cache holds a PENDING observation per (scope, host, reason) accumulating the distinct client-evidence tokens (authenticated identity when available, else client address — the caller decides; the engine treats the token opaquely) that hit a qualifying failure within a rolling window; only when the count reaches confirmN is the host promoted. A single endpoint therefore cannot self-poison.
The CALLER gates BOTH the learn (Observe) and the read (Contains) on the matched rule's fail-open opt-in. This engine stores and answers; it never decides policy. Critical origins kept on fail-close rules are never learned or consulted, so they are un-poisonable by design.
VOLATILE. In-memory only, never persisted, never synced CP->DP, off every config surface. A restart re-learns cheaply. (Per-node exclusions match PAN-OS's per-firewall local cache.)
Concurrency: an RWMutex guards both maps. Contains (the per-CONNECT hot read, fail-open rules only) takes the READ lock and bumps the per-entry hit counter ATOMICALLY, so concurrent reads on different hosts run fully parallel; writers (Observe/Remove/Clear/evict) take the write lock, which excludes all readers so an entry is never mutated or deleted while a reader holds it.
Index ¶
- Constants
- type Cache
- func (c *Cache) Clear() int
- func (c *Cache) Contains(scopeID, host string) (Reason, bool)
- func (c *Cache) Len() int
- func (c *Cache) List() []Entry
- func (c *Cache) Observe(scopeID, scopeName, host string, reason Reason, client string) (promoted bool)
- func (c *Cache) PendingLen() int
- func (c *Cache) Remove(scopeID, host string) bool
- func (c *Cache) Stats() Stats
- type Config
- type Entry
- type Reason
- type Stats
Constants ¶
const ( DefaultTTL = 12 * time.Hour DefaultPinnedTTL = 1 * time.Hour DefaultConfirmN = 2 DefaultWindow = 10 * time.Minute DefaultMaxEntries = 4096 )
Defaults. TTL matches the PAN-OS local-cache default (12h) for the server-observed reasons; PinnedTTL is shorter because the client signal is the spoofable class. ConfirmN=2 distinct client-evidence tokens blocks single-endpoint poisoning while letting a real fleet-wide incompatibility promote quickly. Window bounds how long partial observations accumulate.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Cache ¶
type Cache struct {
// contains filtered or unexported fields
}
Cache is the learned-exclusion store. The zero value is NOT ready; use New.
func (*Cache) Clear ¶
Clear evicts every active exclusion and pending observation. Returns the number of active entries removed.
func (*Cache) Contains ¶
Contains reports whether (scopeID, host) is actively excluded, returning the learn reason. On a hit it increments that entry's blast-radius hit counter. Expired entries read as absent (lazy — physical removal happens in evict/List).
func (*Cache) List ¶
List returns a stable, expired-filtered snapshot of active exclusions, sorted by learnedAt (newest first) for a predictable UI ordering.
func (*Cache) Observe ¶
func (c *Cache) Observe(scopeID, scopeName, host string, reason Reason, client string) (promoted bool)
Observe records a qualifying inspect failure for (scopeID, host) under reason, attributing it to the opaque distinct-evidence token `client` (the caller derives it — authenticated identity preferred, else client address). It reports whether this observation PROMOTED the (scope, host) to an active exclusion (promoted=true is the security-relevant "inspection just went dark" event — the caller fires the audit/alert/metric on it). If already actively excluded, it is a no-op returning false. An empty scopeID or host is ignored (fail-safe).
func (*Cache) PendingLen ¶
PendingLen is the current count of in-progress (unconfirmed) observations.
type Config ¶
type Config struct {
TTL time.Duration
PinnedTTL time.Duration
ConfirmN int
Window time.Duration
MaxEntries int
// Now is injectable for deterministic tests; nil ⇒ time.Now.
Now func() time.Time
}
Config parameterizes a Cache. Zero fields fall back to the Default* constants.
type Entry ¶
type Entry struct {
ScopeID string `json:"scope_id"` // decryption-profile identity that owns this exclusion
ScopeName string `json:"scope_name"` // human-readable scope (profile name) for the UI/audit
Host string `json:"host"`
Reason Reason `json:"reason"`
LearnedAt time.Time `json:"learned_at"`
ExpiresAt time.Time `json:"expires_at"`
// Hits counts sessions that bypassed inspection because of this entry — the
// blast-radius signal a security team triages against.
Hits int64 `json:"hits"`
// ClientCount is how many distinct client-evidence tokens were observed
// failing before promotion (provenance for the learn decision).
ClientCount int `json:"client_count"`
}
Entry is one active exclusion (a (scope, host) inspection is currently OFF for).
type Reason ¶
type Reason string
Reason classifies WHY a host was learned. The set is bounded (safe as a metric label). Only these are ever learned; an untrusted/expired origin cert and every generic/ambiguous origin-controlled TLS alert are dropped by the caller's classifier, because auto-bypassing them is an exfil vector, not a compat fix.
const ( // ReasonClientCertRequired is the origin-demanded-a-client-certificate reason: // the origin sent a CertificateRequest we cannot satisfy (a specific, // structured TLS signal — the one origin-leg reason allowed to live-rescue). ReasonClientCertRequired Reason = "client_cert_required" // ReasonUnsupportedParams is a genuine TLS-parameter incompatibility detected // LOCALLY by our own stack (no supported version overlap / no cipher overlap). // Learn-only: it enters pending learning but never live-rescues the triggering // session (it is lower-confidence than client-cert-required). ReasonUnsupportedParams Reason = "unsupported_params" // ReasonClientPinned is the client-rejected-our-forged-leaf reason (a pinned // app). Spoofable from the client side, so it is the reason most reliant on // the confirm-count and gets the shorter TTL. Learn-only. ReasonClientPinned Reason = "client_pinned" )
type Stats ¶
type Stats struct {
Active int `json:"active"`
Pending int `json:"pending"`
ConfirmN int `json:"confirm_n"`
TTLSecs int `json:"ttl_secs"`
PinnedSecs int `json:"pinned_ttl_secs"`
WindowSecs int `json:"window_secs"`
MaxEntries int `json:"max_entries"`
}
Stats reports the cache posture for the read-only governance/API surface, so an operator can prove the feature's configuration (and that a no-fail-open deployment has an inert, empty cache).