Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions examples/cross-operator-delegation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,9 @@ so the labeling here is deliberate:
## Files

- `ca2a-config.yaml`: runtime config (software-only, advisory).
- `policy.cedar`: the Child's local policy stated as a Cedar rule. It permits
`{task:read, task:audit}`. The demo enforces the same rule with the
`LocalPolicy` allow-set (the model [claim 3](../../experiments/claim3-scope-policy-intersection/)
uses), so it has no dependency on a specific Cedar engine version; binding the
Cedar engine in the peer path is tracked separately.
- `policy.cedar`: the Child's local policy. The demo loads this file into
`CedarPolicy`, which evaluates the Cedar rule permitting
`{task:read, task:audit}`.
- `demo.py`: the end-to-end offline flow. Regenerates `chain.json` / `dag.json`
on each run and re-verifies them through the CLI.
- `chain.json`: a valid two-hop delegation chain, `task:admin` narrowing to
Expand All @@ -68,7 +66,7 @@ Cross-operator delegation example (offline; synthetic SEV-SNP vectors)
[2] mutual attestation binds each channel key (software-asserted): OK
[3] attenuated delegation chain verifies (leaf scope narrows): OK
leaf delegated scope : ['task:read', 'task:write']
child local policy : ['task:audit', 'task:read']
child Cedar policy : ['task:audit', 'task:read'] (policy.cedar)
effective scope : ['task:read']
[4] effective scope = delegated ∩ policy = {task:read}: OK
[5] child ALLOWS task:read (delegated and locally permitted): OK
Expand Down
19 changes: 8 additions & 11 deletions examples/cross-operator-delegation/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@
from cryptography.hazmat.primitives.hashes import SHA384 # noqa: E402
from cryptography.x509.oid import NameOID # noqa: E402

from ca2a_runtime.cedar import CedarPolicy # noqa: E402
from ca2a_runtime.channel import SealedChannel, generate_channel_keypair, open_sealed # noqa: E402
from ca2a_runtime.delegation import DelegationCredential, new_keypair # noqa: E402
from ca2a_runtime.errors import AttestationFailed, ScopeNotPermitted # noqa: E402
from ca2a_runtime.peer import effective_scope, enforce_peer_call # noqa: E402
from ca2a_runtime.policy import LocalPolicy # noqa: E402
from ca2a_runtime.provenance import ( # noqa: E402
DelegationRecord,
cross_check_chain,
Expand Down Expand Up @@ -168,20 +168,17 @@ def main() -> int:
sorted(chain[-1].scope) == ["task:read", "task:write"]))

# ------------------------------------------------------------------
# Scope ∩ local policy at the Child. The child was delegated
# {task:read, task:write}; its local policy permits {task:read, task:audit}.
# Scope ∩ local Cedar policy at the Child. The child was delegated
# {task:read, task:write}; policy.cedar permits {task:read, task:audit}.
# Effective scope is the intersection: {task:read}.
#
# We enforce with the LocalPolicy allow-set (the same model claim 3 uses),
# so the demo has no dependency on a specific Cedar engine version. The
# committed policy.cedar states the SAME rule as a Cedar policy for the real
# engine (ca2a_runtime.cedar.CedarPolicy); binding that engine in the peer
# path is tracked separately (see policy.py and issue #10).
# ------------------------------------------------------------------
policy = LocalPolicy.of(["task:read", "task:audit"])
policy_path = HERE / "policy.cedar"
policy = CedarPolicy(policy_path.read_text(encoding="utf-8"))
eff = effective_scope(chain, policy)
policy_candidates = chain[-1].scope | {"task:audit"}
policy_allow = sorted(cap for cap in policy_candidates if policy.permits(cap))
print(f" leaf delegated scope : {sorted(chain[-1].scope)}")
print(f" child local policy : {sorted(policy.allow)}")
print(f" child Cedar policy : {policy_allow} ({policy_path.name})")
print(f" effective scope : {sorted(eff)}")
checks.append(step(4, "effective scope = delegated ∩ policy = {task:read}",
sorted(eff) == ["task:read"]))
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ requires-python = ">=3.11"
dependencies = [
"cryptography>=42.0",
"pyyaml>=6.0",
"cedarpy>=4.8",
"cedarpy==4.8.7",
# TRACE record models + RFC 8785 signing, reused rather than reimplemented
# (see ROADMAP Tier 0). Pin a compatible minor; cross-repo skew is a known
# risk documented in LIMITATIONS.md.
Expand Down
15 changes: 15 additions & 0 deletions tests/unit/test_cedar.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from __future__ import annotations

from pathlib import Path

import pytest

from ca2a_runtime.cedar import CedarPolicy
Expand All @@ -14,6 +16,12 @@
'permit(principal, action == Action::"read", resource);\n'
'permit(principal, action == Action::"write", resource);\n'
)
EXAMPLE_POLICY = (
Path(__file__).resolve().parents[2]
/ "examples"
/ "cross-operator-delegation"
/ "policy.cedar"
)


def _chain():
Expand Down Expand Up @@ -55,3 +63,10 @@ def test_capability_with_colon() -> None:
p = CedarPolicy('permit(principal, action == Action::"tool:read", resource);')
assert p.permits("tool:read")
assert not p.permits("tool:write")


def test_cross_operator_example_policy() -> None:
p = CedarPolicy(EXAMPLE_POLICY.read_text(encoding="utf-8"))
assert p.permits("task:read")
assert p.permits("task:audit")
assert not p.permits("task:write")