Skip to content

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/ · 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.

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.

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.

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.

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.

Driven by playbook.yml and mero-tee/versions.json:

  • merod, meroctl, and mero-auth at a pinned core tag (via the calimero-core role).
  • Traefik reverse proxy, plus node-exporter, vmagent, and vector for metrics/logs.
  • The fleet HA sidecar systemd service (ansible/roles/merotee/templates/fleet-sidecar.sh.j2).
  • A profile marker at /etc/calimero/image-profile and a calimero.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.
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.

Terminal window
packer build -var-file=ubuntu-x86.pkrvars.hcl ubuntu.pkr.hcl

Release 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/ · 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).

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).

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.

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.