Skip to content

Commit 77d3238

Browse files
committed
feat(policy): support Cedar as an alternate sandbox policy language
A sandbox policy can be written in Cedar instead of YAML. The format is chosen once per sandbox: YAML policies keep using the Rego engine unchanged, and a .cedar policy makes Cedar the sole authority for network connections, per-request L7 checks, and Landlock filesystem grants. Policy model and validation: - New crates openshell-policy-cedar-schema (canonical Cedar schema) and openshell-policy-cedar (CedarEngine). Policies are validated in strict mode, and shapes that cannot be enforced exactly as written are rejected at submission in the CLI, the gateway, and the supervisor. - Load-time analysis derives Landlock grants, the endpoints and paths inspected per request (@protocol rest, json-rpc, mcp, graphql, websocket, or websocket-graphql, scoped by @path), native TCP endpoints (@transport("tcp")), and policy DNS eligibility, including delimited host globs. - Delimited like gives YAML-style segment globs for hosts, paths, and binaries. It comes from ai-openshell/cedar, pinned by commit, with a matching deny.toml allow-git exception; its Lean specification is ai-openshell/cedar-spec. - The schema declares only what the supervisor populates: NetworkConnect carries no request fields, Process has no workdir, and a has test on an undeclared attribute is rejected. A filesystem permit must list ReadFile with WriteFile, and resource.host_port names an endpoint for policy DNS. - @enforcement("audit") provides audit endpoints and staged audit policies; decisions name policies by their @id annotation. - Request policies match query parameters through context.query tags; repeated keys are evaluated per combination of values, as YAML matches every value. - context.destination_ip restricts the resolved addresses a connection may reach, the Cedar form of allowed_ips. Permits read it only as fixed isInRange lists; connections are evaluated before DNS and again per resolved address, private addresses need an address-constrained permit or an exact host, and always-blocked destinations stay blocked. - context.binary_aliases matches symlinked binaries, resolved inside the container as YAML does, and context.binary_globs holds the glob patterns that match the calling binary or an ancestor, so YAML binary globs translate exactly. - @enforcement("staged") marks a policy that is only compared against the enforced decision; @enforcement("audit") has YAML's audit-endpoint semantics. An HttpRequest policy may name its host with a delimited glob, inspecting every matching host as a YAML wildcard endpoint does. A GraphQL path is authoritative for the requests it parses. A delimited like whose ** touches other characters, and a GraphQL-over-WebSocket message permit without a GraphQL condition, are rejected at load. Integration: - SandboxPolicy gains cedar_policy_source, the gateway-managed provider_credential_rules, and endpoint_settings. Attached providers supply credentials and token grants on requests Cedar allows but never grant access; their allowed_ips only narrow addresses. Provider rules are compiled into Cedar policies tagged with their token-grant owner, so owner selection is decided by Cedar. - The CLI accepts .cedar policies and a separate --middleware file that also carries endpoint settings (TLS skip, body limits, MCP versions, persisted queries) and the process identity; policy get renders the Cedar text, middleware, endpoint settings, process identity, and provider rules. - The gateway rejects format switches, Cedar global policies, and removal of Cedar-derived Landlock grants on live sandboxes. - Cedar sandboxes run without an OPA engine or Rego. The supervisor builds the Cedar engine at startup admission, which owns middleware state and its own generation, commits Cedar, credentials, and middleware together on reload, and applies the policy validation failure mode to Cedar. Every network decision point, including the Windows host proxy, routes through the PolicyEngine enum; Cedar tunnels pin Cedar's generation. Middleware selection is shared Rust, the data both engines hand to the proxy is typed Rust with the Rego output converted at one boundary, and engine-neutral types move out of opa.rs. - The supervisor maps JSON-RPC, MCP, GraphQL, and WebSocket requests into Cedar requests the way the YAML engine sees them: uppercase methods, calls and response frames only in a POST, and one evaluation per call, GraphQL operation, or WebSocket message. - Cedar decisions report the policy DNS records that cover a connection, so transparent TCP correlation works as for YAML. - Cedar support is the default cedar feature of openshell-policy; crates that never handle Cedar policies disable it. The Go SDK carries the new fields. - The RPM vendor archive captures the git source replacement tables cargo vendor prints, so the offline RPM build resolves the Cedar dependency. Testing and documentation: - Unit tests for the Cedar crates and supervisor integration, and an e2e:cedar suite run by mise run e2e and the Docker E2E matrix. - A parity harness runs paired YAML and Cedar policies through both engines. It ports 258 of the 260 existing YAML decision tests; the remaining one that is valid YAML needs a Cedar language change. On Linux, 2,904 of 2,909 cases agree; the 5 differences are deliberate and recorded with their reasons, an evaluation error on either side fails a case, and load rejections are asserted. Token-grant owners match YAML for every shipped provider profile. - Every example and e2e directory with a YAML policy has a Cedar version beside it, and its scripts accept POLICY_FORMAT=cedar; a parity test runs each pair through both engines. - User documentation for Cedar policies, and a crate README. Signed-off-by: Vishwanath Raman <vishwanathr@nvidia.com>
1 parent 6546691 commit 77d3238

161 files changed

