TEE nodes
A TEE node proves what runs inside its trust domain (TD) with an attestation
quote. The quote says nothing about who reads your traffic on the way there:
TLS in front of a node usually ends at a proxy, load balancer or relay outside
the TD, and everything crossing it is readable there, bearer tokens included.
sealed=True closes that gap. The client verifies the node’s quote, then
encrypts every request to a key the quote commits to, so only the attested TD
can read it, whatever sits in front.
Deciding which TDs you trust
Section titled “Deciding which TDs you trust”A TeePolicy says which TDs to trust. The quote is checked here, against
Intel’s collateral, and never by asking the node:
from pathlib import Path
from calimero_client_py import TeePolicy
# The node release's published-mrtds.json, from a release you verified.release = Path("trusted/mero-tee-v2.3.78.published-mrtds.json").read_text()policy = TeePolicy.from_releases([release], "locked-read-only")A policy names the image by all of its measurements: the MRTD and RTMR1–3,
each required. The MRTD alone names no image. It measures the platform’s TD
firmware, which on GCP every mero-tee image, profile and release shares, and so
does any other TD there, whatever it runs. The image is in RTMR1–3: the kernel,
the command line with the root-filesystem hash, and the role and profile the
image records at boot. A policy that leaves any of them out raises
ValueError. Mock quotes are never accepted.
TeePolicy.from_releases(releases, profile) reads each release’s
published-mrtds.json:
- one profile:
locked-read-onlyfor production. Adebugimage of the same release has other RTMRs and is refused; - every release your nodes may run. Each release has its own RTMRs, so during a rollout pass the old and the new one, and drop the old one once no node runs it;
- only the TCB statuses every one of them accepts, which the release lists
(today
UpToDateandOutOfDate: GCP’s TDX hosts reportOutOfDate).
Registers are allowed one by one, so a policy of two releases accepts any mix of
their registers. Trust only releases you would accept any mix of. Where the file
comes from is the trust decision: take it from a release you verified
(cosign verify-blob against its .bundle.json), not from the node.
You can also give the lists yourself:
policy = TeePolicy( allowed_mrtd=[mrtd], allowed_rtmr1=[rtmr1], allowed_rtmr2=[rtmr2], allowed_rtmr3=[rtmr3], allowed_tcb_statuses=["UpToDate", "OutOfDate"],)To also require that the node runs a particular application, name it with the bytecode hash it must have:
policy = TeePolicy.from_releases( [release], "locked-read-only", application_id=app_id, application_hash=bytecode_hash)Sealed requests
Section titled “Sealed requests”from calimero_client_py import create_connection, create_client
connection = create_connection( "https://relay.example/node", node_name="my-tee-node", tee=policy, sealed=True,)client = create_client(connection)On the first request the node binds its X25519 transport key into a fresh
quote, and the connection seals nothing until that quote verifies under your
policy. A request that fails attestation raises RuntimeError and is never
sent. After that, every request is sealed in a session to the attested key, and
so is its response. Token refreshes are sealed too. A proxy carries traffic it
cannot read, forge or replay. Each session’s keys come from throwaway keys on
both sides, so once a session ends, at most an hour later, what was sent in it
cannot be opened, even with the node’s transport key.
When the node restarts, it has a new transport key. The connection attests again, through the same policy, before it resends.
WebSockets cannot be sealed. A sealed connection is for the request/response API this client uses.
Nodes behind an auth proxy
Section titled “Nodes behind an auth proxy”What a sealed request can reach depends on who checks requests. A node running
embedded auth checks them itself, so everything this client calls can be
sealed. A node whose auth is left to a proxy (auth_mode = "proxy", which is
how hosted TEE nodes run) cannot let an opened request reach a route the proxy
guards: the proxy sees only POST /sealed/v2, never the route inside. So such a
node serves, sealed, only what it serves without a credential: the health and
TEE probes, and a context’s .../intents where delegated execution is public.
Anything else is refused inside the envelope with 403 and
sealed_route_unguarded, and is never executed. Use sealed=True against
embedded-auth nodes. Against a hosted node, the sealed path is delegated
execution. See
how a client reaches a node.
A key verified some other way
Section titled “A key verified some other way”If you verified a node’s transport key yourself, pass it instead of a policy:
create_connection(url, sealed=True, transport_public_key="<hex>")A connection with a fixed key cannot follow a restart. Its requests then fail until you reconnect with the node’s new key.
create_connection refuses combinations that would not protect anything: a
key or a policy without sealed=True, or sealed=True with neither.