Skip to content

feat(policy): attested policy delivery over openshell.policy.v1alpha1.Engine wire#2

Open
dvavili wants to merge 2 commits into
dvavili/policy-provider-subsystemfrom
dvavili/attested-policy-provider
Open

feat(policy): attested policy delivery over openshell.policy.v1alpha1.Engine wire#2
dvavili wants to merge 2 commits into
dvavili/policy-provider-subsystemfrom
dvavili/attested-policy-provider

Conversation

@dvavili
Copy link
Copy Markdown
Owner

@dvavili dvavili commented Jun 1, 2026

Summary

Adds AttestedPolicyProvider driver that fetches per-sandbox policy from an out-of-process attested engine over a new OpenShell-owned wire contract.

Stacks on #1 — includes the PolicyProvider trait + permits_mutation gate from that PR; the new commits here are the two on top.

  • Wire openshell.policy.v1alpha1.Engine (proto/policy.proto): four RPCs (Health, AcquireHandle, GetProjection, ReleaseHandle). Owned by OpenShell; any conforming engine is valid. Enforcement-shaped subset — no per-request Authorize, no GetPolicyDigest (the gateway enforces locally via OPA + Landlock + seccomp + nft once it has the projection).
  • Driver AttestedPolicyProvider: configured via [openshell.policy] type = "attested" + source_uds_path + trust_store_path. Mutators inherit Unsupported defaults, so the permits_mutation gate plus the three RPC mutators all refuse uniformly under the attested type.
  • Abstraction: driver speaks a PolicySource trait; GrpcPolicySource (the production impl) is the only file that touches the generated proto types. Driver is unit-testable with a MockPolicySource — no tonic server stood up in tests.
  • Trust store: multi-key Ed25519 JSON loader; distinct error variant per malformed shape (missing, unreadable, malformed JSON, zero keys, duplicate id, empty id, empty/malformed PEM).
  • Cleanup: the PolicyProviderRegistry from feat(policy): pluggable PolicyProvider subsystem with mutation gate #1 is replaced with a direct match in the resolver. The registry was paying complexity cost for two providers with divergent construction shapes (sync Store vs. async UDS + trust-store load).

Admission flow: build RuntimeContext → acquire_handle → get_projection("openshell.sandbox.v1") → verify signature against trust store by signing_key_id → decode body → release_handle. Verification fails closed on unknown key id or bad signature; unsigned envelopes admit with a warning (v0 fallback that auto-disables once the engine starts signing). Handle release runs on every exit path so engine-side state never leaks.

Test plan

  • cargo test -p openshell-server --lib -- --test-threads=1 — 770 passing (29 new: 12 trust-store loader, 4 source, 9 driver via MockPolicySource, 4 resolver/config wiring)
  • cargo build -p openshell-server clean
  • End-to-end against a real engine pending the server-side adapter that serves the Engine surface

Deferred follow-ups

Auth-mode gate (LocalDev rejection), audit tagging on admission/lifecycle events, gateway-side handle + signing-key persistence across restarts, release_handle on sandbox deletion (driver releases immediately today), removing the shadow seam at rpv_shadow.rs.

dvavili added 2 commits June 1, 2026 16:44
…solver

Phase A modeled PolicyProviderRegistry after openshell-providers'
ProviderRegistry to "promote policy to a first-class subsystem" on par
with AI providers. The trait + types half of that mirror earned its
keep. The registry half did not.

ProviderRegistry is valuable when (a) there are many implementations
(~10+) so name-based dispatch is the main concern, (b) all impls share
a uniform construction shape (same config record), and (c) no impl
needs external resources or async setup at construction. Policy
providers fail all three: two impls only (local, attested), divergent
construction shapes (LocalPolicyProvider::new takes a sync Store; the
forthcoming AttestedPolicyProvider needs file-loaded trust store + UDS
dial + async health round-trip), and the attested case requires
external resources at construction.

The forced abstraction had already manifested as a special-case branch
in resolve_policy_provider that fell through the registry lookup for
the `attested` type, with a defensive comment explaining why the
registry only held one entry. That comment was the tell.

Replace with a direct `match policy_type { ... }` in
resolve_policy_provider. The `attested` arm still returns the same
"policy type 'attested' is not yet available" startup error pending
its implementation. Trait, error types, contexts, and policy-type-id
constants stay. Mirror the trait, not the registry.

Tests: registry_lookup_returns_registered_provider removed with the
registry. Pre-existing 735 lib tests minus that one = 734 pass
single-threaded.
Define an out-of-process policy delivery contract owned by OpenShell
and a driver that consumes it. With `[openshell.policy] type =
"attested"` plus a UDS path and a multi-key Ed25519 trust store, the
gateway fetches per-sandbox policy from whichever process serves the
wire; mutation RPCs and the chunk-approval surface continue to refuse
via the trait defaults and the `permits_mutation` gate.

Wire: `openshell.policy.v1alpha1.Engine` (Health, AcquireHandle,
GetProjection, ReleaseHandle). The driver consumes a `PolicySource`
trait; `GrpcPolicySource` is the only file touching proto types so the
driver stays implementation-agnostic and unit-testable with a mock
source. Multi-key trust store rejects each malformed shape with a
distinct error variant.

Envelope signature verification flips closed automatically when the
wire starts emitting signed envelopes; empty signatures admit with a
warning today. Gateway runtime-context signing key is per-process and
paired with handle persistence in the follow-up.

Tests: 29 new (12 trust-store loader, 4 source, 9 attested via mock,
4 resolver/config wiring). 763 lib tests pass single-threaded.

Deferred: auth-mode gate (LocalDev rejection), audit tagging, handle
persistence, releasing handles on sandbox deletion, removing the
shadow seam at rpv_shadow.rs.
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