Skip to content

Epic: AuthPolicy/PPE attribute dictionary compatibility #130

Description

@guicassolato

Parent: part of #109 → #100

Summary

Automating translation from Kuadrant AuthPolicy to PPE config is made significantly harder by a mismatch between the two dictionaries used to reference runtime values evaluated through the auth pipeline. AuthPolicy user-defined rules — CEL expressions, OPA/Rego policies, and native gjson pattern-matching — are written against Kuadrant Well-Known Attributes (WKA) (request.*, auth.identity.*, auth.metadata.*, source.*, …), while the PPE evaluates against its Attribute Bags (http.*, subject.*, client.*, claim.*, metadata.*, …). The names and structures differ, so rules that compile cleanly on both sides can misbehave at runtime after a naive move.

This epic tracks the design and implementation of a correct translation strategy for these embedded expressions.

Why this matters for feature parity

Consider an AuthPolicy OPA rule:

opa:
  rego: 'allow { object.get(input.request.headers, "x-custom-header", "") == "foo" }'

A naive lift-and-shift into PPE config:

- opa:
    module: |
      package authz
      import rego.v1
      default allow := false
      allow if { object.get(input.request.headers, "x-custom-header", "") == "foo" }  # wrong at runtime

Both modules are syntactically valid and compile, but the PPE version does not behave as intended because input.request.headers does not exist in the PPE — the equivalent is input.http.request_headers. In this particular OPA case the mismatch fails silently (the lookup resolves to undefined). Other policies behave less gently: the same class of mismatch in CEL tends to surface as a more acute error, up to and including a panic, depending on the expression.

Note: this example also happens to use OPA v0 syntax (allow { … }) where the PPE requires v1 (allow if { … }). The OPA version migration is a separate concern and is out of scope for this epic, which focuses solely on the mismatching dictionaries.

The core problem: incompatible dictionaries

This affects OPA/Rego, CEL, and gjson pattern-matching alike. Naive string substitution (sed s/request.headers/http.request_headers/g) is not a solution, because the reference can be indirected:

req := input.request
allow if { object.get(req.headers, "x-custom-header", "") == "foo" }

That leaves two viable approaches:

  • Option 1 — Adapter input (preferred). Feed the PPE's OPA/CEL evaluation an input/environment shaped like the Kuadrant/Authorino object, so request.headers et al. resolve. User rules stay verbatim. This could be gated behind a "compatibility mode" feature flag. Easier and less costly to deliver; the trade-off is a compatibility shim and its runtime overhead, and that it adapts rather than converts. This is the preferred starting point.
  • Option 2 — Semantic compiler. Parse each rule (Rego/CEL) into an AST and rewrite whole subtrees with proper semantics — e.g. replace an entire input.request subtree (including indirected bindings) with input.http — likely in the control plane. Sound and more elegant (produces native PPE config), but considerably harder to achieve.

Beyond request.*

request.* ↔ http.* is the easy instance. Harder cases:

  • auth.identity.* ↔ subject.* / client.* / claim.*, and auth.metadata.* ↔ metadata.*, where User-Defined Types (UDT) and their inheritance make the mapping non-mechanical.
  • Namespace collision: PPE's own request.* bag denotes environment/trace metadata (request.request_id, request.trace_id, …), not the HTTP request — a source of subtle mistranslation.

Forward look (non-blocking)

This will likely intersect praxis-proxy/praxis#1232 ("small, core type system" for Praxis, @alexsnaps). A natural evolution is expanding that type system to semantic types (HttpRequest, HttpHeader, Metadata, …); the PPE's type system should then be compatible with / inherit from Praxis's, which would give the compiler in Option 2 a principled target.

References

Metadata

Metadata

Assignees

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions