JanssenProject / JanssenProject/jans

feat(jans-cedarling): experimantal dogwood bindings

Open
#14,736 4 comments 1 reaction 1 assignee Assigned to @haileyesus2433 View on GitHub
comp-jans-cedarling kind-feature
Dominant language
Java
Stars
648
Forks
174
Avg merge
1d 18h
Merged PRs (30d)
110

Description

## Title
Explore Dogwood as a second, selectable policy engine in Cedarling

## Context
Dogwood (https://github.com/dogwood-policy/dogwood) extends Cedar with history-aware / temporal policy evaluation — policies that can reference whether something happened within a time range. Raised in CNCF cedar-policy-maintainers list (https://lists.cncf.io/g/cedar-policy-maintainers/topic/120630294), tied to AWS agent-authorization work (https://theaieconomy.substack.com/p/aws-dogwood-ai-agent-authorization).

Use cases that prompted this:
- Deny WRITE/DELETE on privileged actions if admin's own credentials changed in last 24h.
- Rate limits: max 2 password resets/day/account; 1 password reset/user/hour by an admin.
- Deny access after 3+ login failures in last 30 min.

**Note**: all three of these are solvable today with plain Cedar, no history-awareness needed — pass externally-computed context attributes (`last_password_change`, `failed_login_count_30m`, etc.) into the request and write normal Cedar conditions on them. That's cheaper, ships now, zero engine risk. Dogwood only earns its cost if we want policies themselves to reference decision history natively, without the caller pre-computing it — i.e. a deliberate choice to test whether that model is worth the added complexity, not something forced by these use cases.

## Repo findings (github.com/dogwood-policy/dogwood, commit 1fcba6c)
- Pure Rust workspace: `dogwood-language` (core), `dogwood-cli`, `dogwood-docs`. No Go/Python — good fit for cedarling's Rust core.
- `cedar-policy = "4.11"` pinned — matches cedarling's `4.11.2` exactly. No version conflict.
- License: Apache-2.0.
- Deps beyond cedar: `pest`/`pest_derive` (its own parser), `rhai` (sandboxed scripting VM for "provider" fields, no fs/net by default — `net` feature is off by default), `regex`, `rust_decimal`, `serde`. No `std::fs`/`net`/`tokio` in non-test code (only fixture reads in `#[test]` fns). On paper wasm-buildable; **rhai is the real bundle-size unknown** — not measured yet.
- **Not on crates.io.** `amzn-dogwood-language` publishes only to AWS's internal registry (`publish = ["brazil"]` in its workspace Cargo.toml). Only path to depend on it is a git dependency pinned to a specific commit — no semver, no changelog, no crates.io audit tooling coverage. Real ongoing maintenance cost.

## Interface comparison — NOT a drop-in swap
Checked cedarling's `Authz` (`cedarling/src/authz/mod.rs`) against dogwood's `Authorizer`. Three separate mismatches:

1. **Call shape.** cedarling: `cedar_policy::Authorizer` is stateless, `&self`, one-shot `is_authorized(Request, PolicySet, Entities)` — safe to share across concurrent requests, no memory between calls. Dogwood: `Authorizer` is `&mut self`, built once from a `LoweredPolicySet`, then fed one `Event` at a time and keeps history *inside the struct* — that's the point of it (temporal reasoning needs memory). Using it means keeping a live, keyed store of `Authorizer` instances (per subject/session — whoever's history matters), with its own lifecycle (create/evict/persist). Cedar path needs none of that.
2. **Policy representation.** cedarling loads plain `cedar_policy::PolicySet` from the policy store as-is. Dogwood needs its own parse path: `ParsedPolicySet` → `LoweredPolicySet`, since it's a superset syntax (temporal operators, provider macros via rhai). The existing policy-store loader can't feed dogwood without a second parse/validate path alongside the current one.
3. **Input model.** cedarling builds cedar `Entities`/`Context` from its own `Request` type via `EntityBuilder`. Dogwood consumes its own `Event` type — needs a translator, not reuse.

## Implementation plan
Selecting the engine at bootstrap (config flag) is realistic. But it is not a thin adapter — it's closer to a second, parallel authorize pipeline sharing the outer API surface:
- new internal trait abstracting "authorize this input" over both the stateless-cedar and stateful-dogwood call shapes
- session/subject-keyed state store for dogwood `Authorizer` instances
- second policy-loading/validation path in the policy store for dogwood's dialect
- Request → dogwood `Event` translator
- wasm bundle-size measurement (actual `cargo build --target wasm32-unknown-unknown` + size diff) before deciding whether this ships in the default wasm build, a separate/experimental wasm build, or native-only

To keep churn low, scope each piece to a single method, not the full cedarling stack (auth, multi-issuer, batch, etc.) — small surface means bindings don't need to chase an unstable core. Track as separate sub-issues under this one:

### Sub-issue 1 — `cedarling_dogwood` Rust core (single method) — spike
New top-level crate (sibling to `cedarling_opa`, `cedarling_pg` — not a nested `experimental/` folder, matches existing repo convention). Scope: one method only — `authorize_unsigned_dogwood(request) -> Decision`, an analog of `authorize_unsigned`: same unsigned-request shape (no JWT/trusted-issuer stack), but the request is translated into a dogwood `Event` and run through dogwood's stateful `Authorizer` instead of `cedar_policy::Authorizer`. Includes: git-pinned dogwood dependency (pin exact commit, e.g. `1fcba6cdf891a3ce703b5ba54d322a45184c5e7a`), minimal `LoweredPolicySet` loading, session/subject-keyed state store for the stateful `Authorizer`.

Ends with an explicit continue/kill/park decision comment on this issue — not silent drift into permanent experimental code.

**Acceptance criteria** (spike isn't done until all reported in the issue thread):
- Builds native + `wasm32-unknown-unknown`.
- Wasm bundle size delta reported in KB (actual `cargo build` + size diff, not estimated).
- `Authorizer` construction + `is_authorized` latency reported in µs.
- One real PoC policy implemented in dogwood dialect: "deny access after 3+ login failures in 30 min" (from the original Admin UI use case) — proves the spike solves something plain-Cedar-with-precomputed-context can't, or shows it doesn't.

**Non-goals**: default `authorize`/`authorize_unsigned` path untouched; dogwood not committed as a permanent dependency by this issue alone; Python/WASM-JS bindings out of scope here (sub-issues 2/3 stay blocked until the go-decision).

**Risk**: dogwood is unreleased/git-pinned only (see Repo findings above) — re-evaluate if upstream force-pushes, breaks, or the pinned commit goes stale.

**Follow-up**: post spike findings back to the CNCF cedar-policy-maintainers thread that started this (https://lists.cncf.io/g/cedar-policy-maintainers/topic/120630294) — costs nothing, closes the loop with upstream.

Labels: `spike`, `needs-decision`.

### Sub-issue 2 — Python binding
Wraps the single method from sub-issue 1 only, following existing `cedarling_python` binding pattern. Blocked on sub-issue 1 landing (method signature stable).

### Sub-issue 3 — WASM/JS binding
Wraps the same single method, following existing `cedarling_wasm` binding pattern. This is the actual motivating platform (edge-runtime bundle size). Blocked on sub-issue 1.

**Scope note**: all three sub-issues touch only the new `cedarling_dogwood` crate and its bindings. Existing `cedarling` main crate is not modified by this issue. (Slimming the main crate's JWT stack for `authorize_unsigned`-only wasm builds is a separate, unrelated issue — not part of this one.)

## Open questions
- Is history kept *per cedarling instance* (client-local) enough for the intended use cases, or must multiple instances (client + server, multiple devices) agree on the same history? Dogwood's `Authorizer` only guarantees consistency within one instance's event stream — cross-instance sync is on us, not solved by dogwood itself.
- Native-only first (server-side), defer wasm question until bundle-size numbers exist?
- Is Dogwood stable enough to depend on given it's git-pinned/unreleased, or does this stay experimental/behind a feature flag indefinitely?

## References
- https://github.com/dogwood-policy/dogwood
- https://lists.cncf.io/g/cedar-policy-maintainers/topic/120630294
- https://theaieconomy.substack.com/p/aws-dogwood-ai-agent-authorization
- https://github.com/dogwood-policy/dogwood/blob/1fcba6cdf891a3ce703b5ba54d322a45184c5e7a/dogwood-docs/examples/provider_regex_analyze_fields/README.md
- https://dogwood-policy.github.io/dogwood/guide/11-mcp-schema-generation.html

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.