Documentation
¶
Overview ¶
Package pac is the PAC (proxy auto-config) engine: the persisted PAC configuration store and the FindProxyForURL generator. Extracted from package main per ADR-0002; the HTTP handlers, route registration, and the pacStore singleton stay in main (pac.go shim). The former pacDefaultProxyPort package global is now a Store field (SetDefaultPort), set once by the startup slice.
Index ¶
- Constants
- func PoolDirectives(pool Pool) string
- func ValidIdentifier(id string) bool
- func ValidateConfig(c Config) (Normalized, []ValidationIssue)
- type Artifact
- type ArtifactCache
- type Config
- type DestinationImpact
- type DirectBypassKind
- type DirectEntry
- type DirectInventory
- type DirectInventoryDiff
- type DirectPathDelta
- type ExceptionRecord
- type ExceptionState
- type ExceptionStore
- func (s *ExceptionStore) All() map[string]ExceptionRecord
- func (s *ExceptionStore) Delete(id string) error
- func (s *ExceptionStore) Get(id string) (ExceptionRecord, bool)
- func (s *ExceptionStore) Load(path string) error
- func (s *ExceptionStore) Put(rec ExceptionRecord) error
- func (s *ExceptionStore) Restore(st ExceptionState)
- func (s *ExceptionStore) Snapshot() ExceptionState
- type ExclusionKind
- type ImpactCategory
- type ImpactReport
- type LifecycleState
- type LifecycleStore
- func (s *LifecycleStore) All() map[string]*ProfileLifecycle
- func (s *LifecycleStore) Delete(id string) error
- func (s *LifecycleStore) Get(id string) (*ProfileLifecycle, bool)
- func (s *LifecycleStore) Load(path string) error
- func (s *LifecycleStore) Put(lc *ProfileLifecycle) error
- func (s *LifecycleStore) Restore(st LifecycleState)
- func (s *LifecycleStore) Snapshot() LifecycleState
- type Normalized
- type NormalizedExclusion
- type Pool
- type PoolEndpoint
- type Profile
- type ProfileDiff
- type ProfileDirectInventory
- type ProfileLifecycle
- func (lc *ProfileLifecycle) ActiveRevision() (PublishedRevision, bool)
- func (lc *ProfileLifecycle) PreviousRevision() (PublishedRevision, bool)
- func (lc *ProfileLifecycle) Publish(draft Profile, digest, author, reason, ts string) int64
- func (lc *ProfileLifecycle) Rollback(targetN int64, author, ts string) (int64, bool)
- func (lc *ProfileLifecycle) TouchDraft(draft Profile)
- type ProfileState
- type ProfileStore
- func (s *ProfileStore) Get() ProfilesConfig
- func (s *ProfileStore) Load(path string) error
- func (s *ProfileStore) ModTime() time.Time
- func (s *ProfileStore) PoolByID(id string) (Pool, bool)
- func (s *ProfileStore) PoolMap() map[string]Pool
- func (s *ProfileStore) ProfileByID(id string) (Profile, bool)
- func (s *ProfileStore) Restore(st ProfileState)
- func (s *ProfileStore) Set(cfg ProfilesConfig) error
- func (s *ProfileStore) Snapshot() ProfileState
- type ProfilesConfig
- type PublishCheck
- type PublishGuardCode
- type PublishedRevision
- type Rule
- type SimInput
- type SimMatchedRule
- type SimResult
- type State
- type Store
- func (s *Store) Compile(proxyAddr string) Artifact
- func (s *Store) DefaultPort() int
- func (s *Store) GeneratePAC(proxyAddr string) string
- func (s *Store) Get() Config
- func (s *Store) Load(path string) error
- func (s *Store) LoadMigrate(path, legacyPath string) (migrated bool, err error)
- func (s *Store) ModTime() time.Time
- func (s *Store) Restore(st State)
- func (s *Store) Set(c Config) error
- func (s *Store) SetDefaultPort(port int)
- func (s *Store) Snapshot() State
- type ValidationIssue
Constants ¶
const ( // GovUngoverned: a DIRECT-capable profile with no owner or no reason — an // unowned/unjustified bypass. GovUngoverned = "ungoverned" // GovExpired: the exception's expiry is in the past — it should be renewed // or removed. GovExpired = "expired" // GovReviewDue: past the review cadence since the last review (or never // reviewed) — due for re-attestation. GovReviewDue = "review_due" // GovGoverned: owned, justified, not expired, and review-current. GovGoverned = "governed" )
Governance status values (stable API strings). Empty means "not applicable" (the profile cannot emit DIRECT, so it needs no exception governance).
const ( ImpactUnchanged = "unchanged" ImpactPoolChanged = "pool_changed" ImpactBecameDirect = "became_direct" ImpactNoLongerDirect = "no_longer_direct" ImpactLostProxy = "lost_proxy_path" ImpactUndetermined = "undetermined_dns" )
Impact categories.
const ( // ChangeProfileGainedDirect: a profile that is newly DIRECT-capable. ChangeProfileGainedDirect = "profile_gained_direct" // ChangeProfileLostDirect: a profile that is no longer DIRECT-capable. ChangeProfileLostDirect = "profile_lost_direct" // ChangePathAdded: a new DIRECT source on an existing profile. ChangePathAdded = "path_added" // ChangePathRemoved: a DIRECT source removed from an existing profile. ChangePathRemoved = "path_removed" // ChangeProfileServed: an already-DIRECT-capable profile went from disabled // (404) to enabled, so its bypass PAC is now REACHABLE by clients even though // its DIRECT paths are unchanged — a newly-live bypass surface. ChangeProfileServed = "profile_served" // ChangeProfileUnserved: a DIRECT-capable profile was disabled, taking its // bypass offline (404). Risk-reducing. ChangeProfileUnserved = "profile_unserved" )
Change kinds (stable API strings) for one DIRECT-surface delta.
const ( // ModeSecure: pool chain only — NO DIRECT anywhere (DIRECT rules and the // private-network bypass are rejected too). If every proxy is down, // traffic fails closed. NOTE: the legacy /proxy.pac output corresponds // to BALANCED + PrivateDirect (its exclusions are explicit DIRECT // carve-outs), not to secure. ModeSecure = "secure" // ModeBalanced: pool chain terminal (no DIRECT), but explicit DIRECT // rules are permitted where the admin authored them. ModeBalanced = "balanced" // ModeAvailability: pool chain then DIRECT — fail open when all proxies // are unreachable. ModeAvailability = "availability" )
Availability modes: what the terminal directive chain may contain.
const ( // PrivateDirect: loopback + RFC-1918 destinations go DIRECT (legacy). PrivateDirect = "direct" // PrivateProxy: private ranges get no built-in bypass — they follow the // profile's rules and terminal chain like any other destination. PrivateProxy = "proxy" )
Private-network behaviors (replaces the legacy hardcoded RFC1918 bypass).
const ( RuleKindDomain = "domain" // exact host + subdomains RuleKindSuffix = "suffix" // subdomains only (dnsDomainIs) RuleKindWildcard = "wildcard" // shExpMatch host glob RuleKindCIDR4 = "cidr4" // IPv4 CIDR against resolved IP )
Rule kinds.
const ( ActionUsePool = "use_pool" ActionDirect = "direct" )
Rule actions.
const ( MaxProfiles = 64 MaxPools = 64 MaxRulesPerProfile = 1000 MaxPoolEndpoints = 3 )
Engine caps for profiles/pools (mirrored by the cluster snapshot caps).
const ( GuardValidationFailed = "validation_failed" GuardNoProxyRoute = "no_proxy_route" GuardSecureDirect = "secure_mode_direct" GuardMissingPool = "missing_pool" GuardCompileFailed = "compile_failed" GuardDigestFailed = "digest_failed" GuardNewDirectPaths = "new_direct_paths" // requires typed confirmation )
Publish guard codes (stable API strings).
const ( // OutcomeMatched: a rule (or the terminal) decided the directive. OutcomeMatched = "matched" // OutcomeUndeterminedDNS: the deciding rule needs the host's IP and no // resolved IP was supplied — the real answer depends on client DNS. OutcomeUndeterminedDNS = "undetermined_dns" )
Simulate outcomes for the matched decision.
const ( MaxExclusionEntries = 10000 // MaxEntryLen matches the DNS name length bound (RFC 1035). MaxEntryLen = 253 // MaxArtifactBytes is the hard compiled-output budget. Chromium rejects // PAC scripts over 1 MiB at fetch time, so a config that compiles past // that is undeliverable and must be rejected, not served. MaxArtifactBytes = 1 << 20 // WarnArtifactBytes is the advisory compiled-output budget. WarnArtifactBytes = 512 << 10 )
Validation limits. MaxExclusionEntries mirrors the cluster snapshot cap (maxSnapPACExclusions in controlplane_snapshot.go) so a config that validates here can always ride a ConfigSnapshot.
const ( IssueEmptyEntry = "empty_entry" IssueControlChars = "control_chars" IssueEntryTooLong = "entry_too_long" IssueTooManyEntries = "too_many_exclusions" IssueInvalidCIDR = "invalid_cidr" IssueInvalidWildcard = "invalid_wildcard" IssueInvalidHost = "invalid_host" IssueInvalidPort = "invalid_port" IssueInvalidProxyHost = "invalid_proxy_host" IssueDuplicateEntry = "duplicate_entry" IssueHostFallback = "proxy_host_fallback" IssueCIDRNormalized = "cidr_normalized" IssueOutputTooLarge = "output_too_large" IssueOutputLarge = "output_large" )
ValidationIssue codes. Stable strings — they are part of the admin API response shape.
const ( IssueInvalidID = "invalid_id" IssueDuplicateID = "duplicate_id" IssueReservedID = "reserved_id" IssueUnknownPool = "unknown_pool" IssueInvalidEndpoint = "invalid_endpoint" IssueTooManyEndpoints = "too_many_endpoints" IssueNoEndpoints = "no_endpoints" IssueInvalidRule = "invalid_rule" IssueInvalidMode = "invalid_mode" IssueTooManyProfiles = "too_many_profiles" IssueTooManyPools = "too_many_pools" IssueTooManyRules = "too_many_rules" IssueSecureModeConflict = "secure_mode_conflict" )
Profile/pool validation issue codes (stable API strings).
const CompilerVersion = "1"
CompilerVersion identifies the PAC generator's output contract. Bump on any change that alters generated bytes for an unchanged config.
const DefaultProfileID = "default"
DefaultProfileID names the virtual legacy-backed profile.
Variables ¶
This section is empty.
Functions ¶
func PoolDirectives ¶ added in v1.0.125
PoolDirectives renders a pool's ordered failover chain, e.g. "PROXY a:8080; PROXY b:8080". Empty pool renders "" (caller handles).
func ValidIdentifier ¶ added in v1.0.125
ValidIdentifier reports whether id is a valid URL-safe profile/pool ID.
func ValidateConfig ¶ added in v1.0.122
func ValidateConfig(c Config) (Normalized, []ValidationIssue)
ValidateConfig strictly validates and normalizes c. The returned issue list is non-empty exactly when the config must be rejected; warnings that do not reject (dedupe, host-fallback advisory) are on the returned Normalized.
Types ¶
type Artifact ¶ added in v1.0.122
type Artifact struct {
// JS is the generated FindProxyForURL script.
JS string
// Digest is the SHA-256 hex of JS — the HTTP ETag source. It covers the
// ACTUAL bytes served, including a request-Host-derived proxy host when
// the fallback is active.
Digest string
// Fingerprint is the SHA-256 hex of the canonical normalized config,
// independent of the request-derived fallback host.
Fingerprint string
// CompilerVersion is the generator contract version.
CompilerVersion string
// GeneratedAt is when this artifact was built (metadata only; never
// hashed, so it cannot break determinism).
GeneratedAt time.Time
// Warnings carries normalization warnings (dropped legacy junk, dedupes,
// host-fallback advisory).
Warnings []ValidationIssue
// ProxyChain is the effective directive chain, e.g. ["PROXY p:8080"] or
// ["DIRECT"] in the degenerate no-host case.
ProxyChain []string
// HostFallback is true when the proxy hostname was derived from the
// request's Host header rather than configuration. Fallback-mode
// responses vary per request host and must not be shared-cached.
HostFallback bool
}
Artifact is one compiled PAC file plus its provenance metadata. JS, Fingerprint, Digest, and CompilerVersion are deterministic for a given (config, fallback host) input; GeneratedAt and Warnings are operational metadata excluded from both hashes.
func CompileConfig ¶ added in v1.0.122
CompileConfig validates (leniently) and compiles c. fallbackAddr is the request-derived "host[:port]" used when c.ProxyHost is empty; defaultPort is the startup-resolved proxy listener port used when c.ProxyPort is zero.
func CompileProfile ¶ added in v1.0.125
CompileProfile compiles one custom profile against its pools. The returned artifact is deterministic for identical inputs. Unresolvable pieces (unknown pool refs, junk patterns) are DROPPED with warnings — tolerant, mirroring NormalizeLenient — so replayed/synced configs always compile; strict validation guards the API boundary instead.
type ArtifactCache ¶ added in v1.0.125
type ArtifactCache struct {
// contains filtered or unexported fields
}
ArtifactCache memoizes compiled PAC artifacts keyed on the owning store's ModTime. The zero value is a ready, empty cache.
func (*ArtifactCache) Legacy ¶ added in v1.0.125
func (c *ArtifactCache) Legacy(s *Store, fallbackAddr string) Artifact
Legacy returns the compiled default-profile artifact, recompiling only when the store changed. Fallback-mode artifacts (HostFallback) are never cached (they embed the per-request host); the caller compiles those directly.
func (*ArtifactCache) Profile ¶ added in v1.0.125
func (c *ArtifactCache) Profile(s *ProfileStore, p Profile) Artifact
Profile returns the compiled artifact for one profile, recompiling the whole profile map only when the profile store changed. A store mutation invalidates all cached profiles at once (coarse but correct; profile mutations are admin-rate).
type Config ¶
type Config struct {
// ProxyHost is the hostname or IP of this proxy, e.g. "proxy.corp.com".
// If empty the /proxy.pac endpoint uses the hostname from the request's
// Host header (stripping the port, which belongs to the UI — not the proxy).
ProxyHost string `json:"proxyHost"`
// ProxyPort is the port the proxy server listens on.
// If zero it falls back to the runtime proxy port set at startup.
ProxyPort int `json:"proxyPort"`
// Exclusions is the list of host patterns that should bypass the proxy.
// Supports bare domains ("corp.local"), wildcard prefixes ("*.corp.local"),
// and IP CIDR ranges ("192.168.0.0/16").
Exclusions []string `json:"exclusions"`
}
Config is the persisted PAC configuration.
type DestinationImpact ¶ added in v1.0.125
type DestinationImpact struct {
Host string `json:"host"`
Category ImpactCategory `json:"category"`
OldDirective string `json:"oldDirective"`
NewDirective string `json:"newDirective"`
}
DestinationImpact is one sampled destination's movement.
type DirectBypassKind ¶ added in v1.0.134
type DirectBypassKind = string
DirectBypassKind identifies WHY a profile can emit DIRECT.
const ( // BypassRule is an explicit ActionDirect routing rule. BypassRule DirectBypassKind = "direct_rule" // BypassAvailability is availability mode appending DIRECT to the terminal // chain (fail-open when all proxies are unreachable). BypassAvailability DirectBypassKind = "availability_mode" // BypassPrivate is private-networks=direct sending all RFC-1918/loopback // destinations DIRECT. BypassPrivate DirectBypassKind = "private_networks" // BypassPlainHost is the unconditional dotless-intranet-hostname DIRECT // guard the compiler emits in EVERY profile (including secure mode). Every // PAC profile therefore bypasses for plain single-label hostnames. (The // profile compiler additionally excludes IPv6 literals; the legacy default // compiler does not — so the detail text does not promise IPv6 exclusion.) BypassPlainHost DirectBypassKind = "plain_host" // BypassFailOpen is the legacy default PAC failing OPEN to DIRECT for ALL // traffic when no proxy host is configured (and the fetching client // supplies no resolvable Host). It applies only to the synthesized legacy // default profile and is injected by the root inventory builder, since the // static profile model cannot otherwise express the request-dependent // terminal of the legacy compiler. BypassFailOpen DirectBypassKind = "fail_open" )
DIRECT-bypass sources (stable API strings).
type DirectEntry ¶ added in v1.0.134
type DirectEntry struct {
Kind DirectBypassKind `json:"kind"`
Detail string `json:"detail"`
RuleIndex int `json:"ruleIndex,omitempty"` // 1-based, for BypassRule
// RuleKind is the routing-rule kind (domain/suffix/wildcard/cidr4) for a
// BypassRule entry — distinct from Kind (always "direct_rule" for rules).
// Carried structurally so the change-diff can tell a same-pattern kind flip
// (e.g. suffix→domain, which broadens subdomains-only to apex+subdomains)
// apart, which Kind alone cannot.
RuleKind string `json:"ruleKind,omitempty"`
Pattern string `json:"pattern,omitempty"`
Scheme string `json:"scheme,omitempty"`
Port int `json:"port,omitempty"`
// Broad flags a wide-reaching bypass (wildcard, broad IPv4 CIDR, or an
// all-destinations mode/private bypass) — a config-only heuristic, not an
// observation of scope.
Broad bool `json:"broad,omitempty"`
}
DirectEntry is one config-derived path by which a profile fully bypasses Culvert. EvidenceClass is always config-Observable.
type DirectInventory ¶ added in v1.0.134
type DirectInventory struct {
// EvidenceClass is always "config" — this is Observable configuration
// state, never observed DIRECT usage (which the proxy cannot see).
EvidenceClass string `json:"evidenceClass"`
Profiles []ProfileDirectInventory `json:"profiles"`
TotalProfiles int `json:"totalProfiles"`
DirectCapableProfiles int `json:"directCapableProfiles"`
// ServingDirectProfiles counts enabled profiles that can emit DIRECT —
// the ones whose bypass is reachable by clients right now.
ServingDirectProfiles int `json:"servingDirectProfiles"`
TotalDirectPaths int `json:"totalDirectPaths"`
// BroadDirectPaths counts wide-reaching DIRECT paths (wildcards, broad
// CIDRs, all-destination bypasses) — the "high-risk wildcard/broad CIDR"
// signal, computed from config.
BroadDirectPaths int `json:"broadDirectPaths"`
}
DirectInventory is the fleet-wide, config-derived inventory of every PAC full-security-path bypass. Counts describe configuration, not traffic.
func BuildDirectInventory ¶ added in v1.0.134
func BuildDirectInventory(cfg ProfilesConfig) DirectInventory
BuildDirectInventory enumerates the DIRECT footprint of every profile in cfg. Secure-mode profiles are reported not-DIRECT-capable: the compiler neutralizes DIRECT (rules, private bypass, and terminal) in secure mode, so no bypass is reachable regardless of what the spec lists.
type DirectInventoryDiff ¶ added in v1.0.134
type DirectInventoryDiff struct {
// EvidenceClass is always "config" — Observable configuration delta, never
// observed usage.
EvidenceClass string `json:"evidenceClass"`
Deltas []DirectPathDelta `json:"deltas"`
ProfilesGainedDirect int `json:"profilesGainedDirect"`
ProfilesLostDirect int `json:"profilesLostDirect"`
PathsAdded int `json:"pathsAdded"`
PathsRemoved int `json:"pathsRemoved"`
// BroadPathsAdded counts added DIRECT paths flagged broad (wildcard, broad
// CIDR, or an all-destination mode/private/fail-open bypass) — the highest-
// signal risk-increasing changes.
BroadPathsAdded int `json:"broadPathsAdded"`
// RiskIncreased is true when the change adds any DIRECT surface (a gained
// profile or an added path). A pure narrowing/removal leaves it false.
RiskIncreased bool `json:"riskIncreased"`
}
DirectInventoryDiff is the full before→after change to the DIRECT surface.
func DiffDirectInventory ¶ added in v1.0.134
func DiffDirectInventory(before, after DirectInventory) DirectInventoryDiff
DiffDirectInventory computes the before→after DIRECT-surface change. Pure and deterministic (deltas are emitted in a stable order).
type DirectPathDelta ¶ added in v1.0.134
type DirectPathDelta struct {
ProfileID string `json:"profileId"`
Name string `json:"name"`
Change string `json:"change"`
Kind DirectBypassKind `json:"kind,omitempty"`
Detail string `json:"detail,omitempty"`
Pattern string `json:"pattern,omitempty"`
Broad bool `json:"broad,omitempty"`
// RiskIncreasing is true when the change widens the full-bypass surface (a
// newly DIRECT-capable profile, or an added DIRECT path). Removals are not
// risk-increasing. A broadened rule shows as a removed narrow path + an
// added broad path, so the add carries the risk signal.
RiskIncreasing bool `json:"riskIncreasing"`
}
DirectPathDelta is one change to the DIRECT bypass surface.
type ExceptionRecord ¶ added in v1.0.134
type ExceptionRecord struct {
ProfileID string `json:"profileId"`
Owner string `json:"owner,omitempty"`
Reason string `json:"reason,omitempty"`
BusinessApp string `json:"businessApp,omitempty"`
Ticket string `json:"ticket,omitempty"`
CreatedBy string `json:"createdBy,omitempty"`
CreatedAt string `json:"createdAt,omitempty"`
UpdatedAt string `json:"updatedAt,omitempty"`
ExpiresAt string `json:"expiresAt,omitempty"` // optional
ReviewCadenceDays int `json:"reviewCadenceDays,omitempty"` // 0 = no cadence
LastReviewedAt string `json:"lastReviewedAt,omitempty"`
}
ExceptionRecord is the governance metadata for one profile's DIRECT exposure. All timestamps are RFC3339 UTC strings; the engine takes no wall clock, so status is evaluated against a caller-supplied now.
func (ExceptionRecord) Status ¶ added in v1.0.134
func (e ExceptionRecord) Status(now time.Time, directCapable bool) string
Status classifies the governance posture of a DIRECT-capable profile at time now. directCapable=false returns "" (governance is not applicable). A malformed ExpiresAt is treated as expired (fail-safe — an unparseable deadline must not read as "still valid").
type ExceptionState ¶ added in v1.0.134
type ExceptionState struct {
ByID map[string]ExceptionRecord
Path string
ModTime time.Time
}
ExceptionState is a full ExceptionStore snapshot for test isolation.
type ExceptionStore ¶ added in v1.0.134
type ExceptionStore struct {
// contains filtered or unexported fields
}
ExceptionStore persists ExceptionRecords keyed by profile ID. The zero value is a ready, empty store; mutation is race-safe.
func (*ExceptionStore) All ¶ added in v1.0.134
func (s *ExceptionStore) All() map[string]ExceptionRecord
All returns a copy of every record.
func (*ExceptionStore) Delete ¶ added in v1.0.134
func (s *ExceptionStore) Delete(id string) error
Delete removes a profile's record (e.g. when the profile is deleted) and persists. Missing is a no-op.
func (*ExceptionStore) Get ¶ added in v1.0.134
func (s *ExceptionStore) Get(id string) (ExceptionRecord, bool)
Get returns the record for id and whether it existed.
func (*ExceptionStore) Load ¶ added in v1.0.134
func (s *ExceptionStore) Load(path string) error
Load reads the store from path; a missing file is a no-op. A corrupt file is quarantined and the store starts empty (node-local metadata must never brick startup).
func (*ExceptionStore) Put ¶ added in v1.0.134
func (s *ExceptionStore) Put(rec ExceptionRecord) error
Put stores rec under its ProfileID and persists.
func (*ExceptionStore) Restore ¶ added in v1.0.134
func (s *ExceptionStore) Restore(st ExceptionState)
Restore resets the store to a captured state (test support).
func (*ExceptionStore) Snapshot ¶ added in v1.0.134
func (s *ExceptionStore) Snapshot() ExceptionState
Snapshot returns the store state for -shuffle test hermeticity.
type ExclusionKind ¶ added in v1.0.122
type ExclusionKind int
ExclusionKind classifies a normalized exclusion entry.
const ( // KindDomain matches the exact host and all subdomains (legacy bare-domain // semantics: host === "x" || dnsDomainIs(host, ".x")). KindDomain ExclusionKind = iota // KindWildcard matches subdomains only ("*.x" → dnsDomainIs(host, ".x")). KindWildcard // KindHostLiteral matches one IP literal exactly (host === "x"). KindHostLiteral // KindCIDR matches when the resolved IP falls in an IPv4 network. KindCIDR )
Exclusion kinds, in compiler emission group order.
type ImpactCategory ¶ added in v1.0.125
type ImpactCategory = string
ImpactCategory classifies how one destination moves between revisions.
type ImpactReport ¶ added in v1.0.125
type ImpactReport struct {
// Source names where the sample came from ("observed" | "test_vectors").
Source string `json:"source"`
// Sampled is how many destinations were evaluated.
Sampled int `json:"sampled"`
// Counts is the per-category tally.
Counts map[string]int `json:"counts"`
// Movements lists the destinations that changed (unchanged omitted).
Movements []DestinationImpact `json:"movements"`
// UnreachableRules/ShadowedRules/DuplicateRules are static analysis of
// the candidate's rule list, independent of the sample.
UnreachableRules []string `json:"unreachableRules,omitempty"`
ShadowedRules []string `json:"shadowedRules,omitempty"`
DuplicateRules []string `json:"duplicateRules,omitempty"`
// Notes carries caveats (e.g. CIDR outcomes undetermined without IPs).
Notes []string `json:"notes,omitempty"`
}
ImpactReport summarizes a candidate publish against the active revision over a sample of destinations.
func AnalyzeImpact ¶ added in v1.0.125
func AnalyzeImpact(active Profile, hasActive bool, candidate Profile, pools map[string]Pool, sample []string, sampleSource string) ImpactReport
AnalyzeImpact replays sample hosts through active vs candidate and categorizes movement. pools is the current pool map. sampleSource labels the origin. Hosts are plain hostnames (no resolved IPs), so cidr4/private outcomes are reported as undetermined rather than guessed.
type LifecycleState ¶ added in v1.0.125
type LifecycleState struct {
ByID map[string]*ProfileLifecycle
Path string
ModTime time.Time
}
LifecycleState is a full LifecycleStore snapshot for test isolation.
type LifecycleStore ¶ added in v1.0.125
type LifecycleStore struct {
// contains filtered or unexported fields
}
LifecycleStore persists per-profile ProfileLifecycle records keyed by profile ID. Like the other stores, Set is tolerant; the zero value is a ready, empty store.
func (*LifecycleStore) All ¶ added in v1.0.125
func (s *LifecycleStore) All() map[string]*ProfileLifecycle
All returns deep copies of every lifecycle record (for the GET listing).
func (*LifecycleStore) Delete ¶ added in v1.0.125
func (s *LifecycleStore) Delete(id string) error
Delete removes a profile's lifecycle record (called when the profile is deleted) and persists.
func (*LifecycleStore) Get ¶ added in v1.0.125
func (s *LifecycleStore) Get(id string) (*ProfileLifecycle, bool)
Get returns a deep copy of the lifecycle for id (a fresh empty one when absent) and whether it existed.
func (*LifecycleStore) Load ¶ added in v1.0.125
func (s *LifecycleStore) Load(path string) error
Load reads the store from path; a missing file is a no-op.
func (*LifecycleStore) Put ¶ added in v1.0.125
func (s *LifecycleStore) Put(lc *ProfileLifecycle) error
Put stores lc (deep-copied) under its ProfileID and persists.
func (*LifecycleStore) Restore ¶ added in v1.0.125
func (s *LifecycleStore) Restore(st LifecycleState)
Restore resets the store to a captured state (test support).
func (*LifecycleStore) Snapshot ¶ added in v1.0.125
func (s *LifecycleStore) Snapshot() LifecycleState
Snapshot returns the store state for -shuffle test hermeticity (pair with Restore).
type Normalized ¶ added in v1.0.122
type Normalized struct {
// ProxyHost is the validated proxy hostname/IP, or "" when the request
// Host header fallback is in effect.
ProxyHost string
// ProxyPort is the configured port (0 = auto: startup default, then 8080).
ProxyPort int
// Exclusions preserves configured order with duplicates removed.
Exclusions []NormalizedExclusion
// Warnings collects non-fatal normalization notes (dropped entries in
// lenient mode, dedupes, host-fallback advisory).
Warnings []ValidationIssue
}
Normalized is the canonical, validated form of a Config. It is the single input shape the compiler consumes.
func NormalizeLenient ¶ added in v1.0.122
func NormalizeLenient(c Config) Normalized
NormalizeLenient normalizes c tolerantly: malformed entries are dropped and recorded as warnings, never errors. Replay-path safe.
type NormalizedExclusion ¶ added in v1.0.122
type NormalizedExclusion struct {
Kind ExclusionKind
// Host is the lowercased, IDNA-punycoded, trailing-dot-stripped hostname
// (KindDomain/KindWildcard, without the "*." prefix) or the IP literal
// text (KindHostLiteral).
Host string
// CIDRIP/CIDRMask/CIDRPrefix carry the parsed IPv4 network (KindCIDR).
// CIDRIP is the masked network base address.
CIDRIP string
CIDRMask string
CIDRPrefix int
// Raw is the entry as configured (trimmed), for error reporting.
Raw string
}
NormalizedExclusion is one exclusion entry in canonical form.
func (NormalizedExclusion) Canonical ¶ added in v1.0.122
func (e NormalizedExclusion) Canonical() string
Canonical returns the canonical config-file text for the entry — the form persisted after a strictly validated mutation.
type Pool ¶ added in v1.0.125
type Pool struct {
ID string `json:"id"`
Name string `json:"name"`
Endpoints []PoolEndpoint `json:"endpoints"`
}
Pool is an ordered proxy-failover chain (primary → secondary → tertiary).
type PoolEndpoint ¶ added in v1.0.125
PoolEndpoint is one ordered proxy target. Only PROXY directives are emitted (the WinHTTP portability floor).
type Profile ¶ added in v1.0.125
type Profile struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Enabled bool `json:"enabled"`
// PoolID names the profile's default pool (required).
PoolID string `json:"poolId"`
// Rules are evaluated in order before the terminal chain.
Rules []Rule `json:"rules,omitempty"`
// PrivateNetworks is PrivateDirect or PrivateProxy.
PrivateNetworks string `json:"privateNetworks"`
// AvailabilityMode is ModeSecure, ModeBalanced, or ModeAvailability.
AvailabilityMode string `json:"availabilityMode"`
// Revision increments on every mutation of this profile.
Revision int64 `json:"revision"`
}
Profile is one PAC steering profile, served at /pac/<id>.pac.
type ProfileDiff ¶ added in v1.0.125
type ProfileDiff struct {
RulesAdded []string `json:"rulesAdded,omitempty"`
RulesRemoved []string `json:"rulesRemoved,omitempty"`
RulesReordered bool `json:"rulesReordered"`
PoolChanged bool `json:"poolChanged"`
OldPool string `json:"oldPool,omitempty"`
NewPool string `json:"newPool,omitempty"`
AvailabilityChange string `json:"availabilityChange,omitempty"` // "old→new"
PrivateNetChange string `json:"privateNetChange,omitempty"`
NewDirectPaths []string `json:"newDirectPaths,omitempty"`
RemovedDirectPaths []string `json:"removedDirectPaths,omitempty"`
// SecuritySensitive is true when the change widens DIRECT exposure,
// weakens availability toward fail-open, or flips private-network to
// direct — the changes a reviewer must look at first.
SecuritySensitive bool `json:"securitySensitive"`
}
ProfileDiff is the structured change set between two profile revisions.
func DiffProfiles ¶ added in v1.0.125
func DiffProfiles(old Profile, hasOld bool, next Profile) ProfileDiff
DiffProfiles computes the change set from old to new. hasOld=false treats new as an initial publish (everything is "added").
type ProfileDirectInventory ¶ added in v1.0.134
type ProfileDirectInventory struct {
ProfileID string `json:"profileId"`
Name string `json:"name"`
// Serving is true when the profile is enabled and therefore served at
// /pac/<id>.pac (a disabled profile 404s and reaches no client).
Serving bool `json:"serving"`
AvailabilityMode string `json:"availabilityMode"`
DirectCapable bool `json:"directCapable"`
DirectPaths []DirectEntry `json:"directPaths,omitempty"`
}
ProfileDirectInventory summarizes one profile's DIRECT footprint.
type ProfileLifecycle ¶ added in v1.0.125
type ProfileLifecycle struct {
// ProfileID ties the lifecycle to its profile.
ProfileID string `json:"profileId"`
// Draft is the mutable working copy (edited by PUT). It becomes a
// published revision on Publish.
Draft Profile `json:"draft"`
// DraftDirty reports whether the draft diverges from the active revision.
DraftDirty bool `json:"draftDirty"`
// ActiveN is the currently-serving published revision number (0 = none
// published yet).
ActiveN int64 `json:"activeN"`
// Revisions is the append-only immutable history, oldest first.
Revisions []PublishedRevision `json:"revisions"`
}
ProfileLifecycle carries a profile's draft + immutable published history. The persisted pac_profiles.json ProfilesConfig continues to hold the ACTIVE profile spec (so serving and cluster sync are unchanged); this lifecycle metadata persists alongside it in pac_profiles_lifecycle.json.
func (*ProfileLifecycle) ActiveRevision ¶ added in v1.0.125
func (lc *ProfileLifecycle) ActiveRevision() (PublishedRevision, bool)
ActiveRevision returns the currently-serving revision and true, or false when nothing is published yet.
func (*ProfileLifecycle) PreviousRevision ¶ added in v1.0.125
func (lc *ProfileLifecycle) PreviousRevision() (PublishedRevision, bool)
PreviousRevision returns the revision published immediately before the active one (the rollback target) and true, or false when there is none.
func (*ProfileLifecycle) Publish ¶ added in v1.0.125
func (lc *ProfileLifecycle) Publish(draft Profile, digest, author, reason, ts string) int64
Publish appends draft as a new immutable revision, makes it active, and clears the dirty flag. The caller supplies the validated digest, author, reason, and timestamp (the engine takes no wall clock). Returns the new revision number.
func (*ProfileLifecycle) Rollback ¶ added in v1.0.125
func (lc *ProfileLifecycle) Rollback(targetN int64, author, ts string) (int64, bool)
Rollback re-activates a prior published revision by number. It does NOT rewrite history — it records a NEW revision that is a copy of the target spec (so revision numbers stay monotonic and the timeline shows the rollback), authored by the caller. Returns the new revision number and true, or false when targetN is not a published revision.
func (*ProfileLifecycle) TouchDraft ¶ added in v1.0.125
func (lc *ProfileLifecycle) TouchDraft(draft Profile)
TouchDraft records a draft edit (marks dirty). The caller stores the edited Draft separately; this just flags divergence from the active revision.
type ProfileState ¶ added in v1.0.125
type ProfileState struct {
Cfg ProfilesConfig
Path string
ModTime time.Time
}
ProfileState is a full ProfileStore snapshot for test isolation.
type ProfileStore ¶ added in v1.0.125
type ProfileStore struct {
// contains filtered or unexported fields
}
ProfileStore persists ProfilesConfig to a JSON file. Like the legacy Store, Set is TOLERANT (no validation) — replay callers (rollback, cluster apply, import apply) discard errors; strict validation lives at the admin API boundary (ValidateProfilesConfig).
func (*ProfileStore) Get ¶ added in v1.0.125
func (s *ProfileStore) Get() ProfilesConfig
Get returns a deep-enough snapshot of the current config (slices copied; nested slices copied per element).
func (*ProfileStore) Load ¶ added in v1.0.125
func (s *ProfileStore) Load(path string) error
Load reads config from the JSON file; a missing file is a no-op.
func (*ProfileStore) ModTime ¶ added in v1.0.125
func (s *ProfileStore) ModTime() time.Time
ModTime reports when the config last changed (zero before any load/set).
func (*ProfileStore) PoolByID ¶ added in v1.0.125
func (s *ProfileStore) PoolByID(id string) (Pool, bool)
PoolByID returns the pool and true when present.
func (*ProfileStore) PoolMap ¶ added in v1.0.125
func (s *ProfileStore) PoolMap() map[string]Pool
PoolMap returns pools keyed by ID (for the compiler/simulator).
func (*ProfileStore) ProfileByID ¶ added in v1.0.125
func (s *ProfileStore) ProfileByID(id string) (Profile, bool)
ProfileByID returns the profile and true when present (custom profiles only — the virtual default profile lives outside this store).
func (*ProfileStore) Restore ¶ added in v1.0.125
func (s *ProfileStore) Restore(st ProfileState)
Restore resets the store to a previously captured state (test support).
func (*ProfileStore) Set ¶ added in v1.0.125
func (s *ProfileStore) Set(cfg ProfilesConfig) error
Set replaces the config and persists it (tolerant — see type comment).
func (*ProfileStore) Snapshot ¶ added in v1.0.125
func (s *ProfileStore) Snapshot() ProfileState
Snapshot returns the store's full state (test support, -shuffle hermetic).
type ProfilesConfig ¶ added in v1.0.125
ProfilesConfig is the persisted shape of pac_profiles.json.
type PublishCheck ¶ added in v1.0.125
type PublishCheck struct {
OK bool `json:"ok"`
RequiresConfirmation bool `json:"requiresConfirmation"`
Issues []ValidationIssue `json:"issues,omitempty"`
// Digest is the compiled artifact digest of the candidate (empty when
// compilation/validation failed).
Digest string `json:"digest,omitempty"`
// NewDirectPaths lists rule descriptions whose publish would newly make
// DIRECT reachable that the active revision did not (drives the typed
// confirmation).
NewDirectPaths []string `json:"newDirectPaths,omitempty"`
}
PublishCheck is the result of evaluating the publish guardrails against a candidate draft. OK reports whether publish may proceed without operator confirmation; RequiresConfirmation is set when the only blocker is new-DIRECT-path introduction (high-friction confirm, not a hard failure).
func EvaluatePublish ¶ added in v1.0.125
func EvaluatePublish(draft Profile, pools map[string]Pool, activeSpec Profile, hasActive bool) PublishCheck
EvaluatePublish runs the safe-publish guardrails for draft against the active revision (activeSpec; ok=false when nothing is published yet). pools is the current pool map. It never mutates anything.
type PublishGuardCode ¶ added in v1.0.125
type PublishGuardCode = string
PublishGuardCode identifies why a publish was refused.
type PublishedRevision ¶ added in v1.0.125
type PublishedRevision struct {
// N is the monotonic revision number (1-based, never reused).
N int64 `json:"n"`
// Spec is the exact profile content published at revision N (immutable).
Spec Profile `json:"spec"`
// Digest is the SHA-256 of the compiled artifact at publish time — the
// convergence oracle and the "restore exact prior artifact" anchor.
Digest string `json:"digest"`
// Author is the admin identity that published this revision.
Author string `json:"author"`
// Reason is the operator-supplied change reason.
Reason string `json:"reason,omitempty"`
// TS is the publish timestamp (RFC3339); set by the caller (the engine
// takes no wall clock).
TS string `json:"ts"`
}
PublishedRevision is one immutable published snapshot of a profile.
type Rule ¶ added in v1.0.125
type Rule struct {
// Kind is one of RuleKind*.
Kind string `json:"kind"`
// Pattern is the kind-specific match target (domain, suffix, glob, CIDR).
Pattern string `json:"pattern"`
// Scheme optionally restricts the rule to "http" or "https" URLs.
Scheme string `json:"scheme,omitempty"`
// Port optionally restricts the rule to an explicit destination port as
// it appears in the URL (default ports are omitted by clients and cannot
// be matched — documented limitation).
Port int `json:"port,omitempty"`
// Action is ActionUsePool or ActionDirect.
Action string `json:"action"`
// PoolID optionally overrides the profile's pool for ActionUsePool.
PoolID string `json:"poolId,omitempty"`
}
Rule is one ordered routing rule. Order is admin-authored and order-sensitive (first match wins); the compiler honors it verbatim.
type SimInput ¶ added in v1.0.125
type SimInput struct {
// URL is the full request URL (scheme + authority; path is advisory —
// clients strip it for https). Optional if Host is set.
URL string `json:"url"`
// Host is the destination hostname or IP literal. Derived from URL when
// empty.
Host string `json:"host"`
// Scheme overrides the scheme parsed from URL (http/https).
Scheme string `json:"scheme"`
// Port is the destination port as it would appear in the URL authority
// (0 = none/default; default ports are unmatchable, as for real clients).
Port int `json:"port"`
// ResolvedIP optionally supplies the host's resolved IPv4 address so
// cidr4 / private-network rules can be evaluated deterministically. When
// empty, those rules yield OutcomeUndeterminedDNS.
ResolvedIP string `json:"resolvedIp"`
}
SimInput is one simulation query.
type SimMatchedRule ¶ added in v1.0.125
type SimMatchedRule struct {
// Index is the 0-based rule index, or -1 for a non-rule decision.
Index int `json:"index"`
// Kind is the rule kind, or a synthetic label: "plain-host",
// "private-network", "terminal".
Kind string `json:"kind"`
// Pattern is the rule pattern (empty for synthetic decisions).
Pattern string `json:"pattern,omitempty"`
// Action is the rule action or the synthetic decision's effect.
Action string `json:"action"`
}
SimMatchedRule identifies which rule fired (index into the profile's rule list, or a synthetic marker for built-ins/terminal).
type SimResult ¶ added in v1.0.125
type SimResult struct {
// Directive is the PAC return string (e.g. "PROXY a:8080; DIRECT").
Directive string `json:"directive"`
// Outcome is OutcomeMatched or OutcomeUndeterminedDNS.
Outcome string `json:"outcome"`
// MatchedRule describes the deciding rule/branch.
MatchedRule SimMatchedRule `json:"matchedRule"`
// Reason is a human-readable explanation of the decision.
Reason string `json:"reason"`
// PoolID is the pool whose chain was selected (empty for DIRECT).
PoolID string `json:"poolId,omitempty"`
// Chain is the ordered failover directive list.
Chain []string `json:"chain"`
// DirectPossible reports whether this decision can yield DIRECT (either
// the directive is/contains DIRECT, or availability mode appends it).
DirectPossible bool `json:"directPossible"`
// Warnings carries evaluation notes (e.g. undetermined DNS).
Warnings []string `json:"warnings,omitempty"`
// CompilerVersion and Revision pin the semantics used.
CompilerVersion string `json:"compilerVersion"`
Revision int64 `json:"revision"`
}
SimResult is the explainable simulation outcome.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store persists Config to a JSON file.
func (*Store) Compile ¶ added in v1.0.122
Compile builds the PAC artifact for the current config. proxyAddr is the request-derived "host[:port]" used only when Config.ProxyHost is empty (the port part is discarded — /proxy.pac is served from the UI or proxy listener, whose port is not necessarily the proxy port clients must use).
func (*Store) DefaultPort ¶
DefaultPort returns the startup-time fallback proxy port (0 if unset).
func (*Store) GeneratePAC ¶
GeneratePAC builds the PAC JavaScript (compatibility wrapper over Compile).
func (*Store) LoadMigrate ¶ added in v1.0.122
LoadMigrate loads from path, one-way migrating from legacyPath when path does not exist yet but legacyPath does: the legacy file is loaded, the store is re-pointed at path, and the config is persisted there. The legacy file is left in place (frozen; a downgraded binary reads it stale — see docs/operator/pac-traffic-steering.md).
func (*Store) ModTime ¶ added in v1.0.122
ModTime reports when the config last changed (zero before any load/set).
func (*Store) Set ¶
Set replaces the config and persists it. Set is deliberately TOLERANT of entry content (no validation): its callers include config-version rollback and cluster snapshot apply, which replay historical data and discard errors. Strict validation lives at the admin API boundary (ValidateConfig).
func (*Store) SetDefaultPort ¶
SetDefaultPort records the runtime proxy listening port used as the fallback when Config.ProxyPort is zero. Called once by the startup slice.
type ValidationIssue ¶ added in v1.0.122
type ValidationIssue struct {
// Field names the config field: "proxyHost", "proxyPort", "exclusions".
Field string `json:"field"`
// Entry is the offending exclusion entry, when applicable.
Entry string `json:"entry,omitempty"`
// Code is a stable machine-readable issue code (Issue* constants).
Code string `json:"code"`
// Message is the human-readable explanation.
Message string `json:"message"`
}
ValidationIssue is one actionable validation error or warning.
func ValidateProfilesConfig ¶ added in v1.0.125
func ValidateProfilesConfig(cfg ProfilesConfig) []ValidationIssue
ValidateProfilesConfig strictly validates a full profiles+pools config. No compiled-size probe here (unlike the legacy exclusions surface): the worst case is bounded by MaxRulesPerProfile × MaxEntryLen ≈ 300 KB, comfortably under the 1 MiB client fetch cap enforced for exclusions. Empty config is valid. Issues are actionable and name the offending object in Entry ("profile <id>" / "pool <id>" / "rule N of <id>").