Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,35 @@ Thank you for your interest in contributing to this project!
5. Push to the branch (`git push origin feature/amazing-feature`)
6. Open a Pull Request

### Test without TEE

Maintainers can run a development image without TDX to test VMM and guest changes.

> [!WARNING]
> A no-TEE VM has no hardware isolation or attestation. Its disk is unencrypted, and its temporary app keys are not stable across boots. Do not use this mode for production workloads or secrets.

Create an app compose file without KMS, Gateway, or TEE requirements:

```bash
python3 vmm/src/vmm-cli.py compose \
--name no-tee-dev \
--docker-compose ./docker-compose.yml \
--key-provider none \
--output ./app-compose.json
```

Deploy it with an image whose metadata sets `is_dev` to `true`:

```bash
python3 vmm/src/vmm-cli.py deploy \
--name no-tee-dev \
--image "YOUR_DEVELOPMENT_IMAGE" \
--compose ./app-compose.json \
--no-tee
```

Attestation and runtime measurement APIs return errors in this mode. TEE and storage modes cannot change after VM creation; recreate the VM to switch modes.

## Commit Convention

This project uses [Conventional Commits](https://www.conventionalcommits.org/). Please format your commit messages as:
Expand Down
21 changes: 19 additions & 2 deletions basefiles/dstack-prepare.sh
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,22 @@ log "Syncing system time..."
# Let the chronyd correct the system time immediately; keep booting if chronyd is not ready yet.
chronyc makestep || log "Warning: chronyc makestep failed; continuing"

if [[ -e /dev/sev-guest ]] || grep -qw sev_guest /sys/kernel/config/tsm/report/*/provider 2>/dev/null; then
case "$(get_cmdline_value dstack.no_tee || true)" in
1 | true | yes | on)
NO_TEE=true
;;
0 | false | no | off | "")
NO_TEE=false
;;
*)
log "Error: invalid dstack.no_tee value"
exit 1
;;
esac

if $NO_TEE; then
log "Development-only no-TEE mode enabled"
elif [[ -e /dev/sev-guest ]] || grep -qw sev_guest /sys/kernel/config/tsm/report/*/provider 2>/dev/null; then
log "SEV-SNP guest device/TSM provider detected"
elif [[ -e /dev/tdx_guest ]]; then
log "TDX guest device detected"
Expand Down Expand Up @@ -127,7 +142,9 @@ setup_tsm() {
mkdir -p /sys/kernel/config/tsm/report/com.intel.dcap
fi
}
setup_tsm || true
if ! $NO_TEE; then
setup_tsm || true
fi

# Setup dstack system
log "Preparing dstack system..."
Expand Down
40 changes: 38 additions & 2 deletions cert-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
//
// SPDX-License-Identifier: Apache-2.0

use anyhow::{Context, Result};
use std::time::{Duration, UNIX_EPOCH};

use anyhow::{bail, Context, Result};
use dstack_kms_rpc::{kms_client::KmsClient, SignCertRequest};
use dstack_types::{AppKeys, KeyProvider};
use ra_rpc::client::{RaClient, RaClientConfig};
use ra_tls::cert::{generate_ra_cert, CaCert, CertSigningRequestV2};
use ra_tls::{
cert::{generate_ra_cert, CaCert, CertConfigV2, CertRequest, CertSigningRequestV2},
rcgen::SubjectPublicKeyInfo,
};

pub enum CertRequestClient {
Local {
Expand All @@ -19,6 +24,37 @@ pub enum CertRequestClient {
}

impl CertRequestClient {
pub fn sign_unattested(&self, pubkey: &[u8], config: &CertConfigV2) -> Result<Vec<String>> {
if config.ext_quote || config.ext_app_info {
bail!("Attestation is unavailable in no-TEE mode");
}
let CertRequestClient::Local { ca } = self else {
bail!("Unattested certificates require a local key provider");
};
let pki = SubjectPublicKeyInfo::from_der(pubkey).context("Failed to parse public key")?;
let req = CertRequest::builder()
.key(&pki)
.subject(&config.subject)
.maybe_org_name(config.org_name.as_deref())
.alt_names(&config.subject_alt_names)
.usage_server_auth(config.usage_server_auth)
.usage_client_auth(config.usage_client_auth)
.special_usage("app:custom")
.maybe_not_before(
config
.not_before
.map(|seconds| UNIX_EPOCH + Duration::from_secs(seconds)),
)
.maybe_not_after(
config
.not_after
.map(|seconds| UNIX_EPOCH + Duration::from_secs(seconds)),
)
.build();
let cert = ca.sign(req).context("Failed to sign certificate")?;
Ok(vec![cert.pem(), ca.pem_cert.clone()])
}

pub async fn sign_csr(
&self,
csr: &CertSigningRequestV2,
Expand Down
64 changes: 59 additions & 5 deletions dstack-util/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,21 @@ fn gen_app_keys_from_seed(
provider: KeyProviderKind,
mr: Option<Vec<u8>>,
) -> Result<AppKeys> {
let (key, disk_key, k256_key, key_provider) = derive_app_keys_from_seed(seed, provider, mr)?;
make_app_keys(&key, &disk_key, &k256_key, 1, key_provider)
}

fn gen_dev_app_keys_from_seed(seed: &[u8]) -> Result<AppKeys> {
let (key, disk_key, k256_key, key_provider) =
derive_app_keys_from_seed(seed, KeyProviderKind::None, None)?;
make_dev_app_keys(&key, &disk_key, &k256_key, 1, key_provider)
}

fn derive_app_keys_from_seed(
seed: &[u8],
provider: KeyProviderKind,
mr: Option<Vec<u8>>,
) -> Result<(KeyPair, KeyPair, SigningKey, KeyProvider)> {
let key = derive_p256_key_pair_from_bytes(seed, &["app-key".as_bytes()])?;
let disk_key = derive_p256_key_pair_from_bytes(seed, &["app-disk-key".as_bytes()])?;
let k256_key = derive_key(seed, &["app-k256-key".as_bytes()], 32)?;
Expand All @@ -860,7 +875,7 @@ fn gen_app_keys_from_seed(
anyhow::bail!("KMS keys must be fetched from the KMS server")
}
};
make_app_keys(&key, &disk_key, &k256_key, 1, key_provider)
Ok((key, disk_key, k256_key, key_provider))
}

fn make_app_keys(
Expand All @@ -886,15 +901,54 @@ fn make_app_keys(
.self_signed()
.context("Failed to self-sign certificate")?;

Ok(AppKeys {
Ok(app_keys_with_cert(
disk_key,
k256_key,
cert.pem(),
key_provider,
))
}

fn make_dev_app_keys(
app_key: &KeyPair,
disk_key: &KeyPair,
k256_key: &SigningKey,
ca_level: u8,
key_provider: KeyProvider,
) -> Result<AppKeys> {
use ra_tls::cert::CertRequest;
let req = CertRequest::builder()
.subject("App Root Cert")
.key(app_key)
.ca_level(ca_level)
.build();
let cert = req
.self_signed()
.context("Failed to self-sign certificate")?;

Ok(app_keys_with_cert(
disk_key,
k256_key,
cert.pem(),
key_provider,
))
}

fn app_keys_with_cert(
disk_key: &KeyPair,
k256_key: &SigningKey,
ca_cert: String,
key_provider: KeyProvider,
) -> AppKeys {
AppKeys {
disk_crypt_key: sha256(&disk_key.serialize_der()).to_vec(),
env_crypt_key: vec![],
k256_key: k256_key.to_bytes().to_vec(),
k256_signature: vec![],
gateway_app_id: "".to_string(),
ca_cert: cert.pem(),
gateway_app_id: String::new(),
ca_cert,
key_provider,
})
}
}

async fn cmd_notify_host(args: HostNotifyArgs) -> Result<()> {
Expand Down
Loading
Loading