Skip to content

Commit b323f4f

Browse files
committed
kms: require attestation for all KMS flows
1 parent ad924cb commit b323f4f

11 files changed

Lines changed: 91 additions & 209 deletions

docs/deployment.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -459,8 +459,7 @@ Additional KMS instances can onboard from an existing KMS to share the same root
459459
```toml
460460
[core.onboard]
461461
enabled = true
462-
auto_bootstrap_domain = "" # Empty = onboard mode
463-
quote_enabled = true # Require TDX attestation
462+
auto_bootstrap_domain = "" # Empty = onboard mode # Require TDX attestation
464463
address = "0.0.0.0"
465464
port = 9203 # HTTP port for onboard UI
466465
```
@@ -480,7 +479,7 @@ curl http://<new-kms>:9203/finish
480479
# Restart KMS - it will now serve as a full KMS with shared keys
481480
```
482481

483-
> **Note:** For KMS onboarding with `quote_enabled = true`, add the KMS mrAggregated hash to your auth server's `kms.mrAggregated` whitelist.
482+
> **Note:** KMS onboarding requires attested KMS instances. Add the KMS `mrAggregated` hash to your auth server's `kms.mrAggregated` whitelist.
484483
485484
---
486485

docs/tutorials/kms-build-configuration.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,6 @@ url = "http://127.0.0.1:9200"
207207
[core.onboard]
208208
enabled = true
209209
auto_bootstrap_domain = ""
210-
quote_enabled = true
211210
address = "0.0.0.0"
212211
port = 9100
213212
EOF
@@ -495,7 +494,6 @@ enabled = true
495494
# Empty domain = manual bootstrap mode (ensures bootstrap-info.json is written)
496495
auto_bootstrap_domain = ""
497496
# Enable TDX quotes - works because KMS runs in CVM
498-
quote_enabled = true
499497
address = "0.0.0.0"
500498
port = 9100
501499
EOF

docs/tutorials/kms-cvm-deployment.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,6 @@ configs:
188188
[core.onboard]
189189
enabled = true
190190
auto_bootstrap_domain = ""
191-
quote_enabled = true
192191
address = "0.0.0.0"
193192
port = 9100
194193
EOF

docs/tutorials/troubleshooting-kms-deployment.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ export DSTACK_VMM_AUTH_PASSWORD=$(cat ~/.dstack/secrets/vmm-auth-token)
241241
"quote": null
242242
```
243243

244-
This indicates quote_enabled might be false, guest-agent issues, or **SGX not properly configured**:
244+
This indicates guest-agent issues, simulator misconfiguration, or **SGX not properly configured**:
245245

