Skip to content

Commit 5dcf93e

Browse files
committed
docs(proposal): multiple policies per VM
1 parent 6aea4cd commit 5dcf93e

1 file changed

Lines changed: 126 additions & 0 deletions

File tree

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Multiple policies per VM
2+
3+
Status: Proposed (draft PR, design doc only).
4+
Tracking: ROADMAP.md → Medium term ("Multiple policies").
5+
6+
## Motivation
7+
8+
Today the plugin configuration is a single `module`, evaluated against
9+
a hard-coded target rule named `"allow"`. That's enough for one
10+
filter doing one job. Real deployments often want:
11+
12+
- Separate authn and authz rules in distinct, independently authored
13+
modules.
14+
- An audit policy whose decision is "log this" rather than "block
15+
this", evaluated alongside the allow policy.
16+
- Per-route policy bundles where the dispatch happens by package
17+
name, mirroring how OPA addresses rules as `data.<package>.<rule>`.
18+
19+
Stuffing all of those into one module loses the boundaries that make
20+
the policies maintainable.
21+
22+
## Goals
23+
24+
1. Plugin config accepts either a single module (current shape, still
25+
supported) or a list of modules:
26+
27+
```json
28+
{ "modules": [
29+
{ "package": "authz", "type": "module", "rules": [ ... ] },
30+
{ "package": "audit", "type": "module", "rules": [ ... ] }
31+
]}
32+
```
33+
34+
1. Each `module` gains an optional `package` string. Defaults to `""`
35+
(the implicit single-module case).
36+
1. The proxy-wasm shim grows a new plugin config field `targets[]` to
37+
pick which `package.rule` pairs are evaluated:
38+
39+
```yaml
40+
targets:
41+
- package: authz
42+
rule: allow
43+
on_deny: send_local_response_403
44+
- package: audit
45+
rule: log
46+
on_deny: noop # decision is recorded out-of-band, not enforced
47+
```
48+
49+
1. Evaluator gains a fully qualified rule lookup: `evalModule(modules,
50+
"authz.allow", input)` instead of just rule-by-name within a single
51+
module.
52+
53+
## Non-goals
54+
55+
- Cross-module data references (`data.audit.events` from inside an
56+
authz rule). Modules stay independent. If you need shared data, put
57+
it in `input`.
58+
- Hot-reload of individual modules. Replace the full plugin config or
59+
reconfigure the filter; no per-module surgery.
60+
- A package import system. There's no `import data.helpers` because
61+
there are no shared helpers.
62+
63+
## Design sketch
64+
65+
### AST builder
66+
67+
`src/ast.zig` gains a `Modules` (plural) container. The existing
68+
`Module` keeps its `rules` field plus an optional `package` slice.
69+
70+
### Evaluator
71+
72+
`evalModules(target_pkg, target_rule, input)`:
73+
74+
1. Find all modules whose `package` equals `target_pkg`.
75+
1. Within those, OR-combine all rules whose name equals `target_rule`,
76+
reusing the existing `evalModule` body.
77+
1. Apply the same `default` handling.
78+
79+
### Proxy-wasm shim
80+
81+
The `proxy_on_request_headers` callback iterates `targets[]`. For
82+
each:
83+
84+
- Evaluate `package.rule`.
85+
- If decision is deny and `on_deny` is `send_local_response_403`,
86+
short-circuit immediately.
87+
- If decision is recorded but not enforced (`on_deny: noop`), call
88+
`proxy_log` with a structured line, continue.
89+
90+
This makes "authz blocks, audit logs" a config decision, not a code
91+
decision.
92+
93+
### Config compatibility
94+
95+
A bare module (current shape, no `modules` wrapper) is wrapped into a
96+
synthetic single-element list with `package: ""` and a synthetic
97+
`targets[]` of `[{ package: "", rule: "allow", on_deny: send_403 }]`.
98+
Existing configs keep working unchanged.
99+
100+
## API impact
101+
102+
- New plugin config shape `modules[]` + `targets[]` (additive).
103+
- New AST optional field `package` on `module` (additive).
104+
- Existing single-module configs still work.
105+
106+
## Test plan
107+
108+
- Unit tests for `evalModules` covering: same package different rules,
109+
same rule different packages, missing package (returns deny by
110+
default).
111+
- Integration test: two modules (`authz`, `audit`) with the audit
112+
policy logging-only.
113+
- Envoy example: extend `examples/envoy/envoy.yaml` to demonstrate
114+
the audit-without-enforce target.
115+
116+
## Open questions
117+
118+
- Naming: `targets` vs `evaluations` vs `gates`? `targets` reads as
119+
"what to look for", which matches the existing single-target
120+
vocabulary.
121+
- Should `on_deny: noop` be allowed without a matching `proxy_log`
122+
call, i.e., is silent "informational evaluation" useful? A flag-day
123+
decision; safer default is "log on every audit decision".
124+
- Order of evaluation within `targets[]`: declaration order, or
125+
topologically (audit always before allow)? Declaration order is
126+
simpler and documentable.

0 commit comments

Comments
 (0)