Lines changed: 42349 additions & 2535 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/e2e-docker-test.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ on:
2929
required: false
3030
type: string
3131
default: >-
32-
[{"suite":"python","cmd":"mise run --no-deps --skip-deps e2e:python","apt_packages":"","python_proto":true,"mcp":false},{"suite":"oidc-python","cmd":"mise run --no-deps --skip-deps e2e:oidc-python:docker","apt_packages":"","python_proto":true,"mcp":false},{"suite":"oidc-pkce-docker","cmd":"mise run --no-deps --skip-deps e2e:oidc-pkce:docker","apt_packages":"openssh-client","python_proto":false,"mcp":false},{"suite":"rust-docker","cmd":"mise run --no-deps --skip-deps e2e:rust","apt_packages":"openssh-client","python_proto":false,"mcp":false},{"suite":"oci-image","cmd":"mise run --no-deps --skip-deps e2e:oci-image","apt_packages":"openssh-client","python_proto":false,"mcp":false},{"suite":"mcp","cmd":"mise run --no-deps --skip-deps e2e:mcp","apt_packages":"","python_proto":false,"mcp":true}]
32+
[{"suite":"python","cmd":"mise run --no-deps --skip-deps e2e:python","apt_packages":"","python_proto":true,"mcp":false},{"suite":"oidc-python","cmd":"mise run --no-deps --skip-deps e2e:oidc-python:docker","apt_packages":"","python_proto":true,"mcp":false},{"suite":"oidc-pkce-docker","cmd":"mise run --no-deps --skip-deps e2e:oidc-pkce:docker","apt_packages":"openssh-client","python_proto":false,"mcp":false},{"suite":"rust-docker","cmd":"mise run --no-deps --skip-deps e2e:rust","apt_packages":"openssh-client","python_proto":false,"mcp":false},{"suite":"oci-image","cmd":"mise run --no-deps --skip-deps e2e:oci-image","apt_packages":"openssh-client","python_proto":false,"mcp":false},{"suite":"mcp","cmd":"mise run --no-deps --skip-deps e2e:mcp","apt_packages":"","python_proto":false,"mcp":true},{"suite":"mcp-cedar","cmd":"POLICY_FORMAT=cedar mise run --no-deps --skip-deps e2e:mcp","apt_packages":"","python_proto":false,"mcp":true},{"suite":"cedar","cmd":"mise run --no-deps --skip-deps e2e:cedar","apt_packages":"","python_proto":false,"mcp":false}]
3333
3434
permissions:
3535
actions: read

‎.packit.yaml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ actions:
2828
# reflects any patching that Packit may have done (e.g. version bumps).
2929
- 'bash -c "VERSION=${PACKIT_PROJECT_VERSION} && TMPDIR=$(mktemp -d) && DIR=openshell-${VERSION} && mkdir -p ${TMPDIR}/${DIR} && git ls-files -z | xargs -0 tar cf - | tar xf - -C ${TMPDIR}/${DIR}/ && tar -czf openshell-${VERSION}.tar.gz -C ${TMPDIR} ${DIR} && rm -rf ${TMPDIR}"'
3030
# Step 2: Create vendored Cargo dependencies tarball for offline RPM build.
31-
- 'bash -c "VERSION=${PACKIT_PROJECT_VERSION} && CARGO_HTTP_TIMEOUT=600 CARGO_NET_RETRY=5 cargo vendor --locked --quiet && tar -cJf openshell-${VERSION}-vendor.tar.xz vendor/ && rm -rf vendor/"'
31+
# The script also captures the [source."git+..."] replacement tables that
32+
# cargo vendor prints, so git dependencies resolve offline in %prep.
33+
- 'bash deploy/rpm/create-vendor-archive.sh'
3234
# Step 3: Return the primary archive name. Packit expects create-archive
3335
# to print one path for Source0; Source1 is patched explicitly below.
3436
- 'bash -c "echo openshell-${PACKIT_PROJECT_VERSION}.tar.gz"'

‎AGENTS.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Do not rely on this file for a full inventory. The detailed public and contribut
3131
| `crates/openshell-sandbox-backend/` | OpenShell sandbox backend | `OpenShellRuntimeBackend` and the authenticated OpenShell Sandbox Protocol shared with `openshell-sandbox` |
3232
| `crates/openshell-policy/` | Policy engine | Filesystem, network, and process constraints |
3333
| `crates/openshell-policy-schema/` | Authored policy schema | Dependency-light YAML/JSON representation, bounded parsing, and pure authored-language semantics |
34+
| `crates/openshell-policy-cedar/` | Cedar policy engine | Validation, Landlock/L7/DNS derivation, and evaluation for Cedar-authored sandbox policies |
35+
| `crates/openshell-policy-cedar-schema/` | Cedar policy schema | Canonical Cedar schema and entity, action, and context names |
3436
| `crates/openshell-bootstrap/` | Gateway metadata | Gateway registration metadata, auth token storage, mTLS bundle storage |
3537
| `crates/openshell-gateway-interceptors/` | Gateway interceptors | Intercepts and transforms configured gRPC requests at the gateway routing boundary |
3638
| `crates/openshell-ocsf/` | OCSF logging | OCSF v1.8.0 event types, builders, shorthand/JSONL formatters, tracing layers |

‎CONTRIBUTING.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ These are the primary `mise` tasks for day-to-day development:
337337
| --------------- | --------------------------------------------- |
338338
| `crates/` | Rust crates |
339339
| `crates/openshell-policy-schema/` | Canonical authored policy DTOs and bounded YAML/JSON parser |
340+
| `crates/openshell-policy-cedar/` | Validation and evaluation for Cedar-authored sandbox policies |
341+
| `crates/openshell-policy-cedar-schema/` | Canonical Cedar schema and entity, action, and context names |
340342
| `crates/openshell-prover-cli/` | Standalone local policy boundary checker |
341343
| `python/` | Python SDK and bindings |
342344
| `sdk/go/` | Go SDK (types, gRPC clients, converters) |

0 commit comments

Comments
 (0)