chore: Great simplification of SEV key exchange - #11354
Conversation
…on-simplifications # Conflicts: # Cargo.Bazel.json.lock
|
|
||
| /// Wrapper around encrypted disk operations to allow testing guest upgrades without setting up | ||
| /// block devices which requires root. | ||
| #[mockall::automock] |
There was a problem hiding this comment.
Just moved this from the guest_disk crate since it's only useful for testing the upgrade mechanism.
|
✅ No security or compliance issues detected. Reviewed everything up to 067f88b. Security Overview
Detected Code Changes
|
| pub fn can_open_store( | ||
| /// Reads the launch measurement and TCB version from the SEV firmware's attestation | ||
| /// report, for storage in the LUKS2 keyslot metadata token. | ||
| fn get_sev_metadata_for_luks(sev_firmware: &mut dyn SevGuestFirmware) -> Result<SevMetadata> { |
There was a problem hiding this comment.
This used to be method, was moved to be a free-standing function.
There was a problem hiding this comment.
Pull request overview
Simplifies SEV Store key exchange by immediately re-keying per-slot detached LUKS headers and removing previous-key persistence.
Changes:
- Replaces the two-keyslot flow with one keyslot and metadata token.
- Moves key-exchange cryptographic operations into the upgrade client.
- Adds detached-header migration tests and token-count metrics.
Reviewed changes
Copilot reviewed 14 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
rs/ic_os/os_tools/guest_disk/src/tests.rs |
Updates SEV encryption tests. |
rs/ic_os/os_tools/guest_disk/src/sev.rs |
Implements direct header re-keying. |
rs/ic_os/os_tools/guest_disk/src/metrics.rs |
Exports token count. |
rs/ic_os/os_tools/guest_disk/src/main.rs |
Removes previous-key configuration. |
rs/ic_os/os_tools/guest_disk/src/lib.rs |
Removes previous-key path constant. |
rs/ic_os/os_tools/guest_disk/src/crypt.rs |
Adds token counting and legacy cleanup. |
rs/ic_os/os_tools/guest_disk/Cargo.toml |
Removes unused dependencies. |
rs/ic_os/os_tools/guest_disk/BUILD.bazel |
Aligns Bazel dependencies. |
rs/ic_os/guest_upgrade/tests/src/lib.rs |
Updates exchange tests and mocks. |
rs/ic_os/guest_upgrade/client/src/main.rs |
Uses client-owned crypto operations. |
rs/ic_os/guest_upgrade/client/src/lib.rs |
Copies and re-keys received headers. |
rs/ic_os/guest_upgrade/client/Cargo.toml |
Adds mocking dependency. |
rs/ic_os/guest_upgrade/client/BUILD.bazel |
Adds Bazel mocking dependency. |
ic-os/components/guestos/init/setup-encryption/setup-data-encryption.service |
Documents detached-header usage. |
Cargo.lock |
Updates Rust dependency ownership. |
Cargo.Bazel.toml.lock |
Updates Bazel’s Cargo lock data. |
Suppressed comments (2)
rs/ic_os/guest_upgrade/client/src/lib.rs:269
- The final per-slot header is published before re-keying succeeds.
rekeychanges keyslot 0 before cleaning legacy slots and replacing metadata, so a failure in either later step leaves this path unlockable by the new key; on retry,can_openreturns true and the client signals success without completing that cleanup. Re-key a temporary sibling header and atomically rename it into place only after the entire operation succeeds.
tokio::fs::write(&self.store_luks_header_path, &luks_header)
rs/ic_os/os_tools/guest_disk/src/crypt.rs:373
- Unlike the keyslot-status loop above, this treats every token-status lookup error as a present token. A libcryptsetup/I/O error will therefore be hidden and exported as an inflated
num_tokensvalue instead of making parameter extraction fail. Propagate status errors so the metric cannot report fabricated header state.
let num_tokens = (0..LUKS2_N_TOKENS)
.filter(|&token_id| {
!matches!(
crypt_device.token_handle().status(token_id),
Ok(CryptTokenInfo::Invalid) | Ok(CryptTokenInfo::Inactive)
)
})
.count();
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 18 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
rs/ic_os/os_tools/guest_disk/src/crypt.rs:374
- An error returned by
token_handle().status()currently satisfies this negated match and is counted as a present token. That can publish an inflatednum_tokensvalue instead of surfacing that the LUKS header could not be inspected, unlike the keyslot loop above which propagates status errors. Collect/propagate each token status before counting active token entries.
!matches!(
crypt_device.token_handle().status(token_id),
Ok(CryptTokenInfo::Invalid) | Ok(CryptTokenInfo::Inactive)
)
| // Increment port for each test case so tests can run in parallel | ||
| let server_port = FREE_PORT.fetch_add(1, Ordering::Relaxed); | ||
|
|
||
| let can_open_disk = config.can_open_disk; |
There was a problem hiding this comment.
What do you think about handling rekey the same way? Something like config.should_rekey_explode. I think that saves the Arc and mut stuff, and then all of this would be consistent and handled in the same place.
| // TODO: Legacy headers may carry more than one IC key metadata token. Once all nodes | ||
| // have been updated (i.e., the num_tokens metric is 1 everywhere), this removal can | ||
| // be deleted so that only one token is written. |
There was a problem hiding this comment.
I don't follow. Even once we're down to a single key, we still need to clear out the old metadata when we rekey and write a new one, don't we?
We used to keep two keyslots (and two metadata tokens) in the Store's LUKS header. The previous GuestOS's key and the current one. The upgrade VM stored the previous key in a file on the Var partition, and only after it rebooted as the default VM did it unlock the Store with that key and rotate in its own keyslot.
With detached headers, each boot slot carries its own header copy on its Var partition, so we can simplify the passphrase rotation. Each header now holds exactly one keyslot and one metadata token (valid for the corresponding GuestOS), and the previous-key file is gone.
In the new flow, the old GuestOS hands its derived key and header to the upgrade VM; the upgrade VM writes the header to its own Var partition and switches the key to its own derived key right away. This wasn't possible with attached headers because the upgrade VM could not write the shared Store partition.
Benefits: