Policy Management
A policy is the answer to “which measurements do we trust?”. Mero TEE keeps that answer in version-controlled JSON, published per release, and loaded by the KMS at startup so that Key Release can enforce it. This page covers how those policies are defined, how they are versioned and pinned to a profile, how the KMS matches a quote against them, and how versions are tracked in the compatibility catalog.
Two policy artifacts
Section titled “Two policy artifacts”Each release publishes two related, per-profile artifacts:
| Artifact | Release family | Consumed by |
|---|---|---|
kms-phala-attestation-policy.<profile>.json |
mero-kms-v<version> |
the KMS, as its appraisal / release policy |
published-mrtds.json |
mero-tee-v<version> |
operators and the verifier, as node measurement references |
The KMS policy is the allowlist the KMS enforces before releasing a key. The node
published-mrtds.json holds the expected MRTD/RTMR values for the node images of that
release, keyed by profile.
The KMS policy document
Section titled “The KMS policy document”The appraisal policy is a JSON envelope that the KMS validates before extracting the allowlists:
{ "tag": "2.3.50", "role": "kms", "profile": "locked-read-only", "policy": { "node_allowed_tcb_statuses": ["uptodate"], "node_allowed_mrtd": ["<96-hex MRTD>"], "node_allowed_rtmr0": ["<96-hex>"], "node_allowed_rtmr1": ["<96-hex>"], "node_allowed_rtmr2": ["<96-hex>"], "node_allowed_rtmr3": ["<96-hex>"] }}tagmust equal the release version the KMS was told to load;rolemust bekms;profilemust match the KMS’s own profile. A mismatch is rejected at load.- Each measurement is a validated TDX register value — 48 bytes / 96 hex characters,
normalized to lowercase without a
0xprefix. - The
node_allowed_*keys are the current names; the olderallowed_*names are still accepted as a fallback for pre-existing policy files, and a legacy file that omitsrole/profileis only tolerated for thelocked-read-onlyprofile.
Profiles and cohort separation
Section titled “Profiles and cohort separation”There are exactly three profiles, forming distinct trust cohorts:
debug— development only; never production.debug-read-only— staging / pre-production.locked-read-only— hardened production.
A KMS serves exactly one profile. The profile is pinned by a file baked into the image
at /etc/mero-kms/image-profile; if the MERO_KMS_PROFILE environment variable is also
set it must match the pinned value, or the KMS refuses to start (the deprecated
KMS_POLICY_PROFILE is a legacy alias for the same variable). The KMS also extends
RTMR3 with a runtime marker calimero.kms.profile=<profile> at startup, so the profile
is reflected in the hardware measurement itself and a policy for one profile cannot
silently accept a quote from another.
How the KMS loads a policy
Section titled “How the KMS loads a policy”The policy source is resolved at startup:
-
Release mode (default). With
MERO_KMS_VERSIONset (e.g.2.3.50ormero-kms-v2.3.50) and a profile resolved, the KMS builds the download URL itself fromhttps://github.com/calimero-network/mero-tee/releases/download/mero-kms-v<version>/. It tries the profile-specific asset (kms-phala-attestation-policy.<profile>.json) first, then the generickms-phala-attestation-policy.jsonas a legacy fallback. There is no policy-URL environment variable — the URL is derived from the version and profile. -
Optional integrity pin. If
MERO_KMS_POLICY_SHA256is set, the fetched bytes must hash to that value or the load fails. -
Env / air-gapped mode. With
USE_ENV_POLICY=true, the allowlists come fromALLOWED_TCB_STATUSES/ALLOWED_MRTD/ALLOWED_RTMR0..3instead of a release asset, for development and air-gapped deployments. -
Degraded start. If
MERO_KMS_VERSIONis not set (and env mode is off), the KMS still starts, butpolicy_readyis false:/attestkeeps working while/get-keyfails closed with503 policy_not_readyuntil a policy is available.
See Config Reference for the full environment surface.
How a policy is matched
Section titled “How a policy is matched”Matching happens on every /get-key request, after the quote is cryptographically
verified (see Key Release). The KMS compares the quote’s reported
values against the loaded allowlists:
tcb_status ∈ allowed_tcb_statuses else 403 tcb_status_rejectedmrtd ∈ node_allowed_mrtd \rtmr0 ∈ node_allowed_rtmr0 |rtmr1 ∈ node_allowed_rtmr1 | any miss => 403 measurement_policy_rejectedrtmr2 ∈ node_allowed_rtmr2 |rtmr3 ∈ node_allowed_rtmr3 /All five registers plus the TCB status must match; any single mismatch is a policy violation. RTMR3 is the profile discriminator, so a production policy that only lists production RTMR3 values will never admit a debug node.
Versioning and the compatibility catalog
Section titled “Versioning and the compatibility catalog”Because a KMS version change can change measurements, KMS and node-image versions move
together. The repository’s compatibility-catalog.json records the released pairings:
{ "schema_version": 1, "releases": [ { "version": "2.3.50", "kms_tag": "mero-kms-v2.3.50", "node_image_tag": "mero-tee-v2.3.50", "kms_policy_url": ".../mero-kms-v2.3.50/kms-phala-attestation-policy.json", "node_policy_url": ".../mero-tee-v2.3.50/published-mrtds.json" } ]}Each entry pairs a KMS release with the node-image release of the same version and links
directly to that pair’s policy and MRTD assets. The catalog is regenerated
automatically whenever a release is published (the update-compatibility-catalog
workflow enumerates mero-kms-v* releases and commits the rebuilt file to master), so
it is a derived index rather than a hand-edited source. Look up a pairing by version:
jq '.releases[] | select(.version == "2.3.50")' compatibility-catalog.jsonA separate CI guard keeps the KMS and merod/node versions from drifting apart when
either is bumped.
Promotion
Section titled “Promotion”Policy values are not hand-typed. A staging probe deploys a candidate KMS image, pulls a
quote via /attest, extracts the measurements (MRTD, RTMR0–3, TCB status) and the
compose hash, and produces measurement candidates. Those candidates are promoted into
the profile-specific policy file by pull request, reviewed, and then bundled into the
next release — so every accepted measurement is auditable through git history. The
operational details of that pipeline live in the
release pipeline and runbooks.