@@ -28,7 +28,7 @@ current dstack guest agents: `quote`, `event_log`, and `vm_config`.
2828 coordinator admission broker.
29294 . The worker builds a binding statement containing the claimed
3030 identity, peer id, dstack app id, instance id, and compose hash.
31- 5 . The worker calls dstack ` GetQuote(SHA-512 (binding || nonce)) ` and
31+ 5 . The worker calls dstack ` GetQuote(SHA-256 (binding || nonce)) ` and
3232 sends ` {identity, binding, nonce, quote, event_log, vm_config} ` to
3333 the broker.
34346 . The broker verifies the quote through ` dstack-verifier ` , checks
@@ -38,6 +38,80 @@ current dstack guest agents: `quote`, `event_log`, and `vm_config`.
3838 agents receive a node-identity token; service workloads receive
3939 service-identity tokens plus any declared Consul permissions.
4040
41+ ## Report Data
42+
43+ ` report_data ` is an unstructured SHA-256 digest. The structured
44+ admission data stays outside the quote in the binding statement:
45+
46+ ``` text
47+ report_data = SHA-256(binding_statement_json || broker_nonce)
48+ ```
49+
50+ The broker nonce is 32 random bytes encoded as hex. The binding
51+ statement is JSON generated by the workload sidecar:
52+
53+ ``` json
54+ {
55+ "version" : 1 ,
56+ "identity" : " spiffe://demo/postgres" ,
57+ "cluster" : " demo" ,
58+ "peer_id" : " worker-3" ,
59+ "app_id" : " <dstack app id>" ,
60+ "instance_id" : " <dstack instance id>" ,
61+ "compose_hash" : " <dstack compose hash>" ,
62+ "issued_at" : " 2026-05-18T00:00:00Z"
63+ }
64+ ```
65+
66+ The client checks that dstack ` GetQuote() ` returns the same
67+ ` report_data ` it requested. The broker independently recomputes the
68+ digest from the submitted binding statement and nonce, then compares
69+ that digest with the verifier-returned ` report_data ` .
70+
71+ ## Peer IDs
72+
73+ ` peer_id ` is the mesh node label, not a secret and not a hardware
74+ identity. In this example it is derived from the local role and
75+ ordinal, such as ` worker-3 ` , and reused as the Consul node name.
76+
77+ The current binding is:
78+
79+ ``` text
80+ peer_id -> binding statement -> SHA-256 report_data -> TDX quote
81+ ```
82+
83+ The broker also requires the ` peer_id ` to match the Terraform policy
84+ row before it issues a node token or a service token scoped to that
85+ node. This prevents one attested workload row from directly claiming
86+ another row's Consul node identity.
87+
88+ For stronger production binding, the policy should also pin a
89+ platform-stable workload instance field, such as a Phala-provided
90+ instance id or app id when that can be supplied without introducing
91+ a deployment cycle. Until then, ` peer_id ` is best understood as an
92+ authorization namespace declared by Terraform and quoted by the CVM.
93+
94+ ## Evidence Policy
95+
96+ The broker treats ` dstack-verifier ` as the authority for quote and
97+ event-log semantics. The broker should require verifier outputs such
98+ as:
99+
100+ - ` quote_verified == true `
101+ - ` event_log_verified == true `
102+ - ` report_data ` present and equal to the broker-computed digest
103+ - ` app_info.compose_hash ` present and policy-allowed
104+
105+ KMS identity should be policy-controlled because it defines the key
106+ lifecycle. A production policy should pin the long-term KMS public
107+ key or an equivalent well-known KMS identity that is hard-coded in
108+ Terraform and checked against verified dstack evidence.
109+
110+ For production profiles, OS image identity should also be checked.
111+ The expected OS image hash should be both a Terraform launch setting
112+ and an admission policy field, so a workload cannot satisfy the
113+ compose policy while running on an unexpected base image.
114+
41115## Runtime Checks
42116
43117From a coordinator CVM, inspect Consul membership:
0 commit comments