75 lines
2.8 KiB
Rego
75 lines
2.8 KiB
Rego
|
|
# 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 (`<ns>/<sa>` 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 <cto@basicstack.de>
|
||
|
|
# 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],
|
||
|
|
)
|
||
|
|
}
|