MAQPNADocs

Attestation service#

The attestation service (maqpna-attest) is the key broker for tier-2 sessions. It releases a session token to a confidential VM (AMD SEV-SNP or Intel TDX) only after the VM proves, with a hardware-signed report, that it runs a measurement you approved with debug disabled. The token is sealed to an ephemeral key that exists only inside that VM, so it never touches etcd, a Kubernetes Secret, a TLS terminator or the host.

Its partner, maqpna-attest-agent, runs inside the sandbox in three modes: init (redeem the release before the agent starts), sidecar (re-attest to renew the token) and bootstrap (warm-pool claims).

Code: cmd/maqpna-attest, cmd/maqpna-attest-agent, pkg/attest.

Neighbours#

flowchart LR
    OP["Operator"] -- "POST /v1/releases<br/>GET /v1/releases/{id}" --> AT
    subgraph CVM["tier-2 sandbox (confidential VM)"]
      INIT["attest-agent --mode init"]
      REN["attest-agent --mode sidecar"]
      TSM["configfs-tsm<br/>/sys/kernel/config/tsm/report"]
      AG["agent container<br/>/var/run/maqpna/token (read-only)"]
    end
    INIT -- "POST /v1/releases/{id}/attest" --> AT
    REN -- "GET /v1/renewals/{id}/challenge<br/>POST /v1/renewals/{id}" --> AT
    INIT & REN --> TSM
    subgraph ATS["maqpna-attest :8082"]
      AT["API"]
      J[("releases and renewals<br/>memory, file journal or Postgres")]
    end
    AT -- "POST /attestation" --> TR["Trustee AS<br/>(customer-run)"]
    AT -- "POST /v1/token" --> IB["Identity broker"]
    AT --> J

Responsibilities#

Endpoint Caller What it does
POST /v1/releases Operator (bearer MAQPNA_BROKER_TOKEN) Register a single-use release {namespace, agent, session, user, scopes, tier, ttlSeconds, nonce, renewUntil}, valid 15 minutes
POST /v1/releases/{id}/attest attest-agent in the VM Verify evidence, appraise, mint, seal and return the token; the release is consumed
GET /v1/releases/{id} Operator {state: pending, attesting, released, failed, reason}, so a failed attestation fails the session (AttestationFailed)
GET /v1/renewals/{id}/challenge Renewal sidecar A fresh single-use nonce (5 minutes)
POST /v1/renewals/{id} Renewal sidecar Re-attest with the same key and measurement; a new token, capped at renewUntil; 410 afterwards
GET /healthz, GET /metrics Probes, Prometheus

How a release works#

  1. The operator generates a 32-byte nonce and registers a release for the session.
  2. The sandbox starts with the init container. It generates an ephemeral X25519 key and puts SHA-512(nonce ‖ public key) in the report data of a fresh TEE report (configfs-tsm, providers sev_guest or tdx_guest).
  3. It posts the evidence to /v1/releases/{id}/attest.
  4. The service checks that the release is pending and unexpired and the nonce matches, then sends the evidence to Trustee and verifies the returned token's signature, expiry, issuer and report data.
  5. It appraises the claims against the reference values: TEE type, launch measurement, workload values (SNP HOST_DATA, TDX MRCONFIGID and RTMR), debug off, minimum SVN.
  6. Only then it asks the identity broker for the token, seals it (X25519 → HKDF-SHA256 → AES-256-GCM, additional data maqpna-release:<id>), journals the release as consumed and returns the envelope.
  7. The init container opens the envelope, writes the token with mode 0400 to the tmpfs at /var/run/maqpna/token and exits; the agent container starts.
  8. The renewal sidecar re-attests at 80% of the token lifetime until renewUntil.

Any verification or appraisal failure returns 403 {reason} and consumes the release, so it cannot be retried with different evidence. Only verifier (503) and broker (502) outages return the release to pending. The full sequence is in attestation-gated secrets.

Configuration#

maqpna-attest flags:

Flag Default Purpose
-listen :8082 API
-broker-url http://maqpna-identity.maqpna-system.svc:8081 Identity broker
-verifier trustee trustee, or sample (DEV ONLY)
-trustee-url, -trustee-path, -trustee-jwks, -trustee-issuer —, /attestation, —, — Trustee attestation service
-trustee-max-token-age, -trustee-allow-missing-debug-claim 10m, false Token freshness; a missing debug claim is a rejection
-reference-values required Allowed measurements and workload values (empty list rejects everything)
-renew-min-interval 30s Rate limit on renewals
-response-signing-key, -tls-cert, -tls-key — Signed responses and TLS (recommended)
-store-path — (memory) File journal
-state-backend, -state-dsn-file, -state-schema, -state-max-conns file, —, maqpna, 10 Shared PostgreSQL state

maqpna-attest-agent: --mode init|sidecar|bootstrap, --tee auto|snp|tdx|sample (auto never falls back to sample), --out (/var/run/maqpna/token), --state-dir (/var/run/maqpna-attest, a memory-backed volume mounted only into the attest containers), --keep-key-for-renewal, --renew-at (0.8), --max-wait (60 s), --ca-file, --attest-response-pubkey. Environment: MAQPNA_ATTEST_URL, MAQPNA_RELEASE_ID, MAQPNA_RELEASE_NONCE.

Helm: attestation.enabled, attestation.replicas, attestation.verifier, attestation.trusteeURL, attestation.trusteeJWKS, attestation.referenceValues, attestation.responseSigning, attestation.pdb.

Failure modes#

Failure Effect
Wrong measurement, debug on, low SVN, bad signature, nonce mismatch or replay 403; release consumed; session Failed (AttestationFailed)
Trustee unreachable 503; release back to pending; the init container retries for up to about 60 s
Identity broker unreachable 502; same as above
Attestation service restart, memory store Pending releases and renewals lost; the operator must re-register; renewal sidecars exit 1 on 404
PostgreSQL unreachable (shared state) Attestation calls refused with reason store (renewals 503)
Renewal after renewUntil 410 Gone; the sidecar exits 0 and keeps the current token

Scaling#

With the default file or memory store the service must run as one replica: the chart refuses attestation.replicas > 1 unless state.backend: postgres. On Postgres, releases, in-flight claims (2-minute lease), challenges and renewals are journaled in attest-releases and claimed under a cluster-wide lock, so any replica serves any step and each release is used once. values-production.yaml and values-sovereign-eu.yaml run two replicas.

Metrics#

maqpna_attest_success_total, maqpna_attest_failure_total{reason}, maqpna_attest_releases_created_total, maqpna_attest_releases_pending, maqpna_attest_renewal_success_total, maqpna_attest_renewal_failure_total{reason}, maqpna_attest_renewals_active. Alert: MaqpnaAttestationFailureRate. Failure reasons are malformed, nonce, binding, signature, tee, measurement, debug, svn, verifier-unavailable and expired.

What you see#

maqpna doctor reports the attestation posture with the checks attest-verifier (fails on the sample verifier) and attest-response-signing. A failed release shows on the session:

$ maqpna session describe -n team-a fix-4821
Name:          fix-4821
Namespace:     team-a
Agent:         coder
User:          alice@acme.eu
Phase:         Failed
SPIFFE ID:     -
Scopes:        tools:github
Created:       2026-10-02T14:05:09Z
Ready:         -
Expires:       2026-10-02T16:05:09Z
Outcome:       Failed (AttestationFailed)
Attestation:   required, release 9c1f2e7a4b8d03c6a1e5f7b20d9c4e81
Taint:         -

Conditions:
  TYPE        STATUS  REASON             MESSAGE
  TokenReady  False   AttestationFailed  attestation failed: measurement
...

The field layout comes from cmd/maqpna/session_gateway.go; values are illustrative and the output is abbreviated. See maqpna session describe and maqpna doctor.