Prerequisites
- Familiarity with TEEs and Intel TDX
- An Intel Trust Authority API key, or a client library with a built-in local DCAP verifier
The attestation flow
1
The CVM mints an RA-TLS certificate at boot
On startup the TeeSQL sidecar calls
GetTlsKey(usage_ra_tls=true, usage_server_auth=true) on the dstack guest agent over /var/run/dstack.sock. The guest agent:- Generates a fresh keypair
- Asks the Intel TDX module for an attestation quote with
REPORTDATA = SHA-256(public key bytes) - Builds a self-signed X.509 leaf certificate whose Subject Public Key Info matches that public key
- Embeds the attestation in a custom certificate extension (Phala RA-TLS OID
1.3.6.1.4.1.62397.1.8for the current SCALE-encodedVersionedAttestation; legacy raw-quote bytes also published at OID1.3.6.1.4.1.62397.1.1for backward compatibility) - Returns the leaf, intermediates, and the dstack KMS root certificate as a PEM chain
REPORTDATA is what prevents a man-in-the-middle: any substitute certificate would have a different public key, and the quote would no longer match.2
The sidecar serves the cert on :5433
The sidecar parses the chain and private key into rustls types, configures a TLS acceptor that requires client certificates by default in production (
TEESQL_REQUIRE_CLIENT_ATTESTATION=true), and listens on 0.0.0.0:5433. Plain Postgres on 127.0.0.1:5432 is reachable only from inside the CVM.The same RA-TLS pattern is also exposed over HTTP at :8080 — GET /attestation returns the live attestation report described below.3
The client connects and extracts the quote
During the TLS handshake the client receives the server’s self-signed leaf and chain. A standard CA-based check would reject the leaf — RA-TLS clients deliberately disable the default chain check (e.g. node-postgres
rejectUnauthorized: false) and switch to attestation-based trust:- Take the DER bytes of the leaf certificate
- Walk the X.509 extensions for OID
1.3.6.1.4.1.62397.1.8(current SCALE-encoded attestation), falling back to OID1.3.6.1.4.1.62397.1.1(legacy raw quote) for backward compatibility - Strip the DER
OCTET STRINGwrapper and decode if needed - The remaining bytes are the raw TDX quote
4
The client verifies the quote
Verification has two paths, and TeeSQL client libraries implement both:Intel Trust Authority (hosted, REST). The client
POSTs the base64-encoded quote to https://api.trustauthority.intel.com/appraisal/v2/attest with an API key, receives a signed JWT, fetches Intel’s JWKS at the URL named in the JWT header (jku), and verifies the JWT signature with PS384/RS256. Used by IntelApiVerifier in prisma-ra-tls, psycopg-ra-tls, and sqlx-ra-tls.Local DCAP. The client uses dcap-qvl (a Rust quote-verification library) with Intel’s public PCS or a self-hosted PCCS for platform collateral. No external API call required. Used by DcapVerifier in sqlx-ra-tls (default).Either path produces the same trust decision. Pick ITA when you want a managed verifier and don’t mind a third-party dependency on the connection path; pick DCAP when you want self-contained verification or air-gapped operation.5
The client checks specific claims
The verifier returns a structured result; the client library then enforces:
A failure at any step is a hard refusal: the client library does not pass any bytes to the underlying Postgres driver. From the application’s point of view, a verification failure surfaces as an ordinary connection error.
6
The result is cached, then bytes flow
On success the verification result is cached (default 1 hour in
prisma-ra-tls; configurable via cacheTtlMs) so subsequent connections in the same process don’t hit Intel Trust Authority again. The client library then bridges the now-attested TLS channel to the application’s Postgres driver, and SQL can flow normally.What is in an attestation report
The sidecar’sGET /attestation endpoint returns a JSON document with the following fields:
The quote itself binds the request to the database’s current state:
REPORTDATA for /attestation is SHA-256(wal_lsn || controldata_hash || timestamp). So the same endpoint produces a fresh quote on every call, and the quote attests not just the CVM identity but a snapshot of Postgres’s live state at that moment.
For the TLS handshake quote (different from /attestation), REPORTDATA is SHA-256(public key), which binds the TLS session to the attested identity but does not commit to Postgres state.
Where expected measurements come from
To pin a measurement (allowedMrTd, etc.), you need a known-good value to compare against. There are three production-relevant sources:
- MRTD is determined by the CVM image. When the operator cuts a new image, they republish the expected MRTD; clients update their allowlist together with their deployment.
- RTMR1 and RTMR2 are determined by the guest kernel and boot parameters. They change when the kernel image or boot configuration changes.
- RTMR3 is determined by the application layer — most importantly the
compose_hash. It changes whenever the compose definition changes, which in practice means whenever the sidecar or Postgres image is upgraded.