Components
Mero TEE is three components plus a release pipeline that ties them together. This page is the map: what each part does and where it lives.
mero-kms — the KMS
Section titled “mero-kms — the KMS”mero-kms/ · Rust + Axum · runs as a frozen GCP TDX image
(merotee-kms-<profile>-<version>, built by mero-tee/playbook-kms.yml), one
cluster per release.
The KMS validates a node’s TDX attestation and releases a deterministic storage
encryption key, HKDF’d from a random root that exists only in the memory of the
cluster’s replicas. A cluster is five replicas across three zones in at least two
regions, behind one VPC-internal URL. Source of truth: mero-kms/src/; design:
docs/design/gcp-tdx-kms.md.
Endpoints
Section titled “Endpoints”Registered in mero-kms/src/handlers/mod.rs:
| Method · Path | Request | Response |
|---|---|---|
GET /health |
— | { status: "alive", service: "mero-kms", clusterRootReady } (always 200) |
POST /challenge |
{ peerId } |
{ challengeId, nonceB64, expiresAt } |
POST /get-key |
{ challengeId, quoteB64, peerId, peerPublicKeyB64, signatureB64, sealToB64 } |
{ sealedKeyB64, sealNonceB64 } |
POST /attest |
{ nonceB64, bindingB64?, transportKey?, eventLog? } |
{ quoteB64, reportDataHex, transportPublicKeyB64?, eventLogB64? } |
POST /cluster/nonce |
— | a nonce for a joining replica |
POST /cluster/join |
the joiner’s quote and one-time public key | the root sealed to that key, plus the giver’s own quote |
The key release flow walks the /challenge → /get-key
exchange step by step; trust model covers the checks.
Key modules
Section titled “Key modules”| File | Responsibility |
|---|---|
src/main.rs |
Bootstraps config, CORS, the backend, and the Axum server. |
src/config/mod.rs, config/env.rs |
Parse and validate all environment variables into Config. |
src/backend.rs |
configfs-tsm quotes, the in-memory root, and HKDF key derivation. |
src/cluster.rs |
Bootstrap and the attested join protocol (“same as me”, debug TDs refused, the root sealed between one-time X25519 keys). |
src/policy.rs |
AttestationPolicy model and fail-closed startup validation. |
src/measurement.rs, src/util.rs |
The 48-byte SHA-384 HexMeasurement newtype, the debug-TD check, and hex helpers. |
src/stateless_challenge.rs |
Stateless challenges: nonces HMAC’d from the challenge ID and peer ID with a root-derived key, plus a per-replica set of spent challenges until expiry. |
src/sealed.rs |
Sealing released keys to the node’s one-time key. |
src/handlers/ |
challenge.rs, get_key.rs, attest.rs, and the errors.rs ServiceError enum. |
Configuration & deployment
Section titled “Configuration & deployment”Everything that decides what the KMS releases keys to — the node allowlist, the
profile, the listen port — is baked into the image (/etc/mero-kms/kms.env) and
so into its measurements. There is no runtime policy fetch. The only inputs from
outside are two instance-metadata keys, kms-bootstrap and kms-peers, which
decide only whether a replica generates the root or joins, and from whom. The full
variable table is in the config reference.
MDMA deploys a cluster per release: the bootstrap replica first, then the others joining it, then one internal URL. Replicas never restart in place; a dead one is replaced by a new VM that joins. See the runbooks.
Node image build
Section titled “Node image build”mero-tee/ · Packer (googlecompute) + Ansible · builds GCP TDX Confidential
VM images containing merod. The same template builds the KMS image with
image_role = "kms" (playbook-kms.yml): the same base, lockdown and
dm-verity seal, none of the node’s services.
ubuntu.pkr.hcl provisions an Ubuntu 26.04 LTS (Resolute Raccoon) image — kernel
6.17+ is required for RTMR3 sysfs support, and an LTS because an EOL release gets
delisted from ubuntu-os-cloud and fails the build — from
ubuntu-2604-lts-amd64, running the Ansible playbook.yml. The build host does
not need TDX (n2-standard-2); the resulting image runs on TDX-capable hardware
(e.g. c3-standard-4) at runtime. Output image family:
merotee-ubuntu-questing-<profile> — a frozen identifier that mdma’s dispatcher
matches on, not a claim about the base release.
What the image bakes
Section titled “What the image bakes”Driven by playbook.yml and mero-tee/versions.json:
merod,meroctl, andmero-authat a pinned core tag (via thecalimero-corerole).- Traefik reverse proxy, plus
node-exporter,vmagent, andvectorfor metrics/logs. - The fleet HA sidecar systemd service
(
ansible/roles/merotee/templates/fleet-sidecar.sh.j2). - A profile marker at
/etc/calimero/image-profileand acalimero.root_hash(a SHA-256 over/etc/calimero, the baked binaries, and grub config) injected into the kernel cmdline so binary substitution changes the measurements.
Profiles
Section titled “Profiles”| Profile | Posture |
|---|---|
debug |
Development. SSH and read-write filesystem. |
debug-read-only |
Integration / pre-production. Read-only root, SSH retained. |
locked-read-only |
Production. The merod-lockdown, merotee-conformance, and ubuntu-user-removal steps run only for this profile, so its content — and therefore its MRTD/RTMR — differs from the others. |
Each profile produces a distinct RTMR3 via calimero-init, giving the
cryptographic cohort separation the trust model
relies on.
Building
Section titled “Building”packer build -var-file=ubuntu-x86.pkrvars.hcl ubuntu.pkr.hclRelease builds go through mero-tee/build-and-release.sh, which reads versions
from versions.json and can build one profile or all three. Requires Packer,
Ansible, and GCP credentials.
Attestation verifier
Section titled “Attestation verifier”attestation-verifier/ · React (Vite) SPA + Vercel serverless API.
A public tool for independent verification: anyone can confirm a KMS or a
merod node is running genuine TEE code, without being part of the node ⇄ KMS
trust relationship. It routes quotes through Intel Trust Authority (ITA).
Serverless API
Section titled “Serverless API”| Route | Purpose |
|---|---|
POST /api/verify |
Accepts { node_url }, or a pasted { attestation, nonce_b64? } (the only way for a KMS, which listens only inside its VPC); submits the quote to ITA, verifies the returned JWT, and checks the nonce binding. |
GET /api/policy |
Proxies kms-attestation-policy.{profile}.json from a release (avoids CORS). |
GET /api/node-policy |
Proxies published-mrtds.json ({ profiles: {…} }) from a node release. |
GET /api/compat-map |
Proxies kms-compatibility-map.json (KMS release → node release, per-profile images and policy URLs) from a KMS release. |
api/verify.js fetches POST /admin-api/tee/attest (with { nonce } hex) from a
node, or takes a pasted KMS /attest response, then verifies the ITA appraisal JWT against Intel’s JWKS
(https://portal.trustauthority.intel.com/certs, issuer
https://portal.trustauthority.intel.com).
Security & configuration
Section titled “Security & configuration”Deployed on Vercel. Environment: ITA_API_KEY, ITA_APPRAISAL_URL (defaults to
the ITA v2 appraisal endpoint), and NODE_ALLOWED_HOSTS, a regex allowlist a
node URL must match before any outbound request is made (SSRF protection); the
address must also be public, unless NODE_ALLOW_PRIVATE=1 for local development. The
verifier never fetches from a KMS. A node request generates a fresh 32-byte nonce,
a pasted attestation is checked against the nonce_b64 supplied with it, and a
quote whose report_data does not carry the nonce is rejected. See
verification.
Release pipeline
Section titled “Release pipeline”Each version ships two artifact families from GitHub releases:
mero-kms-vX.Y.Z— per-profile attestation policies (kms-attestation-policy.<profile>.json), the compatibility map, the SBOM, checksums and Sigstore signatures. The KMS images (merotee-kms-<profile>-X-Y-Z) are kept in the GCP images project.mero-tee-vX.Y.Z—published-mrtds.json, release provenance, SBOM, checksums, Sigstore signatures.
Release assets are keyless-signed with Sigstore (GitHub OIDC) — no signing
key is stored in the repo (SECURITY.md). After a release, the
update-compatibility-catalog workflow refreshes the repo-root
compatibility-catalog.json, which maps each version to its KMS/node tags and
policy URLs:
{ "schema_version": 1, "releases": [ { "version": "2.3.x", "kms_tag": "mero-kms-v2.3.x", "node_image_tag": "mero-tee-v2.3.x", "kms_policy_url": "https://github.com/.../kms-attestation-policy.json", "node_policy_url": "https://github.com/.../published-mrtds.json" } ]}CI/CD lives in .github/workflows/ (release, staging probes, post-release e2e,
release auditor, security audit, version-sync guard). The
release pipeline and
policy management pages cover it in depth.