246246
```bash
247247
# Check CVM logs for TDX-related errors (replace VM_ID with actual ID from lsvm)
@@ -259,9 +259,7 @@ curl -s -H "Authorization: Bearer $(cat ~/.dstack/secrets/vmm-auth-token)" \
259259

260260
2. **SGX Auto MP Registration not enabled** - Without this BIOS setting, your platform isn't registered with Intel's PCS, and attestation quotes cannot be verified. Re-enter BIOS and enable "SGX Auto MP Registration".
261261

262-
3. **quote_enabled is false** - Verify your `kms.toml` has `quote_enabled = true` in the `[core.onboard]` section.
263-
264-
4. **Guest-agent not running** - The `/var/run/dstack.sock` socket must exist inside the CVM.
262+
3. **Guest-agent / simulator not running** - The KMS must be able to reach a working dstack guest agent endpoint. In a real CVM, `/var/run/dstack.sock` must exist. For local development, start `sdk/simulator` first.
265263

266264
### CVM Fails with "QGS error code: 0x12001"
267265

kms/dstack-app/compose-dev.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,5 @@ configs:
8080
[core.onboard]
8181
enabled = true
8282
auto_bootstrap_domain = ""
83-
quote_enabled = true
8483
address = "0.0.0.0"
8584
port = 8000

kms/dstack-app/compose-simple.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,5 @@ configs:
5656
[core.onboard]
5757
enabled = true
5858
auto_bootstrap_domain = ""
59-
quote_enabled = true
6059
address = "0.0.0.0"
6160
port = 8000

kms/kms.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,5 @@ gateway_app_id = "any"
4545
[core.onboard]
4646
enabled = true
4747
auto_bootstrap_domain = ""
48-
quote_enabled = true
4948
address = "0.0.0.0"
5049
port = 8000

kms/src/config.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,5 @@ pub(crate) struct Dev {
118118
#[derive(Debug, Clone, Deserialize)]
119119
pub(crate) struct OnboardConfig {
120120
pub enabled: bool,
121-
pub quote_enabled: bool,
122121
pub auto_bootstrap_domain: String,
123122
}

kms/src/main_service.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,6 @@ struct BootConfig {
9999

100100
impl RpcHandler {
101101
async fn ensure_self_allowed(&self) -> Result<()> {
102-
if !self.state.config.onboard.quote_enabled {
103-
return Ok(());
104-
}
105102
let boot_info = self
106103
.state
107104
.self_boot_info
@@ -355,9 +352,7 @@ impl KmsRpc for RpcHandler {
355352
self.ensure_self_allowed()
356353
.await
357354
.context("KMS self authorization failed")?;
358-
if self.state.config.onboard.quote_enabled {
359-
let _info = self.ensure_kms_allowed(&request.vm_config).await?;
360-
}
355+
let _info = self.ensure_kms_allowed(&request.vm_config).await?;
361356
Ok(KmsKeyResponse {
362357
temp_ca_key: self.state.inner.temp_ca_key.clone(),
363358
keys: vec![KmsKeys {

kms/src/onboard_service.rs

Lines changed: 62 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -60,29 +60,22 @@ impl RpcCall<OnboardState> for OnboardHandler {
6060

6161
impl OnboardRpc for OnboardHandler {
6262
async fn bootstrap(self, request: BootstrapRequest) -> Result<BootstrapResponse> {
63-
let quote_enabled = self.state.config.onboard.quote_enabled;
64-
if quote_enabled {
65-
ensure_self_kms_allowed(&self.state.config)
66-
.await
67-
.context("KMS is not allowed to bootstrap")?;
68-
}
69-
let keys = Keys::generate(&request.domain, quote_enabled)
63+
ensure_self_kms_allowed(&self.state.config)
64+
.await
65+
.context("KMS is not allowed to bootstrap")?;
66+
let keys = Keys::generate(&request.domain)
7067
.await
7168
.context("Failed to generate keys")?;
7269

7370
let k256_pubkey = keys.k256_key.verifying_key().to_sec1_bytes().to_vec();
7471
let ca_pubkey = keys.ca_key.public_key_der();
75-
let attestation = if quote_enabled {
76-
Some(attest_keys(&ca_pubkey, &k256_pubkey).await?)
77-
} else {
78-
None
79-
};
72+
let attestation = attest_keys(&ca_pubkey, &k256_pubkey).await?;
8073

8174
let cfg = &self.state.config;
8275
let response = BootstrapResponse {
8376
ca_pubkey,
8477
k256_pubkey,
85-
attestation: attestation.unwrap_or_default(),
78+
attestation,
8679
};
8780
// Store the bootstrap info
8881
safe_write(cfg.bootstrap_info(), serde_json::to_vec(&response)?)?;
@@ -101,7 +94,6 @@ impl OnboardRpc for OnboardHandler {
10194
&self.state.config,
10295
&source_url,
10396
&request.domain,
104-
self.state.config.onboard.quote_enabled,
10597
self.state.config.pccs_url.clone(),
10698
)
10799
.await
@@ -169,12 +161,12 @@ struct Keys {
169161
}
170162

171163
impl Keys {
172-
async fn generate(domain: &str, quote_enabled: bool) -> Result<Self> {
164+
async fn generate(domain: &str) -> Result<Self> {
173165
let tmp_ca_key = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)?;
174166
let ca_key = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)?;
175167
let rpc_key = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)?;
176168
let k256_key = SigningKey::random(&mut rand::rngs::OsRng);
177-
Self::from_keys(tmp_ca_key, ca_key, rpc_key, k256_key, domain, quote_enabled).await
169+
Self::from_keys(tmp_ca_key, ca_key, rpc_key, k256_key, domain).await
178170
}
179171

180172
async fn from_keys(
@@ -183,7 +175,6 @@ impl Keys {
183175
rpc_key: KeyPair,
184176
k256_key: SigningKey,
185177
domain: &str,
186-
quote_enabled: bool,
187178
) -> Result<Self> {
188179
let tmp_ca_cert = CertRequest::builder()
189180
.org_name("Dstack")
@@ -201,25 +192,20 @@ impl Keys {
201192
.key(&ca_key)
202193
.build()
203194
.self_signed()?;
204-
let attestation = if quote_enabled {
205-
let pubkey = rpc_key.public_key_der();
206-
let report_data = QuoteContentType::RaTlsCert.to_report_data(&pubkey);
207-
let response = app_attest(report_data.to_vec())
208-
.await
209-
.context("Failed to get quote")?;
210-
let attestation = VersionedAttestation::from_scale(&response.attestation)
211-
.context("Invalid attestation")?;
212-
Some(attestation)
213-
} else {
214-
None
215-
};
195+
let pubkey = rpc_key.public_key_der();
196+
let report_data = QuoteContentType::RaTlsCert.to_report_data(&pubkey);
197+
let response = app_attest(report_data.to_vec())
198+
.await
199+
.context("Failed to get quote")?;
200+
let attestation = VersionedAttestation::from_scale(&response.attestation)
201+
.context("Invalid attestation")?;
216202

217203
// Sign WWW server cert with KMS cert
218204
let rpc_cert = CertRequest::builder()
219205
.subject(domain)
220206
.alt_names(&[domain.to_string()])
221207
.special_usage("kms:rpc")
222-
.maybe_attestation(attestation.as_ref())
208+
.maybe_attestation(Some(&attestation))
223209
.key(&rpc_key)
224210
.build()
225211
.signed_by(&ca_cert, &ca_key)?;
@@ -239,54 +225,44 @@ impl Keys {
239225
cfg: &KmsConfig,
240226
other_kms_url: &str,
241227
domain: &str,
242-
quote_enabled: bool,
243228
pccs_url: Option<String>,
244229
) -> Result<Self> {
245-
let mut source_attestation_slot = None;
246-
let mut kms_client = if quote_enabled {
247-
let attestation_slot = Arc::new(Mutex::new(None::<VerifiedAttestation>));
248-
let attestation_slot_out = attestation_slot.clone();
249-
let client = RaClientConfig::builder()
250-
.tls_no_check(true)
251-
.remote_uri(other_kms_url.to_string())
252-
.cert_validator(Box::new(move |info: Option<CertInfo>| {
253-
let Some(info) = info else {
254-
bail!("Source KMS did not present a TLS certificate");
255-
};
256-
let Some(attestation) = info.attestation else {
257-
bail!("Source KMS certificate does not contain attestation");
258-
};
259-
let mut slot = attestation_slot_out
260-
.lock()
261-
.map_err(|_| anyhow::anyhow!("source attestation mutex poisoned"))?;
262-
*slot = Some(attestation);
263-
Ok(())
264-
}))
265-
.maybe_pccs_url(pccs_url.clone())
266-
.build()
267-
.into_client()?;
268-
source_attestation_slot = Some(attestation_slot);
269-
KmsClient::new(client)
270-
} else {
271-
KmsClient::new(RaClient::new(other_kms_url.into(), true)?)
272-
};
273-
274-
if quote_enabled {
275-
let tmp_ca = kms_client.get_temp_ca_cert().await?;
276-
let (ra_cert, ra_key) = gen_ra_cert(tmp_ca.temp_ca_cert, tmp_ca.temp_ca_key).await?;
277-
let ra_client = RaClient::new_mtls(other_kms_url.into(), ra_cert, ra_key, pccs_url)
278-
.context("Failed to create client")?;
279-
kms_client = KmsClient::new(ra_client);
280-
let source_attestation = source_attestation_slot
281-
.context("source attestation slot missing")?
282-
.lock()
283-
.map_err(|_| anyhow::anyhow!("source attestation mutex poisoned"))?
284-
.clone()
285-
.context("Missing source KMS attestation")?;
286-
ensure_remote_kms_allowed(cfg, &source_attestation)
287-
.await
288-
.context("Source KMS is not allowed for onboarding")?;
289-
}
230+
let attestation_slot = Arc::new(Mutex::new(None::<VerifiedAttestation>));
231+
let attestation_slot_out = attestation_slot.clone();
232+
let client = RaClientConfig::builder()
233+
.tls_no_check(true)
234+
.remote_uri(other_kms_url.to_string())
235+
.cert_validator(Box::new(move |info: Option<CertInfo>| {
236+
let Some(info) = info else {
237+
bail!("Source KMS did not present a TLS certificate");
238+
};
239+
let Some(attestation) = info.attestation else {
240+
bail!("Source KMS certificate does not contain attestation");
241+
};
242+
let mut slot = attestation_slot_out
243+
.lock()
244+
.map_err(|_| anyhow::anyhow!("source attestation mutex poisoned"))?;
245+
*slot = Some(attestation);
246+
Ok(())
247+
}))
248+
.maybe_pccs_url(pccs_url.clone())
249+
.build()
250+
.into_client()?;
251+
let mut kms_client = KmsClient::new(client);
252+
253+
let tmp_ca = kms_client.get_temp_ca_cert().await?;
254+
let (ra_cert, ra_key) = gen_ra_cert(tmp_ca.temp_ca_cert, tmp_ca.temp_ca_key).await?;
255+
let ra_client = RaClient::new_mtls(other_kms_url.into(), ra_cert, ra_key, pccs_url)
256+
.context("Failed to create client")?;
257+
kms_client = KmsClient::new(ra_client);
258+
let source_attestation = attestation_slot
259+
.lock()
260+
.map_err(|_| anyhow::anyhow!("source attestation mutex poisoned"))?
261+
.clone()
262+
.context("Missing source KMS attestation")?;
263+
ensure_remote_kms_allowed(cfg, &source_attestation)
264+
.await
265+
.context("Source KMS is not allowed for onboarding")?;
290266

291267
let info = dstack_client().info().await.context("Failed to get info")?;
292268
let keys_res = kms_client
@@ -308,15 +284,7 @@ impl Keys {
308284
KeyPair::from_pem(&tmp_ca_key_pem).context("Failed to parse tmp CA key")?;
309285
let ecdsa_key =
310286
SigningKey::from_slice(&root_k256_key).context("Failed to parse ECDSA key")?;
311-
Self::from_keys(
312-
tmp_ca_key,
313-
ca_key,
314-
rpc_key,
315-
ecdsa_key,
316-
domain,
317-
quote_enabled,
318-
)
319-
.await
287+
Self::from_keys(tmp_ca_key, ca_key, rpc_key, ecdsa_key, domain).await
320288
}
321289

322290
fn store(&self, cfg: &KmsConfig) -> Result<()> {
@@ -360,16 +328,9 @@ pub(crate) async fn update_certs(cfg: &KmsConfig) -> Result<()> {
360328
let domain = domain.trim();
361329

362330
// Regenerate certificates using existing keys
363-
let keys = Keys::from_keys(
364-
tmp_ca_key,
365-
ca_key,
366-
rpc_key,
367-
k256_key,
368-
domain,
369-
cfg.onboard.quote_enabled,
370-
)
371-
.await
372-
.context("Failed to regenerate certificates")?;
331+
let keys = Keys::from_keys(tmp_ca_key, ca_key, rpc_key, k256_key, domain)
332+
.await
333+
.context("Failed to regenerate certificates")?;
373334

374335
// Write the new certificates to files
375336
keys.store_certs(cfg)?;
@@ -378,17 +339,12 @@ pub(crate) async fn update_certs(cfg: &KmsConfig) -> Result<()> {
378339
}
379340

380341
pub(crate) async fn bootstrap_keys(cfg: &KmsConfig) -> Result<()> {
381-
if cfg.onboard.quote_enabled {
382-
ensure_self_kms_allowed(cfg)
383-
.await
384-
.context("KMS is not allowed to auto-bootstrap")?;
385-
}
386-
let keys = Keys::generate(
387-
&cfg.onboard.auto_bootstrap_domain,
388-
cfg.onboard.quote_enabled,
389-
)
390-
.await
391-
.context("Failed to generate keys")?;
342+
ensure_self_kms_allowed(cfg)
343+
.await
344+
.context("KMS is not allowed to auto-bootstrap")?;
345+
let keys = Keys::generate(&cfg.onboard.auto_bootstrap_domain)
346+
.await
347+
.context("Failed to generate keys")?;
392348
keys.store(cfg)?;
393349
Ok(())
394350
}

0 commit comments

Comments
 (0)