@@ -12,21 +12,23 @@ internal/
1212 celenv/ CEL environment, custom functions, program cache
1313 jsonpath/ tiny JSONPath subset (used by `jsonPath` CEL fn)
1414 facts/ facts registry: inline values, file reads, URL fetchers
15- policy/ config types, YAML parser, evaluator
15+ policy/ config types, YAML parser, evaluators (extAuthz + extProc)
1616 configwatch/ fsnotify wrapper, debounce, k8s ConfigMap-aware
17- httpserver/ /healthz, /readyz, /metrics, ext-authz endpoint
17+ httpserver/ /healthz, /readyz, /metrics, extAuthz endpoint
18+ grpcserver/ extProc endpoint (Envoy ext_proc gRPC stream)
1819```
1920
2021The dependency graph is acyclic and one-directional:
2122
2223```
23- cmd ──► policy ──► facts
24- │ │
25- ├──► celenv ──► jsonpath
26- │
27- cmd ──► httpserver ──► policy
28- cmd ──► configwatch
29- cmd ──► log (everyone else also imports log)
24+ cmd --> policy --> facts
25+ | |
26+ +--> celenv --> jsonpath
27+ |
28+ cmd --> httpserver --> policy
29+ cmd --> grpcserver --> policy
30+ cmd --> configwatch
31+ cmd --> log (everyone else also imports log)
3032```
3133
3234` log ` is the only package every other one depends on. It must stay
@@ -38,20 +40,23 @@ dependency-free of the rest.
3840
3941```
4042Config{
41- Defaults Defaults // action, denyStatus, denyBody, maxBodyBytes, allowOnError
43+ Defaults Defaults // per-engine: Defaults.ExtAuthz + Defaults.ExtProc + global DryRun
4244 Logging Logging // level, format, exclude/redact headers, etc.
4345 Facts []facts.Spec // declared facts
44- Groups []Group // ordered list of rule buckets
46+ Groups []Group // ordered list of rule buckets, each bound to one engine
4547
4648 // not in YAML, set during LoadBytes:
4749 env *celenv.Env // shared CEL env + program cache
4850 registry *facts.Registry // facts runtime (with URL fetchers etc.)
4951}
5052```
5153
52- A ` Group ` carries its compiled ` matchProg cel.Program ` , and each ` Rule `
53- carries its own. Compilation happens once in ` LoadBytes() ` ; the request
54- path only executes already-compiled programs.
54+ A ` Group ` carries ` parameters ` (engine, mode, phase) and its compiled
55+ ` matchProg cel.Program ` . Each ` Rule ` carries its own ` matchProg ` , plus
56+ either a ` Validation ` (extAuthz) or a list of ` Mutation ` whose CEL
57+ expressions are also compiled (value/code/headers/body). Compilation
58+ happens once in ` LoadBytes() ` ; the request path only executes
59+ already-compiled programs.
5560
5661## Request lifecycle
5762
@@ -72,14 +77,14 @@ path only executes already-compiled programs.
7277 ┌────────────┴────────────┐
7378 │ for each Group, in order│
7479 └────────────┬────────────┘
75- │ 6. group.matchProg → bool
80+ │ 6. group.matchProg to bool
7681 │ (skip silently if false)
7782 ▼
7883 ┌──────────────────────────┐
7984 │ Group.Mode == firstMatch │ every rule:
80- │ or == all │ rule.matchProg → bool
81- └──────────────┬───────────┘ + action inheritance
82- ▼ + dryRun + fallthrough
85+ │ or == all │ rule.matchProg to bool
86+ └──────────────┬───────────┘ + validation. action
87+ ▼ + dryRun (no fallthrough)
8388 ┌──────────┐
8489 │ Decision │
8590 │ {Allowed,│
@@ -96,12 +101,44 @@ path only executes already-compiled programs.
96101There is exactly ** one** access-log record per request, level ` INFO ` for
97102allow / ` WARN ` for deny. The CEL programs were compiled at policy load,
98103so the only per-request cost is body read + map build + a few CEL calls.
104+ Only groups whose ` parameters.engine ` is ` extAuthz ` are evaluated here;
105+ ` firstMatch ` lets the first matching rule decide, ` matchAll ` requires
106+ every rule to match or denies. There is no action inheritance and no
107+ fallthrough.
108+
109+ ## Response lifecycle (extProc, gRPC)
110+
111+ ` grpcserver ` implements Envoy's ` ext_proc ` bidirectional stream. Each
112+ stream message maps to a phase (` requestHeaders ` , ` requestBody ` ,
113+ ` responseHeaders ` , ` responseBody ` ); the server keeps per-stream state
114+ (the request, then the response) and calls `policy.EvaluateProc(phase,
115+ req, resp)`, which walks the extProc groups bound to that phase
116+ (` firstMatch ` or ` applyAll ` ) and returns the resolved mutations (CEL
117+ values already evaluated). The server then:
118+
119+ - if a ` directResponse ` is applicable, emits an Envoy ` ImmediateResponse `
120+ (status + headers + body) and ignores the rest (short-circuit);
121+ - otherwise builds a ` CommonResponse ` with the header/body mutations;
122+ - under dry-run (global or per-rule), responds CONTINUE while logging what
123+ it would have done;
124+ - on a body phase, enforces ` extProc.maxBodyBytes ` with
125+ ` onBodyOverflow: skip | fail ` .
126+
127+ Live CEL variables follow the phase: ` request ` /` facts ` in request phases,
128+ plus ` response ` in response phases.
99129
100130## CEL environment (` internal/celenv ` )
101131
102- Built once per policy load, in ` celenv.New() ` :
132+ Built once per policy load, in ` celenv.New() ` , as two scoped environments :
103133
104- - Variables declared: ` request ` (dyn) and ` facts ` (dyn).
134+ - ` ScopeRequest ` declares ` request ` (dyn) and ` facts ` (dyn).
135+ - ` ScopeResponse ` declares ` request ` , ` response ` (dyn) and ` facts ` .
136+ An expression that references ` response ` in a request scope fails to
137+ compile, which is how the per-phase variable contract is enforced.
138+ - Compilation is typed: ` Compile ` (bool, for ` match ` ), ` CompileString `
139+ (header/body values), ` CompileInt ` (` setStatus ` code), ` CompileStringMap `
140+ (` directResponse.headers ` , ` map<string,string> ` ). Output type is checked
141+ at load. Their ` Eval* ` counterparts re-check the type at runtime.
105142- Standard library + these extensions enabled:
106143 ` ext.Strings() ` , ` ext.Encoders() ` , ` ext.Lists() ` , ` ext.Sets() ` ,
107144 ` ext.Math() ` , ` ext.Bindings() ` .
@@ -136,17 +173,17 @@ policy.LoadFile
136173 │
137174 ▼
138175policy.LoadBytes
139- ├─ yaml.Unmarshal → Config{Defaults, Logging, Facts, Groups}
176+ ├─ yaml.Unmarshal to Config{Defaults, Logging, Facts, Groups}
140177 ├─ applyDefaults
141178 ├─ validate
142- ├─ celenv.New ← every Compile() is cached
143- ├─ facts.New(Facts) ← builds Registry, value entries already populated
144- └─ compile ← turn match strings into cel.Program
179+ ├─ celenv.New ( every Compile() is cached)
180+ ├─ facts.New(Facts) ( builds Registry, value entries already populated)
181+ └─ compile ( turn match/mutation strings into cel.Program)
145182
146183cfg.Start(ctx)
147184 └─ for each fact:
148- file → os.ReadFile → store as string
149- url → http GET → store as string + spawn goroutine
185+ file os.ReadFile store as string
186+ url http GET store as string + spawn goroutine
150187 with time.Ticker(interval)
151188```
152189
@@ -205,17 +242,24 @@ masked. The redaction policy: a value of length `< 2 * redactReveal` is
205242fully masked; otherwise the first ` redactReveal ` characters are shown
206243and the rest replaced with ` * ` .
207244
208- ## HTTP server endpoints
245+ ## Servers and endpoints
246+
247+ The HTTP server (` httpserver ` , default ` :8080 ` ) serves extAuthz plus
248+ operational endpoints:
209249
210250| Path | Purpose |
211251| ---------- | ----------------------------------------------------------------------- |
212- | ` / ` | ext-authz check. Envoy POSTs the original request here. |
252+ | ` / ` | extAuthz check. Envoy POSTs the original request here. |
213253| ` /healthz ` | always 200 once the process is up. |
214254| ` /readyz ` | 200 only after the first policy is installed (used as readiness probe). |
215255| ` /metrics ` | Prometheus text format. Counters per (rule, outcome, dry_run). |
216256
217257` / ` accepts any method and path; it inspects whatever Envoy forwarded.
218258
259+ The gRPC server (` grpcserver ` , default ` :9090 ` ) serves the Envoy ext_proc
260+ ` ExternalProcessor ` service for the extProc engine. Both servers share the
261+ same ` *policy.Config ` pointer and the same hot-reload path in ` cmd ` .
262+
219263## Build & ship
220264
221265- ` Dockerfile ` produces a ` gcr.io/distroless/static:nonroot ` image with
0 commit comments