# METADATA # title: Signing-service policy (ProofedMesh Phase 1) # description: | # Decides whether an authenticated client may obtain a DSSE signature for a # given key_ref. Enforces two independent conditions: # # 1. The Pocket-ID client identity (`sub` claim) and the Kubernetes # ServiceAccount identity (`/` from TokenReview) resolve to the # same agent row in the trust-api `agents` table. # 2. The requested `key_ref` is in the set of key_refs this agent's role # is permitted to sign with. # # Both lookups are passed in as `input.registry.by_pocket_id` and # `input.registry.by_k8s`. The signing-service MUST perform both lookups # from the DB and hand the raw rows (or `null` on no match) to OPA — the # policy does not reach out to the DB on its own. # authors: # - CTO Agent # related_resources: # - ref: docs/architecture/v1.1.md#user-content-44-identitaet-und-schluessel # - ref: db/trust/migrations/0002_agents.up.sql package proofedmesh.sinks.signing_service import rego.v1 default allow := false # Map agent_id → set of allowed key_ref prefixes. Prefixes end at the # quarter boundary so we can rotate the suffix (`-2026-q4`, `-2027-q1`, …) # without changing the policy. Keep this list in sync with the trust-api # agent registry seeds (db/trust/seeds/0001_phase1_agents.sql). agent_key_prefixes := { "agent-approval-service": {"trust-api/approval-signer-"}, "agent-testagent-transform": {"trust-api/agent-testagent-transform-"}, "agent-dummy-sink": {"trust-api/dummy-sink-signer-"}, } # Both registry lookups must have returned the SAME agent row and that row # must be internally consistent with the identities actually presented by # the caller (Pocket-ID `sub` and the K8s ServiceAccount reported by # TokenReview). Any null, mismatch, or absent field denies. identities_agree if { input.registry.by_pocket_id.agent_id == input.registry.by_k8s.agent_id input.registry.by_pocket_id.pocket_id_client == input.pocket_id_client input.registry.by_pocket_id.k8s_namespace == input.k8s.namespace input.registry.by_pocket_id.k8s_service_account == input.k8s.service_account input.registry.by_k8s.pocket_id_client == input.pocket_id_client input.registry.by_k8s.k8s_namespace == input.k8s.namespace input.registry.by_k8s.k8s_service_account == input.k8s.service_account } key_ref_matches_role if { some prefix in agent_key_prefixes[input.registry.by_pocket_id.agent_id] startswith(input.key_ref, prefix) } allow if { identities_agree key_ref_matches_role } deny_reason contains msg if { not identities_agree msg := "pocket-id and k8s identities do not resolve to the same registry agent" } deny_reason contains msg if { identities_agree not key_ref_matches_role msg := sprintf( "agent %q is not permitted to sign with key_ref %q", [input.registry.by_pocket_id.agent_id, input.key_ref], ) }