Skip to content

Commit 5851d6d

Browse files
frankdavidIDX GitHub Automation
andauthored
chore: Remove attached LUKS header from Store partition (#10604)
The Store partition is now formatted with a detached LUKS header only. A legacy attached header still present on a device (which was the previous approach) is wiped on the next open, so only the detached header remains going forward. In addition, `guest_disk` now uses a shared metrics `Registry` and writes all metrics once. The PR adds the `guest_disk_store_attached_luks2_header_status` gauge to track the header migration state. --------- Co-authored-by: IDX GitHub Automation <infra+github-automation@dfinity.org>
1 parent a8182dd commit 5851d6d

11 files changed

Lines changed: 453 additions & 196 deletions

File tree

rs/ic_os/guest_upgrade/client/src/lib.rs

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -198,46 +198,32 @@ impl DiskEncryptionKeyExchangeClientAgent {
198198
.key
199199
.context("GetKeyResponse does not contain a key")?;
200200

201-
self.persist_disk_encryption_artifacts(
202-
disk_encryption_key,
203-
// This can be None when upgrading from older GuestOS-s that do not populate this field.
204-
// TODO: Error on None once all GuestOS-s support detached headers
205-
disk_encryption_data.luks_header,
206-
)
207-
.await?;
201+
let luks_header = disk_encryption_data
202+
.luks_header
203+
.context("Server did not send a Store LUKS header")?;
204+
205+
self.persist_disk_encryption_artifacts(disk_encryption_key, luks_header)
206+
.await?;
208207

209208
Ok(())
210209
}
211210

212211
async fn persist_disk_encryption_artifacts(
213212
&self,
214213
disk_encryption_key: Vec<u8>,
215-
luks_header: Option<Vec<u8>>,
214+
luks_header: Vec<u8>,
216215
) -> Result<()> {
217216
let disk_encryption_key =
218217
String::from_utf8(disk_encryption_key).context("Key is not valid UTF-8")?;
219218

220-
match luks_header {
221-
Some(luks_header) => {
222-
tokio::fs::write(&self.store_luks_header_path, &luks_header)
223-
.await
224-
.with_context(|| {
225-
format!(
226-
"Failed to write store LUKS header to {}",
227-
self.store_luks_header_path.display()
228-
)
229-
})?;
230-
}
231-
None => {
232-
println!(
233-
"GetKeyResponse does not contain a store LUKS header; recovering it locally from {}",
234-
self.store_device_path.display()
235-
);
236-
self.crypto_ops
237-
.backup_luks_header(&self.store_device_path, &self.store_luks_header_path)
238-
.context("Local LUKS header backup failed")?;
239-
}
240-
}
219+
tokio::fs::write(&self.store_luks_header_path, &luks_header)
220+
.await
221+
.with_context(|| {
222+
format!(
223+
"Failed to write Store LUKS header to {}",
224+
self.store_luks_header_path.display()
225+
)
226+
})?;
241227

242228
tokio::fs::write(&self.previous_key_path, disk_encryption_key)
243229
.await

rs/ic_os/guest_upgrade/server/src/lib.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,6 @@ pub struct DiskEncryptionKeyExchangeServerAgent {
4242
registry_client: Arc<dyn RegistryClient>,
4343
store_device_path: PathBuf,
4444
store_luks_header_path: PathBuf,
45-
/// If true, the server will send the Store LUKS header to the client.
46-
/// Only false in unit tests testing backwards compatibility.
47-
// TODO: Remove when all deployed GuestOS versions support sending the Store LUKS header.
48-
send_luks_header: bool,
4945
port: u16,
5046
success_timeout: Duration,
5147
}
@@ -60,7 +56,6 @@ impl DiskEncryptionKeyExchangeServerAgent {
6056
registry_client: Arc<dyn RegistryClient>,
6157
store_device_path: PathBuf,
6258
store_luks_header_path: PathBuf,
63-
send_luks_header: bool,
6459
port: u16,
6560
success_timeout: Duration,
6661
) -> Self {
@@ -73,7 +68,6 @@ impl DiskEncryptionKeyExchangeServerAgent {
7368
registry_client,
7469
store_device_path,
7570
store_luks_header_path,
76-
send_luks_header,
7771
port,
7872
success_timeout,
7973
}
@@ -105,7 +99,6 @@ impl DiskEncryptionKeyExchangeServerAgent {
10599
self.trusted_execution_environment_config.clone(),
106100
self.store_device_path.clone(),
107101
self.store_luks_header_path.clone(),
108-
self.send_luks_header,
109102
status_sender,
110103
expected_measurements,
111104
));

rs/ic_os/guest_upgrade/server/src/orchestrator.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ pub fn new_disk_encryption_key_exchange_server_agent_for_orchestrator(
7575
registry_client,
7676
STORE_DEVICE.into(),
7777
DEFAULT_STORE_LUKS_HEADER_PATH.into(),
78-
/*send_luks_header=*/ true,
7978
DEFAULT_SERVER_PORT,
8079
DEFAULT_SUCCESS_TIMEOUT,
8180
))

rs/ic_os/guest_upgrade/server/src/service.rs

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@ pub struct DiskEncryptionKeyExchangeServiceImpl {
3030
sev_root_certificate_verification: SevRootCertificateVerification,
3131
store_device_path: PathBuf,
3232
store_luks_header_path: PathBuf,
33-
/// If true, the server will send the Store LUKS header to the client.
34-
/// Only false in unit tests testing backwards compatibility.
35-
// TODO: Remove when all deployed GuestOS versions support sending the Store LUKS header.
36-
send_luks_header: bool,
3733
}
3834

3935
impl DiskEncryptionKeyExchangeServiceImpl {
@@ -44,7 +40,6 @@ impl DiskEncryptionKeyExchangeServiceImpl {
4440
trusted_execution_environment_config: TrustedExecutionEnvironmentConfig,
4541
store_device_path: PathBuf,
4642
store_luks_header_path: PathBuf,
47-
send_luks_header: bool,
4843
status_sender: Sender<Result<(), String>>,
4944
expected_measurements: Vec<Vec<u8>>,
5045
) -> Self {
@@ -57,7 +52,6 @@ impl DiskEncryptionKeyExchangeServiceImpl {
5752
sev_root_certificate_verification,
5853
store_device_path,
5954
store_luks_header_path,
60-
send_luks_header,
6155
}
6256
}
6357

@@ -155,17 +149,9 @@ impl DiskEncryptionKeyExchangeServiceImpl {
155149
let mut sev_firmware = self.sev_firmware_factory.deref()()
156150
.map_err(|e| Status::internal(format!("Failed to create SEV firmware: {e:?}")))?;
157151

158-
let luks_header = if self.send_luks_header {
159-
Some(
160-
tokio::fs::read(&self.store_luks_header_path)
161-
.await
162-
.map_err(|e| {
163-
Status::internal(format!("Failed to read Store LUKS header: {e:#}"))
164-
})?,
165-
)
166-
} else {
167-
None
168-
};
152+
let luks_header = tokio::fs::read(&self.store_luks_header_path)
153+
.await
154+
.map_err(|e| Status::internal(format!("Failed to read Store LUKS header: {e:#}")))?;
169155

170156
Ok(Response::new(GetDiskEncryptionKeyResponse {
171157
key: Some(
@@ -179,7 +165,7 @@ impl DiskEncryptionKeyExchangeServiceImpl {
179165
.into_bytes(),
180166
),
181167
sev_attestation_package: Some(my_attestation_package.into()),
182-
luks_header,
168+
luks_header: Some(luks_header),
183169
}))
184170
}
185171

rs/ic_os/guest_upgrade/tests/src/lib.rs

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@ struct TestConfig {
6868
/// Allows testing invalid client attestation data.
6969
client_custom_data_override: Option<[u8; 64]>,
7070
can_open_disk: bool,
71-
server_returns_luks_header: bool,
72-
client_recovered_luks_header: Option<Vec<u8>>,
7371
}
7472

7573
impl Default for TestConfig {
@@ -84,8 +82,6 @@ impl Default for TestConfig {
8482
client_chip_id: DEFAULT_CHIP_ID,
8583
server_chip_id: DEFAULT_CHIP_ID,
8684
can_open_disk: false,
87-
server_returns_luks_header: true,
88-
client_recovered_luks_header: None,
8985
}
9086
}
9187
}
@@ -114,8 +110,6 @@ struct DiskEncryptionKeyExchangeTestFixture {
114110
server_port: u16,
115111
/// True to assume that the disk can already be opened without key exchange
116112
can_open_disk: bool,
117-
server_returns_luks_header: bool,
118-
client_recovered_luks_header: Option<Vec<u8>>,
119113
}
120114

121115
impl DiskEncryptionKeyExchangeTestFixture {
@@ -206,8 +200,6 @@ impl DiskEncryptionKeyExchangeTestFixture {
206200
client_store_luks_header,
207201
server_port,
208202
can_open_disk: config.can_open_disk,
209-
server_returns_luks_header: config.server_returns_luks_header,
210-
client_recovered_luks_header: config.client_recovered_luks_header,
211203
}
212204
}
213205

@@ -264,7 +256,6 @@ impl DiskEncryptionKeyExchangeTestFixture {
264256
self.registry_client.clone(),
265257
self.server_store.path().to_path_buf(),
266258
self.server_store_luks_header.path().to_path_buf(),
267-
self.server_returns_luks_header,
268259
self.server_port,
269260
Duration::from_secs(2),
270261
)
@@ -281,22 +272,6 @@ impl DiskEncryptionKeyExchangeTestFixture {
281272
.expect_can_open_store()
282273
.returning(move |_, _, _, _| Ok(can_open_disk));
283274

284-
if let Some(recovered_luks_header) = self.client_recovered_luks_header.clone() {
285-
let expected_store_device_path = store_device_path.clone();
286-
crypto_ops
287-
.expect_backup_luks_header()
288-
.times(1)
289-
.withf(move |store_device_path, _| {
290-
store_device_path == expected_store_device_path.as_path()
291-
})
292-
.returning(move |_, store_luks_header_path| {
293-
std::fs::write(store_luks_header_path, &recovered_luks_header)?;
294-
Ok(())
295-
});
296-
} else {
297-
crypto_ops.expect_backup_luks_header().times(0);
298-
}
299-
300275
DiskEncryptionKeyExchangeClientAgent::new(
301276
self.client_guestos_config.clone(),
302277
SevRootCertificateVerification::TestOnlySkipVerification,
@@ -339,15 +314,6 @@ impl DiskEncryptionKeyExchangeTestFixture {
339314
);
340315
}
341316

342-
fn verify_client_luks_header_matches(&self, expected: &[u8]) {
343-
let luks_header = std::fs::read(self.client_store_luks_header.path())
344-
.expect("Failed to read client Store LUKS header");
345-
assert_eq!(
346-
luks_header, expected,
347-
"Store LUKS header file content does not match expected content"
348-
);
349-
}
350-
351317
fn verify_no_artifacts_persisted(&self) {
352318
assert!(
353319
std::fs::read(self.previous_key.path()).unwrap().is_empty(),
@@ -417,23 +383,6 @@ async fn test_exchange_keys_successfully() {
417383
fixture.verify_luks_header_populated();
418384
}
419385

420-
#[tokio::test]
421-
async fn test_exchange_keys_recovers_luks_header_when_server_omits_it() {
422-
let recovered_luks_header = sample_luks_header_bytes();
423-
let fixture = DiskEncryptionKeyExchangeTestFixture::new(TestConfig {
424-
server_returns_luks_header: false,
425-
client_recovered_luks_header: Some(recovered_luks_header.clone()),
426-
..Default::default()
427-
});
428-
let (server_result, client_result) = fixture.run_key_exchange_test().await;
429-
430-
server_result.expect("Key exchange should succeed");
431-
client_result.expect("Key exchange should succeed");
432-
433-
fixture.verify_previous_key_populated();
434-
fixture.verify_client_luks_header_matches(&recovered_luks_header);
435-
}
436-
437386
#[tokio::test]
438387
async fn test_client_measurement_not_in_registry() {
439388
let config = TestConfig {

rs/ic_os/os_tools/guest_disk/src/crypt.rs

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ use libcryptsetup_rs::consts::vals::{CryptKdf, EncryptionFormat, KeyslotInfo};
66
use libcryptsetup_rs::{
77
CryptDevice, CryptInit, CryptParamsLuks2Ref, CryptSettingsHandle, CryptTokenInfo, TokenInput,
88
};
9+
use prometheus::Registry;
910
use serde::{Deserialize, Serialize};
1011
use std::fs;
11-
use std::fs::File;
12+
use std::fs::{File, OpenOptions};
13+
use std::io::{Read, Write};
1214
use std::os::unix::fs::PermissionsExt;
1315
use std::path::Path;
1416
use tracing::{info, warn};
@@ -141,7 +143,7 @@ pub fn activate_crypt_device(
141143
passphrase: &[u8],
142144
flags: CryptActivate,
143145
verify_luks_params: bool,
144-
metrics_file: Option<&Path>,
146+
metrics_registry: Option<&Registry>,
145147
) -> Result<(CryptDevice, u32)> {
146148
let mut crypt_device = open_luks2_device(device_path, header_location)?;
147149

@@ -153,9 +155,9 @@ pub fn activate_crypt_device(
153155
.activate_by_passphrase(Some(name), None, passphrase, flags)
154156
.context("Failed to activate cryptographic device")?;
155157

156-
if let Some(metrics_file) = metrics_file {
158+
if let Some(registry) = metrics_registry {
157159
let log_result = luks_parameters.and_then(|luks_parameters| {
158-
export_luks_parameters(metrics_file, &luks_parameters, device_path, active_keyslot)
160+
export_luks_parameters(registry, &luks_parameters, device_path, active_keyslot)
159161
});
160162
if let Err(e) = log_result {
161163
warn!("Failed to export LUKS parameters: {e:#}");
@@ -210,6 +212,10 @@ pub fn format_crypt_device(
210212
.context("Failed to create detached LUKS header file")?
211213
.set_len(16 * 1024 * 1024)
212214
.context("Failed to set size of detached LUKS header file")?;
215+
// The replica (running as user ic-replica) needs read access to the detached header so
216+
// that it can share it during an upgrade.
217+
fs::set_permissions(header_path, fs::Permissions::from_mode(0o644))
218+
.context("Failed to set permissions on detached LUKS header file")?;
213219
}
214220

215221
let mut crypt_device = obtain_crypt_device_handle(device_path, header_location)?;
@@ -311,6 +317,60 @@ pub fn backup_luks_header_to_file(device_path: &Path, header_path: &Path) -> Res
311317
Ok(())
312318
}
313319

320+
/// Checks whether the device at `device_path` contains a valid LUKS2 header in the attached
321+
/// (on-device) position.
322+
pub fn has_attached_luks_header(device_path: &Path) -> Result<bool> {
323+
const LUKS_MAGIC: &[u8; 4] = b"LUKS";
324+
325+
let mut start = vec![];
326+
std::fs::File::open(device_path)
327+
.context("Failed to open device for header check")?
328+
.take(LUKS_MAGIC.len() as u64)
329+
.read_to_end(&mut start)
330+
.context("Failed to read first few bytes for header check")?;
331+
332+
Ok(start == LUKS_MAGIC)
333+
}
334+
335+
/// Wipes (zeroizes) the header area of the device at `device_path`, removing any attached LUKS
336+
/// header.
337+
///
338+
/// This is used when the Store partition is opened with a detached header: if the data device still
339+
/// carries a legacy attached header (from an older GuestOS that wrote both), we wipe it so that
340+
/// only the detached header remains going forward.
341+
pub fn wipe_attached_luks_header(device_path: &Path) -> Result<()> {
342+
let mut crypt_device = open_luks2_device(device_path, LuksHeaderLocation::Attached)
343+
.context("Failed to open LUKS device to determine header size")?;
344+
// `get_data_offset` returns the offset in 512-byte sectors; convert to bytes.
345+
let data_offset_sectors = crypt_device.status_handle().get_data_offset();
346+
let header_size_bytes = data_offset_sectors * 512;
347+
348+
let mut device = OpenOptions::new()
349+
.write(true)
350+
.open(device_path)
351+
.with_context(|| {
352+
format!(
353+
"Failed to open device {} for writing",
354+
device_path.display()
355+
)
356+
})?;
357+
device
358+
.write_all(&vec![0_u8; header_size_bytes as usize])
359+
.with_context(|| {
360+
format!(
361+
"Failed to wipe LUKS header on device {}",
362+
device_path.display()
363+
)
364+
})?;
365+
device.flush().with_context(|| {
366+
format!(
367+
"Failed to flush LUKS header wipe on device {}",
368+
device_path.display()
369+
)
370+
})?;
371+
Ok(())
372+
}
373+
314374
/// Checks if the LUKS parameters match the expected values set in format_crypt_device.
315375
/// If verify_luks_params is false, it will only log a warning if the verification fails.
316376
fn maybe_verify_luks_parameters(

0 commit comments

Comments
 (0)