Skip to content

wafList: named WAF IP lists contract (wafList.*, ipInList macro)#130

Open
acoshift wants to merge 2 commits into
mainfrom
waf-ip-lists
Open

wafList: named WAF IP lists contract (wafList.*, ipInList macro)#130
acoshift wants to merge 2 commits into
mainfrom
waf-ip-lists

Conversation

@acoshift

Copy link
Copy Markdown
Member

PR 1 of 6 for SPEC-waf-ip-lists.md (workspace spec, §13 row 1): the api-library contract for named, reusable WAF IP/CIDR lists.

What's here

  • waflist.goWAFLists interface (wafList.set/get/list/delete), WAFListSet/Get/List/Delete requests with Valid() (structural: name grammar, entry IP/CIDR parse, zoned-address reject, caps), WAFListItem/WAFListListResult + Table().
  • waflistmacro.go — the ipInList(<field>, "<list-name>") platform macro (spec §2): WAFListRefs (string-literal- and comment-aware scanner, all CEL literal forms) and ExpandWAFListMacros (expansion into pure pinned engine CEL: (ipInCidr(...) || ...), empty list → (false)). The token is reserved: any occurrence outside literals/comments that is not the exact macro grammar — including member-selector position x.ipInList(...) and digit-glued 123ipInList(...) — fails at Valid() instead of wedging at the engine. Entry splice is canonical by construction (re-parse + Masked() re-render).
  • constraint.goWAFListMaxLists/MaxEntries/MaxEntryLength + expanded-size caps WAFMaxExpandedExpressionLength/WAFMaxExpandedDocumentBytes (spec §3.2).
  • errors.goErrWAFListNotFound.
  • role.gowafList.* permission family; reads follow the default public-bindable rule (spec §7).
  • waf.govalidWAFRules/validWAFLimits run the macro scanner per expression/filter.
  • client/waflist.go, client/client.go, api.go — client + Interface.WAFList().

Test evidence

gofmt clean; go build ./..., go vet ./..., go test ./...142 tests pass (root + client). New coverage per spec §14: scanner literal-forms/comment/boundary cases, malformed usages (incl. receiver-dot and digit-glued regressions), multi-macro expansion goldens (splice bookkeeping), empty-list/(false), canonicalization of corrupt-but-parseable entries, byte-identical pass-through, entry validation (v4/v6/CIDR/zoned/dup).

Rollout / composition notes

  • Tag gate: downstreams (apiserver/CLI/MCP) re-pin the api module only after this merges; the apiserver PR (spec §13 row 2) applies its schema migration (waf_lists, waf_zones.generation/dispatched_generation) before its binary deploys — this library PR itself needs no migration.
  • apiserver#238 composition (waf.test / compile-validate waf.set): compileWAFZoneCEL and waf.test compile via parapet pkg/waf, where ipInList is an unknown function. The apiserver wafList PR must run ExpandWAFListMacros before any compile step, and waf.test must expand against current lists ("test what will run") — spec §5.4 item 3 (amended with this PR's review pass). Until that wiring lands, a #238-carrying binary that re-pins the api module past this branch would reject macro-bearing waf.set/waf.test at compile — sequence the apiserver wafList PR with (or before) such a re-pin.
  • No merge conflict expected with the sibling api branch waf-test (#238's pinned pseudo-version): verified clean via git merge-tree — the two branches touch disjoint regions of waf.go/constraint.go.
  • No deployer/parapet changes anywhere in the feature (spec §1.2/§13).

acoshift added 2 commits July 11, 2026 21:29
api-repo part of SPEC-waf-ip-lists.md:

- waflist.go: WAFLists interface (wafList.set/get/list/delete),
  WAFListSet/Get/List/Delete + WAFListItem/WAFListListResult with
  structural Valid() (netip-based entry checks, zoned addresses
  rejected; duplicates are a server-side post-normalization check)
  and NAME|TYPE|ENTRIES|REFERENCED BY|AGE Table() helpers.
- waflistmacro.go: shared ipInList macro scanner/expander —
  WAFListRefs (sorted, deduped refs; malformed usage = structural
  error) and ExpandWAFListMacros (ipInCidr || chain, bare v4 -> /32,
  v6 -> /128, empty list -> (false), macro-free text passes through
  byte-identical). Literal-aware: skips all CEL string forms (raw/
  bytes/triple prefixes) and // comments.
- waf.go: validWAFRules/validWAFLimits reject malformed ipInList
  usage with the existing rule/limit ref convention.
- constraint.go: WAFList* caps + expanded-size caps
  (WAFMaxExpandedExpressionLength/WAFMaxExpandedDocumentBytes).
- errors.go: ErrWAFListNotFound.
- role.go: wafList.* permission family (reads stay default
  public-bindable, matching waf.get/waf.list).
- api.go/client: Interface.WAFList() + client/waflist.go.

Cross-feature notes (apiserver#238 / api branch waf-test):

- apiserver#238 compile-validates waf.set CEL via parapet pkg/waf
  NewPredicate. ipInList is NOT engine CEL: that compile (and the
  waf.test dry run) must run on the expression AFTER
  api.ExpandWAFListMacros, never on the stored macro form — to the
  engine ipInList is an unknown function.
- Expected (trivial) merge friction with the api waf-test branch:
  both add constants adjacent to the WAF block in constraint.go, and
  waf-test adds Test() to the WAF interface plus waf_test.go (this
  change deliberately puts its tests in waflist_test.go /
  waflistmacro_test.go to avoid that file). No semantic overlap:
  waf.test drafts flow through validWAFRules/validWAFLimits and so
  pick up the structural macro check automatically.
…ntry splice

Review fixes:

- Scanner: x.ipInList(request.remote_ip, "office-ips") matched the macro
  grammar textually and expanded to `x.(...)` -- engine-invalid CEL --
  breaking the "fails at Valid(), never wedges at the engine" contract.
  The scanner now tracks whether the last significant byte was '.' and
  rejects the reserved token in member-selector position, plus the
  digit-glued form (123ipInList) per the SPEC token-boundary rule.
- wafListEntryCIDR: re-render entries (Masked prefix, canonical address
  + /32 or /128) so spliced CIDR text is canonical by construction, not
  dependent on wafList.set normalization staying correct upstream.
- Tests: multi-macro expansion goldens (splice bookkeeping), receiver /
  digit-glued regression cases, sloppy-entry canonicalization golden.

Composition note (apiserver#238, branch waf-test): its compileWAFZoneCEL /
waf.test compile raw expressions via parapet pkg/waf, where ipInList is an
unknown function -- the apiserver wafList PR must run ExpandWAFListMacros
before any compile step (SPEC-waf-ip-lists.md section 5.4 item 3, added with
this change). The sibling api branch waf-test merges cleanly with this one
(verified via git merge-tree).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant