Skip to content

Commit e8b036d

Browse files
authored
Merge pull request #160 from Dstack-TEE/api-extend-rtmr3
2 parents c824569 + 94c7249 commit e8b036d

File tree

7 files changed

+148
-3
lines changed

7 files changed

+148
-3
lines changed

guest-agent/rpc/proto/agent_rpc.proto

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ service DstackGuest {
3939
// Generates a TDX quote with given report data.
4040
rpc GetQuote(RawQuoteArgs) returns (GetQuoteResponse) {}
4141

42+
// Emit an event. This extends the event to RTMR3 on TDX platform.
43+
rpc EmitEvent(EmitEventArgs) returns (google.protobuf.Empty) {}
44+
4245
// Get worker info
4346
rpc Info(google.protobuf.Empty) returns (WorkerInfo) {}
4447
}
@@ -161,6 +164,13 @@ message GetQuoteResponse {
161164
bytes report_data = 3;
162165
}
163166

167+
message EmitEventArgs {
168+
// The event name
169+
string event = 1;
170+
// The event data
171+
bytes payload = 2;
172+
}
173+
164174
// The request to derive a key
165175
message WorkerInfo {
166176
// App ID

guest-agent/src/rpc_service.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use dstack_guest_agent_rpc::{
66
dstack_guest_server::{DstackGuestRpc, DstackGuestServer},
77
tappd_server::{TappdRpc, TappdServer},
88
worker_server::{WorkerRpc, WorkerServer},
9-
DeriveK256KeyResponse, DeriveKeyArgs, GetKeyArgs, GetKeyResponse, GetQuoteResponse,
10-
GetTlsKeyArgs, GetTlsKeyResponse, RawQuoteArgs, TdxQuoteArgs, TdxQuoteResponse, WorkerInfo,
11-
WorkerVersion,
9+
DeriveK256KeyResponse, DeriveKeyArgs, EmitEventArgs, GetKeyArgs, GetKeyResponse,
10+
GetQuoteResponse, GetTlsKeyArgs, GetTlsKeyResponse, RawQuoteArgs, TdxQuoteArgs,
11+
TdxQuoteResponse, WorkerInfo, WorkerVersion,
1212
};
1313
use dstack_types::AppKeys;
1414
use fs_err as fs;
@@ -161,6 +161,13 @@ impl DstackGuestRpc for InternalRpcHandler {
161161
})
162162
}
163163

164+
async fn emit_event(self, request: EmitEventArgs) -> Result<()> {
165+
if self.state.config().simulator.enabled {
166+
return Ok(());
167+
}
168+
tdx_attest::extend_rtmr3(&request.event, &request.payload)
169+
}
170+
164171
async fn info(self) -> Result<WorkerInfo> {
165172
ExternalRpcHandler { state: self.state }.info().await
166173
}

sdk/curl/api.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,33 @@ curl --unix-socket /var/run/dstack.sock http://dstack/Info
165165
}
166166
```
167167

168+
### 5. Emit Event
169+
170+
Emit an event to be extended to RTMR3 on TDX platform. This API requires Dstack OS 0.5.0 or later.
171+
172+
**Endpoint:** `/EmitEvent`
173+
174+
**Request Parameters:**
175+
176+
| Field | Type | Description | Example |
177+
|-------|------|-------------|----------|
178+
| `event` | string | The event name | `"custom-event"` |
179+
| `payload` | string | Hex-encoded payload data | `"deadbeef"` |
180+
181+
**Example:**
182+
```bash
183+
curl --unix-socket /var/run/dstack.sock -X POST \
184+
http://dstack/EmitEvent \
185+
-H 'Content-Type: application/json' \
186+
-d '{
187+
"event": "custom-event",
188+
"payload": "deadbeef"
189+
}'
190+
```
191+
192+
**Response:**
193+
Empty response with HTTP 200 status code on success.
194+
168195
## Error Responses
169196

170197
All endpoints may return the following HTTP status codes:

sdk/go/dstack/client.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,3 +417,15 @@ func (c *DstackClient) Info(ctx context.Context) (*InfoResponse, error) {
417417

418418
return &response, nil
419419
}
420+
421+
// EmitEvent sends an event to be extended to RTMR3 on TDX platform.
422+
// The event will be extended to RTMR3 with the provided name and payload.
423+
//
424+
// Requires Dstack OS 0.5.0 or later.
425+
func (c *DstackClient) EmitEvent(ctx context.Context, event string, payload []byte) error {
426+
_, err := c.sendRPCRequest(ctx, "/EmitEvent", map[string]interface{}{
427+
"event": event,
428+
"payload": hex.EncodeToString(payload),
429+
})
430+
return err
431+
}

sdk/js/src/index.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,4 +314,28 @@ export class DstackClient {
314314
tcb_info: JSON.parse(result.tcb_info) as TcbInfo,
315315
})
316316
}
317+
318+
/**
319+
* Emit an event. This extends the event to RTMR3 on TDX platform.
320+
*
321+
* Requires Dstack OS 0.5.0 or later.
322+
*
323+
* @param event The event name
324+
* @param payload The event data as string or Buffer or Uint8Array
325+
*/
326+
async emitEvent(event: string, payload: string | Buffer | Uint8Array): Promise<void> {
327+
if (!event) {
328+
throw new Error('Event name cannot be empty')
329+
}
330+
331+
const hexPayload = to_hex(payload)
332+
await send_rpc_request(
333+
this.endpoint,
334+
'/EmitEvent',
335+
JSON.stringify({
336+
event: event,
337+
payload: hexPayload
338+
})
339+
)
340+
}
317341
}

sdk/python/src/dstack_sdk/dstack_client.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,33 @@ def info(self) -> InfoResponse:
203203
result = self._send_rpc_request("/Info", {})
204204
return InfoResponse.model_validate(result)
205205

206+
def emit_event(
207+
self,
208+
event: str,
209+
payload: str | bytes,
210+
) -> None:
211+
"""
212+
Emit an event. This extends the event to RTMR3 on TDX platform.
213+
214+
Requires Dstack OS 0.5.0 or later.
215+
216+
Args:
217+
event: The event name
218+
payload: The event data as string or bytes
219+
220+
Returns:
221+
None
222+
"""
223+
if not event:
224+
raise ValueError("event name cannot be empty")
225+
226+
if isinstance(payload, str):
227+
payload = payload.encode()
228+
229+
hex_payload = binascii.hexlify(payload).decode()
230+
self._send_rpc_request("/EmitEvent", {"event": event, "payload": hex_payload})
231+
return None
232+
206233
def get_tls_key(
207234
self,
208235
subject: str | None = None,
@@ -285,6 +312,33 @@ async def info(self) -> InfoResponse:
285312
result = await self._send_rpc_request("/Info", {})
286313
return InfoResponse.model_validate(result)
287314

315+
async def emit_event(
316+
self,
317+
event: str,
318+
payload: str | bytes,
319+
) -> None:
320+
"""
321+
Emit an event. This extends the event to RTMR3 on TDX platform.
322+
323+
Requires Dstack OS 0.5.0 or later.
324+
325+
Args:
326+
event: The event name
327+
payload: The event data as string or bytes
328+
329+
Returns:
330+
None
331+
"""
332+
if not event:
333+
raise ValueError("event name cannot be empty")
334+
335+
if isinstance(payload, str):
336+
payload = payload.encode()
337+
338+
hex_payload = binascii.hexlify(payload).decode()
339+
await self._send_rpc_request("/EmitEvent", {"event": event, "payload": hex_payload})
340+
return None
341+
288342
async def get_tls_key(
289343
self,
290344
subject: str | None = None,

tdx-attest/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,14 @@ pub type TdxReportData = [u8; 64];
2020

2121
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2222
pub struct TdxReport(pub [u8; 1024]);
23+
24+
pub fn extend_rtmr3(event: &str, payload: &[u8]) -> anyhow::Result<()> {
25+
use anyhow::Context;
26+
// This code is not defined in the TCG specification.
27+
// See https://trustedcomputinggroup.org/wp-content/uploads/PC-ClientSpecific_Platform_Profile_for_TPM_2p0_Systems_v51.pdf
28+
let event_type = 0x08000001;
29+
let index = 3;
30+
let log = eventlog::TdxEventLog::new(index, event_type, event.to_string(), payload.to_vec());
31+
extend_rtmr(index, event_type, log.digest).context("Failed to extend RTMR")?;
32+
log_rtmr_event(&log).context("Failed to log RTMR event")
33+
}

0 commit comments

Comments
 (0)