Policies / detail
attestation.rego
Type rego · State inactive · Current version v2 · Updated 2026-05-08T14:35:37.969592648+00:00
Current body
sha256: ff5884cf1814db51d283dae51647aa386759790a80039595a333a6408d7faf84# Attestation enforcement (warden-specs/TECH_SPEC.md#identity-service §6).
#
# Two deny rules gate sensitive tools on a verified attestation claim:
#
# 1. tool_requires_attestation true + attestation absent or expired
# → deny with `attestation_stale` reason.
# 2. tool_requires_attestation true + attestation fresh, but the
# claimed measurement is not in the per-tool allowlist
# → deny with `measurement not in allowlist` reason.
#
# The allowlist itself lives in `attestation_allowlist.json` (loaded as
# a regorus data document by `build_engine_from_dir`). v1 covers
# `wire_transfer` and the `delete_*` family, with `dev-binary-hash` as
# the only approved measurement — the chaos-monkey scenario in
# fires `wire_transfer` with a non-allowlisted measurement and asserts
# the deny path. Per warden-specs/TECH_SPEC.md#identity-service §6 the file format will move to a
# Sigstore-style signed transparency log; v1 is "the code that calls
# wire_transfer is the code we checked into git."
#
# Why `input.tool_type` and not `input.method` (as the spec sketch
# uses): existing rules in `governance.rego` key on `tool_type` (the
# `params.name` extracted by the proxy), while `method` is the
# JSON-RPC method (`call_tool`, `tools/list`, ...). For attestation
# enforcement the question is "what does the tool DO," so `tool_type`
# is the right key.
package warden.authz
import rego.v1
# Tools that require a fresh, allowlisted attestation. The v1 set is
# the warden-specs/TECH_SPEC.md#identity-service §9-phase-4 launch slice: `wire_transfer` and the
# `delete_*` family. Pattern matching with `startswith` keeps the
# allowlist data-driven rather than baking each delete tool here.
#
# **Migration-phase gating.** Both rules also require
# `input.agent_spiffe` to be present — i.e. attestation is only
# enforced for agents that have switched to warden-identity-issued
# SVIDs (post ). Legacy CA-minted agents (CN-only certs in
# `warden-e2e/run.sh` and `warden-chaos-monkey`) continue to flow
# through the existing review path until SVID-bound mTLS becomes
# mandatory. The chaos-monkey `unattested_binary` scenario in
# bypasses this clause by minting a SPIFFE-SAN cert, which is what
# makes that scenario a real attestation-gate test.
tool_requires_attestation if {
# `is_string` handles both shapes a non-SVID caller can produce:
# the JSON key absent (Option::None with `skip_serializing_if`)
# AND the JSON key set to `null` (Option::None without the skip).
# A bare `input.agent_spiffe` would fire on the second shape since
# rego treats null-valued bindings as defined.
is_string(input.agent_spiffe)
input.tool_type == "wire_transfer"
}
tool_requires_attestation if {
is_string(input.agent_spiffe)
startswith(input.tool_type, "delete_")
}
# Fresh = attestation present AND `expires_at` in the future per the
# proxy's clock (`ns_now`, defined in governance.rego). Undefined when
# `input.attestation` is absent — `not fresh_attestation` then fires
# the stale-deny rule below. RFC 3339 parse failures also leave the
# rule undefined → safe (we err toward "attestation_stale" rather
# than letting a malformed expires_at sneak through).
fresh_attestation if {
input.attestation
expires_ns := time.parse_rfc3339_ns(input.attestation.expires_at)
expires_ns > ns_now
}
# Lookup against the allowlist data document. Undefined when the tool
# has no allowlist entry OR when the agent's measurement isn't listed.
# The deny rule below pairs this with `tool_requires_attestation` so
# tools we never registered (i.e. typos) hit the stale-deny path
# rather than this one — attempting to run an unregistered sensitive
# tool should still 403, just with a clearer reason than "your binary
# isn't in the allowlist for $tool that doesn't exist."
measurement_allowed if {
input.attestation
some m in data.warden.attestation.allowed_measurements[input.tool_type]
m == input.attestation.measurement
}
# Stale / missing attestation for an attestation-required tool. The
# `attestation_stale` substring is the operator-facing grep target —
# chaos-monkey's `unattested_binary` scenario asserts on it, and
# auditors filter ledger rows by it.
deny contains msg if {
tool_requires_attestation
not fresh_attestation
msg := sprintf(
"Violation: attestation_stale — required attestation is missing or expired for %s.",
[input.tool_type],
)
}
# Fresh attestation but the binary is not approved for this tool.
# Format the measurement into the message so a triager pasting the
# row into the SIEM can see exactly which build was rejected.
deny contains msg if {
tool_requires_attestation
fresh_attestation
not measurement_allowed
# regorus's `sprintf` doesn't support Go's `%q` verb (quote-and-
# escape) so we splice the measurement with explicit quotes.
msg := sprintf(
"Violation: agent measurement \"%s\" not in allowlist for %s.",
[input.attestation.measurement, input.tool_type],
)
}