mero-tee

June 19, 2026 · View on GitHub

TEE infrastructure for Calimero: mero-kms-phala (Key Management Service for Phala Cloud) and GCP node-image build (Packer-based merod node images with TDX attestation).

Full documentation: Architecture Reference

Components

ComponentDescription
mero-kms-phalaKMS that validates TDX attestations and releases storage encryption keys to merod nodes running in Phala CVMs
mero-tee/GCP Packer build for locked merod node images (debug, debug-read-only, locked-read-only profiles)
Fleet HA sidecarsystemd service baked into ReadOnly fleet node images (mero-tee/ansible/roles/merotee/templates/fleet-sidecar.sh.j2); waits for merod readiness via meroctl --output-format json peers, reads its own PeerId from config.toml ([identity].peer_id) and its own MRTD from /sys/class/misc/tdx_guest/measurements/mrtd:sha384, polls MDMA for group assignments (sending peer_id + mrtd so MDMA's MRTD-gated should_join can match) and joins each via meroctl tee fleet-join <GROUP_ID> (group id passed positionally, core >= 0.10.1-rc.27). Confirms an assignment back to MDMA only when the join command exits 0. See Fleet HA sidecar lifecycle for the join + leave-on-disable reconcile loop.
attestation-verifier/Public web tool for verifying KMS and node attestations via Intel Trust Authority

Quick Start

Build mero-kms-phala

cargo build --release

Requires Rust. Dependencies on calimero-tee-attestation and calimero-server-primitives via git dependency on calimero-network/core.

Build GCP Images

See mero-tee/README.md. Requires Packer, Ansible, and GCP credentials.

Verify Release Assets

# Verify all release trust assets for a tag
scripts/release/verify-release-assets.sh X.Y.Z

# Generate pinned merod KMS config from signed release policy
scripts/policy/generate-merod-kms-phala-attestation-config.sh \
  --profile locked-read-only X.Y.Z https://<kms-url>/

Documentation

All detailed documentation lives in the Architecture Reference:

TopicPage
High-level architecture & system mapSystem Overview
KMS, node images, attestation verifierComponents
Mutual attestation & trust boundariesTrust Model
Challenge/get-key protocolKey Release Flow
KMS self-attestation & public verifierAttestation Flow
MRTD/RTMR, compose hash, operator verifyVerification
Release classes, CI/CD, pipeline flowsRelease Pipeline
Staging probes, policy promotion, ADRsPolicy Management
Phala KMS, GCP nodes, blue-green rolloutRunbooks
All environment variablesConfig Reference
ServiceError variants & HTTP codesError Handling
TEE terms & definitionsGlossary

Fleet HA sidecar lifecycle

The fleet HA sidecar (mero-tee/ansible/roles/merotee/templates/fleet-sidecar.sh.j2) runs a one-second reconcile loop on each ReadOnly TEE fleet node. Every cycle it polls MDMA's POST /api/fleet/should-join with the node's peer_id + mrtd, then drives merod toward the namespace set MDMA currently assigns to it.

Join (converge toward desired). For each assigned namespace not yet confirmed, the sidecar retries meroctl tee fleet-join <GROUP_ID> until core reports admitted: true, then POSTs /api/fleet/confirm back to MDMA. A namespace is only recorded in the local fleet-confirmed.json once both local admission and the MDMA confirm succeed, so a transient MDMA blip on the confirm step is retried (fleet-join is idempotent in core) rather than lost.

Leave on disable (converge away from dropped namespaces). When a namespace the node had previously confirmed (confirmed) is no longer in MDMA's assignment set (desired) — i.e. HA disabled, the slot reclaimed, or the node's MRTD no longer trusted — the sidecar self-leaves it with meroctl namespace leave <hex_namespace_id>. That publishes MemberLeft at the namespace root and cascades through every descendant subgroup, so core evicts the node from the namespace and all subgroups and purges its local data and keys. namespace leave is idempotent and non-fatal: leaving a namespace the node already left (or was never a direct member of) is treated as benign and never aborts the loop.

Poll-success safety gate. The entire reconcile — join, leave, and prune — runs only after a successful poll: HTTP 200 with a body that parses as an {"assignments": [...]} object. Any curl error, non-2xx, timeout, or unparseable/wrong-shape 200 body causes the cycle to skip reconcile and preserve fleet-confirmed.json untouched. This is safety-critical: because a missing assignment now triggers an irreversible leave + key purge, a transient MDMA outage must never be conflated with "all namespaces disabled" and shred keys on healthy replicas.

Note: this is the mero-tee side — the sidecar is the trigger. The actual eviction and data/key purge are performed by core in response to MemberLeft. The key-deletion half of that purge requires core ≥ 0.11.0-rc.6 (the merodVersion baked into these node images; see mero-tee/versions.json), which carries the leave→purge, transparent subgroup admission, and the Open-subgroup replication fix (core #2809). On older merod the sidecar will still issue the leave, but keys may not be purged.

Release Process

  1. Merge version bump PR (Cargo.toml and versions.json aligned)
  2. Node release runs first; KMS release waits, then creates draft
  3. Human reviews and publishes KMS draft release
  4. update-compatibility-catalog workflow updates compatibility-catalog.json

Two artifact families per version:

  • mero-kms-vX.Y.Z: KMS binaries, attestation policies, compatibility map, Sigstore signatures
  • mero-tee-vX.Y.Z: published-mrtds.json, release provenance, SBOM, checksums, Sigstore signatures

License

MIT OR Apache-2.0