From e547611a3c97e680d733aef724cec9da0dd3428f Mon Sep 17 00:00:00 2001 From: John Baublitz Date: Mon, 10 Apr 2023 09:09:52 -0400 Subject: [PATCH] Clean up crypt module in preparation for metadata rework This commit aims to make the crypt layer look a little bit more like our other devicemapper layers in service of simplifying our metadata rework. This will allow us to factor out a large amount of shared code between the crypt header used to encrypt the pool level metadata and the crypt header used to encrypt the data. --- .../strat_engine/backstore/backstore.rs | 10 +- .../strat_engine/backstore/blockdevmgr.rs | 5 +- .../strat_engine/backstore/crypt/activate.rs | 117 ----- .../strat_engine/backstore/crypt/handle.rs | 466 ++++++++++++++---- .../backstore/crypt/initialize.rs | 266 ---------- .../backstore/crypt/metadata_handle.rs | 88 ---- .../strat_engine/backstore/crypt/mod.rs | 72 +-- .../strat_engine/backstore/crypt/shared.rs | 116 ++--- src/engine/strat_engine/backstore/devices.rs | 76 +-- src/engine/strat_engine/backstore/mod.rs | 5 +- src/engine/strat_engine/liminal/identify.rs | 38 +- src/engine/strat_engine/liminal/liminal.rs | 76 +-- src/engine/strat_engine/liminal/setup.rs | 7 +- src/engine/strat_engine/shared.rs | 14 +- 14 files changed, 581 insertions(+), 775 deletions(-) delete mode 100644 src/engine/strat_engine/backstore/crypt/activate.rs delete mode 100644 src/engine/strat_engine/backstore/crypt/initialize.rs delete mode 100644 src/engine/strat_engine/backstore/crypt/metadata_handle.rs diff --git a/src/engine/strat_engine/backstore/backstore.rs b/src/engine/strat_engine/backstore/backstore.rs index ad88046d59..88a788ff57 100644 --- a/src/engine/strat_engine/backstore/backstore.rs +++ b/src/engine/strat_engine/backstore/backstore.rs @@ -20,7 +20,9 @@ use crate::{ blockdev::StratBlockDev, blockdevmgr::BlockDevMgr, cache_tier::CacheTier, - crypt::{back_up_luks_header, interpret_clevis_config, restore_luks_header}, + crypt::{ + back_up_luks_header, interpret_clevis_config, restore_luks_header, CryptHandle, + }, data_tier::DataTier, devices::UnownedDevices, shared::BlockSizeSummary, @@ -30,7 +32,7 @@ use crate::{ metadata::{MDADataSize, BDA}, names::{format_backstore_ids, CacheRole}, serde_structs::{BackstoreSave, CapSave, Recordable}, - shared::{bds_to_bdas, can_unlock}, + shared::bds_to_bdas, types::BDARecordResult, writing::wipe_sectors, }, @@ -726,7 +728,7 @@ impl Backstore { } if (existing_pin.as_str(), &config_to_check) == (pin, &parsed_config) - && can_unlock( + && CryptHandle::can_unlock( self.blockdevs() .get(0) .expect("Must have at least one blockdev") @@ -800,7 +802,7 @@ impl Backstore { if let Some(kd) = encryption_info.key_description() { if kd == key_desc { - if can_unlock( + if CryptHandle::can_unlock( self.blockdevs() .get(0) .expect("Must have at least one blockdev") diff --git a/src/engine/strat_engine/backstore/blockdevmgr.rs b/src/engine/strat_engine/backstore/blockdevmgr.rs index 3146e37bbe..85f62751d3 100644 --- a/src/engine/strat_engine/backstore/blockdevmgr.rs +++ b/src/engine/strat_engine/backstore/blockdevmgr.rs @@ -19,6 +19,7 @@ use crate::{ strat_engine::{ backstore::{ blockdev::StratBlockDev, + crypt::CryptHandle, devices::{initialize_devices, wipe_blockdevs, UnownedDevices}, range_alloc::PerDevSegments, shared::{BlkDevSegment, Segment}, @@ -26,7 +27,7 @@ use crate::{ }, metadata::{MDADataSize, BDA}, serde_structs::{BaseBlockDevSave, Recordable}, - shared::{bds_to_bdas, can_unlock}, + shared::bds_to_bdas, }, types::{DevUuid, EncryptionInfo, Name, PoolEncryptionInfo, PoolUuid}, }, @@ -114,7 +115,7 @@ impl BlockDevMgr { let encryption_info = pool_enc_to_enc!(self.encryption_info()); if let Some(ref ei) = encryption_info { - if !can_unlock( + if !CryptHandle::can_unlock( self.block_devs .get(0) .expect("Must have at least one blockdev") diff --git a/src/engine/strat_engine/backstore/crypt/activate.rs b/src/engine/strat_engine/backstore/crypt/activate.rs deleted file mode 100644 index 897cf93720..0000000000 --- a/src/engine/strat_engine/backstore/crypt/activate.rs +++ /dev/null @@ -1,117 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::path::Path; - -use libcryptsetup_rs::consts::flags::CryptActivate; - -use crate::{ - engine::{ - strat_engine::{ - backstore::crypt::{ - consts::CLEVIS_LUKS_TOKEN_ID, - handle::CryptHandle, - shared::{ - acquire_crypt_device, check_luks2_token, get_keyslot_number, - key_desc_from_metadata, setup_crypt_device, setup_crypt_handle, - }, - }, - cmd::clevis_decrypt, - }, - types::UnlockMethod, - }, - stratis::{StratisError, StratisResult}, -}; - -/// Handle for activating a locked encrypted device. -pub struct CryptActivationHandle; - -impl CryptActivationHandle { - /// Check whether the given physical device can be unlocked with the current - /// environment (e.g. the proper key is in the kernel keyring, the device - /// is formatted as a LUKS2 device, etc.) - pub fn can_unlock( - physical_path: &Path, - try_unlock_keyring: bool, - try_unlock_clevis: bool, - ) -> bool { - fn can_unlock_with_failures( - physical_path: &Path, - try_unlock_keyring: bool, - try_unlock_clevis: bool, - ) -> StratisResult { - let mut device = acquire_crypt_device(physical_path)?; - - if try_unlock_keyring { - let key_description = key_desc_from_metadata(&mut device); - - if key_description.is_some() { - check_luks2_token(&mut device)?; - } - } - if try_unlock_clevis { - let token = device.token_handle().json_get(CLEVIS_LUKS_TOKEN_ID).ok(); - let jwe = token.as_ref().and_then(|t| t.get("jwe")); - if let Some(jwe) = jwe { - let pass = clevis_decrypt(jwe)?; - if let Some(keyslot) = get_keyslot_number(&mut device, CLEVIS_LUKS_TOKEN_ID)? - .and_then(|k| k.into_iter().next()) - { - log_on_failure!( - device.activate_handle().activate_by_passphrase( - None, - Some(keyslot), - pass.as_ref(), - CryptActivate::empty(), - ), - "libcryptsetup reported that the decrypted Clevis passphrase \ - is unable to open the encrypted device" - ); - } else { - return Err(StratisError::Msg( - "Clevis JWE was found in the Stratis metadata but was \ - not associated with any keyslots" - .to_string(), - )); - } - } - } - Ok(true) - } - - can_unlock_with_failures(physical_path, try_unlock_keyring, try_unlock_clevis) - .map_err(|e| { - warn!( - "stratisd was unable to simulate opening the given device \ - in the current environment: {}", - e, - ); - }) - .unwrap_or(false) - } - - /// Query the device metadata to reconstruct a handle for performing operations - /// on an existing encrypted device. - /// - /// This method will check that the metadata on the given device is - /// for the LUKS2 format and that the LUKS2 metadata is formatted - /// properly as a Stratis encrypted device. If it is properly - /// formatted it will return the device identifiers (pool and device UUIDs). - /// - /// NOTE: This method attempts to activate the device and thus returns a CryptHandle - /// - /// The checks include: - /// * is a LUKS2 device - /// * has a valid Stratis LUKS2 token - /// * has a token of the proper type for LUKS2 keyring unlocking - pub fn setup( - physical_path: &Path, - unlock_method: UnlockMethod, - ) -> StratisResult> { - match setup_crypt_device(physical_path)? { - Some(ref mut device) => setup_crypt_handle(device, physical_path, Some(unlock_method)), - None => Ok(None), - } - } -} diff --git a/src/engine/strat_engine/backstore/crypt/handle.rs b/src/engine/strat_engine/backstore/crypt/handle.rs index 58f9b57074..0ad5092cf7 100644 --- a/src/engine/strat_engine/backstore/crypt/handle.rs +++ b/src/engine/strat_engine/backstore/crypt/handle.rs @@ -4,18 +4,20 @@ use std::{ fmt::Debug, - iter::once, path::{Path, PathBuf}, }; use either::Either; -use serde_json::Value; +use serde_json::{to_value, Value}; use devicemapper::{Device, DmName, DmNameBuf, Sectors}; use libcryptsetup_rs::{ c_uint, - consts::{flags::CryptActivate, vals::EncryptionFormat}, - CryptDevice, TokenInput, + consts::{ + flags::{CryptActivate, CryptVolumeKey}, + vals::{EncryptionFormat, KeyslotsSize, MetadataSize}, + }, + CryptDevice, CryptInit, TokenInput, }; use crate::{ @@ -23,12 +25,17 @@ use crate::{ strat_engine::{ backstore::{ crypt::{ - consts::{CLEVIS_LUKS_TOKEN_ID, LUKS2_TOKEN_ID}, - metadata_handle::CryptMetadataHandle, + consts::{ + CLEVIS_LUKS_TOKEN_ID, DEFAULT_CRYPT_KEYSLOTS_SIZE, + DEFAULT_CRYPT_METADATA_SIZE, LUKS2_TOKEN_ID, STRATIS_MEK_SIZE, + STRATIS_TOKEN_ID, + }, shared::{ - acquire_crypt_device, add_keyring_keyslot, clevis_info_from_metadata, - ensure_inactive, ensure_wiped, get_keyslot_number, interpret_clevis_config, - read_key, replace_pool_name, setup_crypt_device, setup_crypt_handle, + acquire_crypt_device, activate, add_keyring_keyslot, check_luks2_token, + clevis_info_from_metadata, ensure_inactive, ensure_wiped, + get_keyslot_number, interpret_clevis_config, key_desc_from_metadata, + load_crypt_metadata, read_key, replace_pool_name, setup_crypt_device, + setup_crypt_handle, wipe_fallback, StratisLuks2Token, }, }, devices::get_devno_from_path, @@ -37,59 +44,331 @@ use crate::{ dm::DEVICEMAPPER_PATH, keys::MemoryPrivateFilesystem, metadata::StratisIdentifiers, + names::format_crypt_name, + }, + types::{ + DevUuid, DevicePath, EncryptionInfo, KeyDescription, Name, PoolUuid, SizedKeyMemory, + UnlockMethod, }, - types::{DevicePath, EncryptionInfo, KeyDescription, Name, SizedKeyMemory}, + ClevisInfo, }, stratis::{StratisError, StratisResult}, }; -/// Handle for performing operations on an encrypted device. -/// -/// This device assumes that its logical, unlocked device path has been activated and -/// is present. This checked in all mechanisms that yield a CryptHandle. -/// * CryptInitializer will ensure that the newly formatted device is activated. -/// * CryptActivationHandle requires the user to activate a device to yield a CryptHandle. -/// * CryptHandle::setup() fails if the device is not active. +#[derive(Debug, Clone)] +pub struct CryptMetadata { + pub physical_path: DevicePath, + pub identifiers: StratisIdentifiers, + pub encryption_info: EncryptionInfo, + pub activation_name: DmNameBuf, + pub activated_path: PathBuf, + pub pool_name: Option, + pub device: Device, +} + +/// Handle for performing all operations on an encrypted device. /// /// `Clone` is derived for this data structure because `CryptHandle` acquires -/// a next crypt device context for each operation. +/// a new crypt device context for each operation. #[derive(Debug, Clone)] pub struct CryptHandle { - activated_path: DevicePath, - metadata_handle: CryptMetadataHandle, + metadata: CryptMetadata, } impl CryptHandle { pub(super) fn new( physical_path: DevicePath, - identifiers: StratisIdentifiers, + pool_uuid: PoolUuid, + dev_uuid: DevUuid, encryption_info: EncryptionInfo, - activation_name: DmNameBuf, pool_name: Option, - ) -> StratisResult { - let device = get_devno_from_path(&physical_path)?; - CryptHandle::new_with_metadata_handle(CryptMetadataHandle::new( + devno: Device, + ) -> CryptHandle { + let activation_name = format_crypt_name(&dev_uuid); + let path = vec![DEVICEMAPPER_PATH, &activation_name.to_string()] + .into_iter() + .collect::(); + let activated_path = path.canonicalize().unwrap_or(path); + CryptHandle { + metadata: CryptMetadata { + physical_path, + identifiers: StratisIdentifiers { + pool_uuid, + device_uuid: dev_uuid, + }, + encryption_info, + activation_name, + pool_name, + device: devno, + activated_path, + }, + } + } + + /// Check whether the given physical device can be unlocked with the current + /// environment (e.g. the proper key is in the kernel keyring, the device + /// is formatted as a LUKS2 device, etc.) + pub fn can_unlock( + physical_path: &Path, + try_unlock_keyring: bool, + try_unlock_clevis: bool, + ) -> bool { + fn can_unlock_with_failures( + physical_path: &Path, + try_unlock_keyring: bool, + try_unlock_clevis: bool, + ) -> StratisResult { + let mut device = acquire_crypt_device(physical_path)?; + + if try_unlock_keyring { + let key_description = key_desc_from_metadata(&mut device); + + if key_description.is_some() { + check_luks2_token(&mut device)?; + } + } + if try_unlock_clevis { + let token = device.token_handle().json_get(CLEVIS_LUKS_TOKEN_ID).ok(); + let jwe = token.as_ref().and_then(|t| t.get("jwe")); + if let Some(jwe) = jwe { + let pass = clevis_decrypt(jwe)?; + if let Some(keyslot) = get_keyslot_number(&mut device, CLEVIS_LUKS_TOKEN_ID)? + .and_then(|k| k.into_iter().next()) + { + log_on_failure!( + device.activate_handle().activate_by_passphrase( + None, + Some(keyslot), + pass.as_ref(), + CryptActivate::empty(), + ), + "libcryptsetup reported that the decrypted Clevis passphrase \ + is unable to open the encrypted device" + ); + } else { + return Err(StratisError::Msg( + "Clevis JWE was found in the Stratis metadata but was \ + not associated with any keyslots" + .to_string(), + )); + } + } + } + Ok(true) + } + + can_unlock_with_failures(physical_path, try_unlock_keyring, try_unlock_clevis) + .map_err(|e| { + warn!( + "stratisd was unable to simulate opening the given device \ + in the current environment: {}", + e, + ); + }) + .unwrap_or(false) + } + + /// Initialize a device with the provided key description and Clevis info. + pub fn initialize( + physical_path: &Path, + pool_uuid: PoolUuid, + dev_uuid: DevUuid, + pool_name: Name, + encryption_info: &EncryptionInfo, + ) -> StratisResult { + let activation_name = format_crypt_name(&dev_uuid); + let mut device = log_on_failure!( + CryptInit::init(physical_path), + "Failed to acquire context for device {} while initializing; \ + nothing to clean up", + physical_path.display() + ); + device.settings_handle().set_metadata_size( + MetadataSize::try_from(convert_int!(*DEFAULT_CRYPT_METADATA_SIZE, u128, u64)?)?, + KeyslotsSize::try_from(convert_int!(*DEFAULT_CRYPT_KEYSLOTS_SIZE, u128, u64)?)?, + )?; + Self::initialize_with_err(&mut device, physical_path, pool_uuid, dev_uuid, &pool_name, encryption_info) + .and_then(|path| clevis_info_from_metadata(&mut device).map(|ci| (path, ci))) + .and_then(|(_, clevis_info)| { + let encryption_info = + EncryptionInfo::from_options((encryption_info.key_description().cloned(), clevis_info)) + .expect("Encrypted device must be provided encryption parameters"); + let device_path = DevicePath::new(physical_path)?; + let devno = get_devno_from_path(physical_path)?; + Ok(CryptHandle::new( + device_path, + pool_uuid, + dev_uuid, + encryption_info, + Some(pool_name), + devno, + )) + }) + .map_err(|e| { + if let Err(err) = + Self::rollback(&mut device, physical_path, &activation_name) + { + warn!( + "Failed to roll back crypt device initialization; you may need to manually wipe this device: {}", + err + ); + } + e + }) + } + + /// Initialize with a passphrase in the kernel keyring only. + fn initialize_with_keyring( + device: &mut CryptDevice, + key_description: &KeyDescription, + ) -> StratisResult<()> { + add_keyring_keyslot(device, key_description, None)?; + + Ok(()) + } + + /// Initialize with Clevis only. + fn initialize_with_clevis( + device: &mut CryptDevice, + physical_path: &Path, + (pin, json, yes): (&str, &Value, bool), + ) -> StratisResult<()> { + let fs = log_on_failure!( + MemoryPrivateFilesystem::new(), + "Failed to initialize in memory filesystem for temporary keyfile for + Clevis binding" + ); + let keyfile = log_on_failure!( + fs.rand_key(), + "Failed to generate a key with random data for Clevis initialization" + ); + + let keyslot = log_on_failure!( + device.keyslot_handle().add_by_key( + None, + None, + keyfile.as_ref(), + CryptVolumeKey::empty(), + ), + "Failed to initialize keyslot with provided key in keyring" + ); + + clevis_luks_bind( physical_path, - identifiers, - encryption_info, - activation_name, - pool_name, - device, - )) - } - - pub(super) fn new_with_metadata_handle( - metadata_handle: CryptMetadataHandle, - ) -> StratisResult { - let activated_path = DevicePath::new( - &once(DEVICEMAPPER_PATH.to_string()) - .chain(once(metadata_handle.activation_name().to_string())) - .collect::(), + keyfile.keyfile_path(), + CLEVIS_LUKS_TOKEN_ID, + pin, + json, + yes, )?; - Ok(CryptHandle { - activated_path, - metadata_handle, - }) + + // Need to reload device here to refresh the state of the device + // after being modified by Clevis. + if let Err(e) = device + .context_handle() + .load::<()>(Some(EncryptionFormat::Luks2), None) + { + return Err(wipe_fallback(physical_path, StratisError::from(e))); + } + + device.keyslot_handle().destroy(keyslot)?; + + Ok(()) + } + + /// Initialize with both a passphrase in the kernel keyring and Clevis. + fn initialize_with_both( + device: &mut CryptDevice, + physical_path: &Path, + key_description: &KeyDescription, + (pin, json, yes): (&str, &Value, bool), + ) -> StratisResult<()> { + Self::initialize_with_keyring(device, key_description)?; + + let fs = MemoryPrivateFilesystem::new()?; + fs.key_op(key_description, |kf| { + clevis_luks_bind(physical_path, kf, CLEVIS_LUKS_TOKEN_ID, pin, json, yes) + })?; + + // Need to reload device here to refresh the state of the device + // after being modified by Clevis. + if let Err(e) = device + .context_handle() + .load::<()>(Some(EncryptionFormat::Luks2), None) + { + return Err(wipe_fallback(physical_path, StratisError::from(e))); + } + + Ok(()) + } + + fn initialize_with_err( + device: &mut CryptDevice, + physical_path: &Path, + pool_uuid: PoolUuid, + dev_uuid: DevUuid, + pool_name: &Name, + encryption_info: &EncryptionInfo, + ) -> StratisResult<()> { + log_on_failure!( + device.context_handle().format::<()>( + EncryptionFormat::Luks2, + ("aes", "xts-plain64"), + None, + libcryptsetup_rs::Either::Right(STRATIS_MEK_SIZE), + None, + ), + "Failed to format device {} with LUKS2 header", + physical_path.display() + ); + + match encryption_info { + EncryptionInfo::Both(kd, (pin, config)) => { + let mut parsed_config = config.clone(); + let y = interpret_clevis_config(pin, &mut parsed_config)?; + Self::initialize_with_both(device, physical_path, kd, (pin, &parsed_config, y))? + } + EncryptionInfo::KeyDesc(kd) => Self::initialize_with_keyring(device, kd)?, + EncryptionInfo::ClevisInfo((pin, config)) => { + let mut parsed_config = config.clone(); + let y = interpret_clevis_config(pin, &mut parsed_config)?; + Self::initialize_with_clevis(device, physical_path, (pin, &parsed_config, y))? + } + }; + + let activation_name = format_crypt_name(&dev_uuid); + // Initialize stratis token + log_on_failure!( + device.token_handle().json_set(TokenInput::ReplaceToken( + STRATIS_TOKEN_ID, + &to_value(&StratisLuks2Token { + devname: activation_name.clone(), + identifiers: StratisIdentifiers { + pool_uuid, + device_uuid: dev_uuid + }, + pool_name: Some(pool_name.clone()), + })?, + )), + "Failed to create the Stratis token" + ); + + activate( + if let Some(kd) = encryption_info.key_description() { + Either::Left((device, kd)) + } else { + Either::Right(physical_path) + }, + &activation_name, + ) + } + + pub fn rollback( + device: &mut CryptDevice, + physical_path: &Path, + name: &DmName, + ) -> StratisResult<()> { + ensure_wiped(device, physical_path, name) } /// Acquire the crypt device handle for the physical path in this `CryptHandle`. @@ -106,55 +385,61 @@ impl CryptHandle { /// formatted it will return the device identifiers (pool and device UUIDs). /// /// NOTE: This will not validate that the proper key is in the kernel - /// keyring. For that, use `CryptActivationHandle::can_unlock()`. + /// keyring. For that, use `CryptHandle::can_unlock()`. /// /// The checks include: /// * is a LUKS2 device /// * has a valid Stratis LUKS2 token /// * has a token of the proper type for LUKS2 keyring unlocking - pub fn setup(physical_path: &Path) -> StratisResult> { + pub fn setup( + physical_path: &Path, + unlock_method: Option, + ) -> StratisResult> { match setup_crypt_device(physical_path)? { - Some(ref mut device) => setup_crypt_handle(device, physical_path, None), + Some(ref mut device) => setup_crypt_handle(device, physical_path, unlock_method), None => Ok(None), } } + /// Load the required information for Stratis from the LUKS2 metadata. + pub fn load_metadata(physical_path: &Path) -> StratisResult> { + load_crypt_metadata(physical_path) + } + /// Get the encryption info for this encrypted device. pub fn encryption_info(&self) -> &EncryptionInfo { - self.metadata_handle.encryption_info() + &self.metadata.encryption_info } /// Return the path to the device node of the underlying storage device /// for the encrypted device. pub fn luks2_device_path(&self) -> &Path { - self.metadata_handle.luks2_device_path() - } - - /// Return the path to the device node of the decrypted contents of the encrypted - /// storage device. In an encrypted pool, this is the path that can be used to read - /// the Stratis blockdev metadata. - pub fn activated_device_path(&self) -> &Path { - &self.activated_path + &self.metadata.physical_path } /// Return the name of the activated devicemapper device. pub fn activation_name(&self) -> &DmName { - self.metadata_handle.activation_name() + &self.metadata.activation_name + } + + /// Return the path of the activated devicemapper device. + pub fn activated_device_path(&self) -> &Path { + &self.metadata.activated_path } /// Return the pool name recorded in the LUKS2 metadata. pub fn pool_name(&self) -> Option<&Name> { - self.metadata_handle.pool_name() + self.metadata.pool_name.as_ref() } /// Device number for the LUKS2 encrypted device. pub fn device(&self) -> &Device { - self.metadata_handle.device() + &self.metadata.device } /// Get the Stratis device identifiers for a given encrypted device. pub fn device_identifiers(&self) -> &StratisIdentifiers { - self.metadata_handle.device_identifiers() + &self.metadata.identifiers } /// Get the keyslot associated with the given token ID. @@ -163,7 +448,7 @@ impl CryptHandle { } /// Get info for the clevis binding. - pub fn clevis_info(&self) -> StratisResult> { + pub fn clevis_info(&self) -> StratisResult> { clevis_info_from_metadata(&mut self.acquire_crypt_device()?) } @@ -173,7 +458,7 @@ impl CryptHandle { let yes = interpret_clevis_config(pin, &mut json_owned)?; let key_desc = self - .metadata_handle + .metadata .encryption_info .key_description() .ok_or_else(|| { @@ -194,27 +479,22 @@ impl CryptHandle { yes, ) })?; - self.metadata_handle.encryption_info = self - .metadata_handle - .encryption_info - .clone() - .set_clevis_info(self.clevis_info()?.ok_or_else(|| { - StratisError::Msg( - "Clevis reported successfully binding to device but no metadata was found" - .to_string(), - ) - })?); + self.metadata.encryption_info = + self.metadata + .encryption_info + .clone() + .set_clevis_info(self.clevis_info()?.ok_or_else(|| { + StratisError::Msg( + "Clevis reported successfully binding to device but no metadata was found" + .to_string(), + ) + })?); Ok(()) } /// Unbind the given device using clevis. pub fn clevis_unbind(&mut self) -> StratisResult<()> { - if self - .metadata_handle - .encryption_info - .key_description() - .is_none() - { + if self.metadata.encryption_info.key_description().is_none() { return Err(StratisError::Msg( "No kernel keyring binding found; removing the Clevis binding \ would remove the ability to open this device; aborting" @@ -234,11 +514,7 @@ impl CryptHandle { self.luks2_device_path().display() ); } - self.metadata_handle.encryption_info = self - .metadata_handle - .encryption_info - .clone() - .unset_clevis_info(); + self.metadata.encryption_info = self.metadata.encryption_info.clone().unset_clevis_info(); Ok(()) } @@ -248,7 +524,7 @@ impl CryptHandle { /// the config may change specifically in the case where a new thumbprint /// is provided if Tang keys are rotated. pub fn rebind_clevis(&mut self) -> StratisResult<()> { - if self.metadata_handle.encryption_info.clevis_info().is_none() { + if self.metadata.encryption_info.clevis_info().is_none() { return Err(StratisError::Msg( "No Clevis binding found; cannot regenerate the Clevis binding if the device does not already have a Clevis binding".to_string(), )); @@ -279,8 +555,8 @@ impl CryptHandle { self.luks2_device_path().display() )) })?; - self.metadata_handle.encryption_info = self - .metadata_handle + self.metadata.encryption_info = self + .metadata .encryption_info .clone() .set_clevis_info((pin, config)); @@ -301,8 +577,8 @@ impl CryptHandle { add_keyring_keyslot(&mut device, key_desc, Some(Either::Left(key)))?; - self.metadata_handle.encryption_info = self - .metadata_handle + self.metadata.encryption_info = self + .metadata .encryption_info .clone() .set_key_desc(key_desc.clone()); @@ -311,7 +587,7 @@ impl CryptHandle { /// Add a keyring binding to the underlying LUKS2 volume. pub fn unbind_keyring(&mut self) -> StratisResult<()> { - if self.metadata_handle.encryption_info.clevis_info().is_none() { + if self.metadata.encryption_info.clevis_info().is_none() { return Err(StratisError::Msg( "No Clevis binding was found; removing the keyring binding would \ remove the ability to open this device; aborting" @@ -333,11 +609,7 @@ impl CryptHandle { .token_handle() .json_set(TokenInput::RemoveToken(LUKS2_TOKEN_ID))?; - self.metadata_handle.encryption_info = self - .metadata_handle - .encryption_info - .clone() - .unset_key_desc(); + self.metadata.encryption_info = self.metadata.encryption_info.clone().unset_key_desc(); Ok(()) } @@ -346,7 +618,7 @@ impl CryptHandle { pub fn rebind_keyring(&mut self, new_key_desc: &KeyDescription) -> StratisResult<()> { let mut device = self.acquire_crypt_device()?; - let old_key_description = self.metadata_handle.encryption_info + let old_key_description = self.metadata.encryption_info .key_description() .ok_or_else(|| { StratisError::Msg("Cannot change passphrase because this device is not bound to a passphrase in the kernel keyring".to_string()) @@ -356,8 +628,8 @@ impl CryptHandle { new_key_desc, Some(Either::Right(old_key_description)), )?; - self.metadata_handle.encryption_info = self - .metadata_handle + self.metadata.encryption_info = self + .metadata .encryption_info .clone() .set_key_desc(new_key_desc.clone()); diff --git a/src/engine/strat_engine/backstore/crypt/initialize.rs b/src/engine/strat_engine/backstore/crypt/initialize.rs deleted file mode 100644 index 7f220330a6..0000000000 --- a/src/engine/strat_engine/backstore/crypt/initialize.rs +++ /dev/null @@ -1,266 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::path::Path; - -use either::Either; -use serde_json::{to_value, Value}; - -use devicemapper::{DmName, DmNameBuf}; -use libcryptsetup_rs::{ - consts::{ - flags::CryptVolumeKey, - vals::{EncryptionFormat, KeyslotsSize, MetadataSize}, - }, - CryptDevice, CryptInit, TokenInput, -}; - -use crate::{ - engine::{ - strat_engine::{ - backstore::crypt::{ - consts::{ - CLEVIS_LUKS_TOKEN_ID, DEFAULT_CRYPT_KEYSLOTS_SIZE, DEFAULT_CRYPT_METADATA_SIZE, - STRATIS_MEK_SIZE, STRATIS_TOKEN_ID, - }, - handle::CryptHandle, - shared::{ - activate, add_keyring_keyslot, clevis_info_from_metadata, ensure_wiped, - interpret_clevis_config, wipe_fallback, StratisLuks2Token, - }, - }, - cmd::clevis_luks_bind, - keys::MemoryPrivateFilesystem, - metadata::StratisIdentifiers, - names::format_crypt_name, - }, - types::{ClevisInfo, DevUuid, DevicePath, EncryptionInfo, KeyDescription, Name, PoolUuid}, - }, - stratis::{StratisError, StratisResult}, -}; - -/// Handle for initialization actions on a physical device. -pub struct CryptInitializer { - physical_path: DevicePath, - identifiers: StratisIdentifiers, - activation_name: DmNameBuf, -} - -impl CryptInitializer { - pub fn new( - physical_path: DevicePath, - pool_uuid: PoolUuid, - dev_uuid: DevUuid, - ) -> CryptInitializer { - CryptInitializer { - physical_path, - activation_name: format_crypt_name(&dev_uuid), - identifiers: StratisIdentifiers::new(pool_uuid, dev_uuid), - } - } - - /// Initialize a device with the provided key description and Clevis info. - pub fn initialize( - self, - pool_name: Name, - key_description: Option<&KeyDescription>, - clevis_info: Option<&ClevisInfo>, - ) -> StratisResult { - let mut clevis_info_owned = - clevis_info.map(|(pin, config)| (pin.to_owned(), config.clone())); - let clevis_parsed = match clevis_info_owned { - Some((ref pin, ref mut config)) => { - let yes = interpret_clevis_config(pin, config)?; - Some((pin.as_str(), &*config, yes)) - } - None => None, - }; - - let mut device = log_on_failure!( - CryptInit::init(&self.physical_path), - "Failed to acquire context for device {} while initializing; \ - nothing to clean up", - self.physical_path.display() - ); - device.settings_handle().set_metadata_size( - MetadataSize::try_from(convert_int!(*DEFAULT_CRYPT_METADATA_SIZE, u128, u64)?)?, - KeyslotsSize::try_from(convert_int!(*DEFAULT_CRYPT_KEYSLOTS_SIZE, u128, u64)?)?, - )?; - self - .initialize_with_err(&mut device, &pool_name, key_description, clevis_parsed) - .and_then(|path| clevis_info_from_metadata(&mut device).map(|ci| (path, ci))) - .and_then(|(_, clevis_info)| { - let encryption_info = - EncryptionInfo::from_options((key_description.cloned(), clevis_info)) - .expect("Encrypted device must be provided encryption parameters"); - CryptHandle::new( - self.physical_path.clone(), - self.identifiers, - encryption_info, - self.activation_name.clone(), - Some(pool_name), - ) - }) - .map_err(|e| { - if let Err(err) = - Self::rollback(&mut device, &self.physical_path, &self.activation_name) - { - warn!( - "Failed to roll back crypt device initialization; you may need to manually wipe this device: {}", - err - ); - } - e - }) - } - - /// Initialize with a passphrase in the kernel keyring only. - fn initialize_with_keyring( - device: &mut CryptDevice, - key_description: &KeyDescription, - ) -> StratisResult<()> { - add_keyring_keyslot(device, key_description, None)?; - - Ok(()) - } - - /// Initialize with Clevis only. - fn initialize_with_clevis( - &self, - device: &mut CryptDevice, - (pin, json, yes): (&str, &Value, bool), - ) -> StratisResult<()> { - let fs = log_on_failure!( - MemoryPrivateFilesystem::new(), - "Failed to initialize in memory filesystem for temporary keyfile for - Clevis binding" - ); - let keyfile = log_on_failure!( - fs.rand_key(), - "Failed to generate a key with random data for Clevis initialization" - ); - - let keyslot = log_on_failure!( - device.keyslot_handle().add_by_key( - None, - None, - keyfile.as_ref(), - CryptVolumeKey::empty(), - ), - "Failed to initialize keyslot with provided key in keyring" - ); - - clevis_luks_bind( - &self.physical_path, - keyfile.keyfile_path(), - CLEVIS_LUKS_TOKEN_ID, - pin, - json, - yes, - )?; - - // Need to reload device here to refresh the state of the device - // after being modified by Clevis. - if let Err(e) = device - .context_handle() - .load::<()>(Some(EncryptionFormat::Luks2), None) - { - return Err(wipe_fallback(&self.physical_path, StratisError::from(e))); - } - - device.keyslot_handle().destroy(keyslot)?; - - Ok(()) - } - - /// Initialize with both a passphrase in the kernel keyring and Clevis. - fn initialize_with_both( - &self, - device: &mut CryptDevice, - key_description: &KeyDescription, - (pin, json, yes): (&str, &Value, bool), - ) -> StratisResult<()> { - Self::initialize_with_keyring(device, key_description)?; - - let fs = MemoryPrivateFilesystem::new()?; - fs.key_op(key_description, |kf| { - clevis_luks_bind( - &self.physical_path, - kf, - CLEVIS_LUKS_TOKEN_ID, - pin, - json, - yes, - ) - })?; - - // Need to reload device here to refresh the state of the device - // after being modified by Clevis. - if let Err(e) = device - .context_handle() - .load::<()>(Some(EncryptionFormat::Luks2), None) - { - return Err(wipe_fallback(&self.physical_path, StratisError::from(e))); - } - - Ok(()) - } - - fn initialize_with_err( - &self, - device: &mut CryptDevice, - pool_name: &Name, - key_description: Option<&KeyDescription>, - clevis_info: Option<(&str, &Value, bool)>, - ) -> StratisResult<()> { - log_on_failure!( - device.context_handle().format::<()>( - EncryptionFormat::Luks2, - ("aes", "xts-plain64"), - None, - libcryptsetup_rs::Either::Right(STRATIS_MEK_SIZE), - None, - ), - "Failed to format device {} with LUKS2 header", - self.physical_path.display() - ); - - match (key_description, clevis_info) { - (Some(kd), Some(ci)) => self.initialize_with_both(device, kd, ci)?, - (Some(kd), _) => Self::initialize_with_keyring(device, kd)?, - (_, Some(ci)) => self.initialize_with_clevis(device, ci)?, - (_, _) => unreachable!(), - }; - - // Initialize stratis token - log_on_failure!( - device.token_handle().json_set(TokenInput::ReplaceToken( - STRATIS_TOKEN_ID, - &to_value(&StratisLuks2Token { - devname: self.activation_name.clone(), - identifiers: self.identifiers, - pool_name: Some(pool_name.clone()), - })?, - )), - "Failed to create the Stratis token" - ); - - activate( - if let Some(kd) = key_description { - Either::Left((device, kd)) - } else { - Either::Right(&self.physical_path) - }, - &self.activation_name, - ) - } - - pub fn rollback( - device: &mut CryptDevice, - physical_path: &Path, - name: &DmName, - ) -> StratisResult<()> { - ensure_wiped(device, physical_path, name) - } -} diff --git a/src/engine/strat_engine/backstore/crypt/metadata_handle.rs b/src/engine/strat_engine/backstore/crypt/metadata_handle.rs deleted file mode 100644 index 9da6d6abc4..0000000000 --- a/src/engine/strat_engine/backstore/crypt/metadata_handle.rs +++ /dev/null @@ -1,88 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::path::Path; - -use devicemapper::{Device, DmName, DmNameBuf}; - -use crate::{ - engine::{ - strat_engine::{ - backstore::crypt::shared::{setup_crypt_device, setup_crypt_metadata_handle}, - metadata::StratisIdentifiers, - }, - types::{DevicePath, EncryptionInfo, Name}, - }, - stratis::StratisResult, -}; - -/// Handle for reading metadata of a device that does not need to be active. -#[derive(Debug, Clone)] -pub struct CryptMetadataHandle { - pub(super) physical_path: DevicePath, - pub(super) identifiers: StratisIdentifiers, - pub(super) encryption_info: EncryptionInfo, - pub(super) activation_name: DmNameBuf, - pub(super) pool_name: Option, - pub(super) device: Device, -} - -impl CryptMetadataHandle { - pub(super) fn new( - physical_path: DevicePath, - identifiers: StratisIdentifiers, - encryption_info: EncryptionInfo, - activation_name: DmNameBuf, - pool_name: Option, - device: Device, - ) -> Self { - CryptMetadataHandle { - physical_path, - identifiers, - encryption_info, - activation_name, - pool_name, - device, - } - } - - /// Set up a handle to a crypt device for accessing metadata on the device. - pub fn setup(physical_path: &Path) -> StratisResult> { - match setup_crypt_device(physical_path)? { - Some(ref mut device) => setup_crypt_metadata_handle(device, physical_path), - None => Ok(None), - } - } - - /// Get the encryption info for this encrypted device. - pub fn encryption_info(&self) -> &EncryptionInfo { - &self.encryption_info - } - - /// Return the path to the device node of the underlying storage device - /// for the encrypted device. - pub fn luks2_device_path(&self) -> &Path { - &self.physical_path - } - - /// Get the Stratis device identifiers for a given encrypted device. - pub fn device_identifiers(&self) -> &StratisIdentifiers { - &self.identifiers - } - - /// Get the name of the activated device when it is activated. - pub fn activation_name(&self) -> &DmName { - &self.activation_name - } - - /// Get the pool name recorded in the LUKS2 metadata. - pub fn pool_name(&self) -> Option<&Name> { - self.pool_name.as_ref() - } - - /// Device number for LUKS2 device. - pub fn device(&self) -> &Device { - &self.device - } -} diff --git a/src/engine/strat_engine/backstore/crypt/mod.rs b/src/engine/strat_engine/backstore/crypt/mod.rs index 29c2f169b0..91edfddbcc 100644 --- a/src/engine/strat_engine/backstore/crypt/mod.rs +++ b/src/engine/strat_engine/backstore/crypt/mod.rs @@ -5,19 +5,13 @@ #[macro_use] mod macros; -mod activate; mod consts; mod handle; -mod initialize; -mod metadata_handle; mod shared; pub use self::{ - activate::CryptActivationHandle, consts::CLEVIS_TANG_TRUST_URL, handle::CryptHandle, - initialize::CryptInitializer, - metadata_handle::CryptMetadataHandle, shared::{ back_up_luks_header, crypt_metadata_size, interpret_clevis_config, restore_luks_header, set_up_crypt_logging, @@ -56,7 +50,7 @@ mod tests { ns::{unshare_mount_namespace, MemoryFilesystem}, tests::{crypt, loopbacked, real}, }, - types::{DevUuid, DevicePath, KeyDescription, Name, PoolUuid, UnlockMethod}, + types::{DevUuid, EncryptionInfo, KeyDescription, Name, PoolUuid, UnlockMethod}, }, stratis::StratisError, }; @@ -77,13 +71,18 @@ mod tests { let pool_name = Name::new("pool_name".to_string()); let dev_uuid = DevUuid::new_v4(); - let result = CryptInitializer::new(DevicePath::new(path).unwrap(), pool_uuid, dev_uuid) - .initialize(pool_name, Some(&key_description), None); + let result = CryptHandle::initialize( + path, + pool_uuid, + dev_uuid, + pool_name, + &EncryptionInfo::KeyDesc(key_description), + ); // Initialization cannot occur with a non-existent key assert!(result.is_err()); - assert!(CryptHandle::setup(path).unwrap().is_none()); + assert!(CryptHandle::load_metadata(path).unwrap().is_none()); // TODO: Check actual superblock with libblkid } @@ -118,13 +117,18 @@ mod tests { for path in paths { let dev_uuid = DevUuid::new_v4(); - let handle = CryptInitializer::new(DevicePath::new(path)?, pool_uuid, dev_uuid) - .initialize(pool_name.clone(), Some(key_desc), None)?; + let handle = CryptHandle::initialize( + path, + pool_uuid, + dev_uuid, + pool_name.clone(), + &EncryptionInfo::KeyDesc(key_desc.clone()), + )?; handles.push(handle); } for path in paths { - if !CryptActivationHandle::can_unlock(path, true, false) { + if !CryptHandle::can_unlock(path, true, false) { return Err(Box::new(StratisError::Msg( "All devices should be able to be unlocked".to_string(), ))); @@ -136,7 +140,7 @@ mod tests { } for path in paths { - if !CryptActivationHandle::can_unlock(path, true, false) { + if !CryptHandle::can_unlock(path, true, false) { return Err(Box::new(StratisError::Msg( "All devices should be able to be unlocked".to_string(), ))); @@ -148,7 +152,7 @@ mod tests { } for path in paths { - if CryptActivationHandle::can_unlock(path, true, false) { + if CryptHandle::can_unlock(path, true, false) { return Err(Box::new(StratisError::Msg( "All devices should no longer be able to be unlocked".to_string(), ))); @@ -205,8 +209,13 @@ mod tests { let pool_name = Name::new("pool_name".to_string()); let dev_uuid = DevUuid::new_v4(); - let handle = CryptInitializer::new(DevicePath::new(path)?, pool_uuid, dev_uuid) - .initialize(pool_name, Some(key_desc), None)?; + let handle = CryptHandle::initialize( + path, + pool_uuid, + dev_uuid, + pool_name, + &EncryptionInfo::KeyDesc(key_desc.clone()), + )?; let logical_path = handle.activated_device_path(); const WINDOW_SIZE: usize = 1024 * 1024; @@ -300,7 +309,7 @@ mod tests { handle.deactivate()?; let handle = - CryptActivationHandle::setup(path, UnlockMethod::Keyring)?.ok_or_else(|| { + CryptHandle::setup(path, Some(UnlockMethod::Keyring))?.ok_or_else(|| { Box::new(io::Error::new( io::ErrorKind::Other, format!( @@ -361,18 +370,18 @@ mod tests { .copied() .ok_or_else(|| StratisError::Msg("Expected exactly one path".to_string()))?; let pool_name = Name::new("pool_name".to_string()); - let handle = CryptInitializer::new( - DevicePath::new(path)?, + let handle = CryptHandle::initialize( + path, PoolUuid::new_v4(), DevUuid::new_v4(), - ) - .initialize( pool_name, - Some(key_desc), - Some(&( - "tang".to_string(), - json!({"url": env::var("TANG_URL")?, "stratis:tang:trust_url": true}), - )), + &EncryptionInfo::Both( + key_desc.clone(), + ( + "tang".to_string(), + json!({"url": env::var("TANG_URL")?, "stratis:tang:trust_url": true}), + ), + ), )?; let mut device = acquire_crypt_device(handle.luks2_device_path())?; @@ -424,15 +433,12 @@ mod tests { let path = paths[0]; let pool_name = Name::new("pool_name".to_string()); - let handle = CryptInitializer::new( - DevicePath::new(path).unwrap(), + let handle = CryptHandle::initialize( + path, PoolUuid::new_v4(), DevUuid::new_v4(), - ) - .initialize( pool_name, - None, - Some(&( + &EncryptionInfo::ClevisInfo(( "tang".to_string(), json!({"url": env::var("TANG_URL").unwrap(), "stratis:tang:trust_url": true}), )), diff --git a/src/engine/strat_engine/backstore/crypt/shared.rs b/src/engine/strat_engine/backstore/crypt/shared.rs index 87d87fa7bc..7e94a4f9c2 100644 --- a/src/engine/strat_engine/backstore/crypt/shared.rs +++ b/src/engine/strat_engine/backstore/crypt/shared.rs @@ -45,12 +45,12 @@ use crate::{ STRATIS_TOKEN_TYPE, TOKEN_KEYSLOTS_KEY, TOKEN_TYPE_KEY, }, handle::CryptHandle, - metadata_handle::CryptMetadataHandle, }, devices::get_devno_from_path, }, cmd::clevis_luks_unlock, dm::get_dm, + dm::DEVICEMAPPER_PATH, keys, metadata::StratisIdentifiers, }, @@ -62,6 +62,8 @@ use crate::{ stratis::{StratisError, StratisResult}, }; +use super::handle::CryptMetadata; + /// Set up crypt logging to log cryptsetup debug information at the trace level. pub fn set_up_crypt_logging() { fn logging_callback(level: CryptLogLevel, msg: &str, _: Option<&mut ()>) { @@ -367,15 +369,19 @@ pub fn setup_crypt_device(physical_path: &Path) -> StratisResult StratisResult> { - let identifiers = identifiers_from_metadata(device)?; - let activation_name = activation_name_from_metadata(device)?; - let pool_name = pool_name_from_metadata(device)?; - let key_description = key_desc_from_metadata(device); +/// Load crypt device metadata. +pub fn load_crypt_metadata(physical_path: &Path) -> StratisResult> { + let physical = DevicePath::new(physical_path)?; + + let mut device = match setup_crypt_device(physical_path)? { + Some(d) => d, + None => return Ok(None), + }; + + let identifiers = identifiers_from_metadata(&mut device)?; + let activation_name = activation_name_from_metadata(&mut device)?; + let pool_name = pool_name_from_metadata(&mut device)?; + let key_description = key_desc_from_metadata(&mut device); let devno = get_devno_from_path(physical_path)?; let key_description = match key_description .as_ref() @@ -396,23 +402,24 @@ pub fn setup_crypt_metadata_handle( } None => None, }; - let clevis_info = clevis_info_from_metadata(device)?; + let clevis_info = clevis_info_from_metadata(&mut device)?; - let encryption_info = match (key_description, clevis_info) { - (Some(kd), Some(ci)) => EncryptionInfo::Both(kd, ci), - (Some(kd), _) => EncryptionInfo::KeyDesc(kd), - (_, Some(ci)) => EncryptionInfo::ClevisInfo(ci), - (None, None) => return Ok(None), - }; + let encryption_info = EncryptionInfo::from_options((key_description, clevis_info)) + .expect("Must have at least one unlock method"); - Ok(Some(CryptMetadataHandle::new( - DevicePath::new(physical_path)?, + let path = vec![DEVICEMAPPER_PATH, &activation_name.to_string()] + .into_iter() + .collect::(); + let activated_path = path.canonicalize().unwrap_or(path); + Ok(Some(CryptMetadata { + physical_path: physical, identifiers, encryption_info, activation_name, pool_name, - devno, - ))) + device: devno, + activated_path, + })) } /// Set up a handle to a crypt device using either Clevis or the keyring to activate @@ -422,48 +429,41 @@ pub fn setup_crypt_handle( physical_path: &Path, unlock_method: Option, ) -> StratisResult> { - let metadata_handle = match setup_crypt_metadata_handle(device, physical_path)? { - Some(handle) => handle, + let metadata = match load_crypt_metadata(physical_path)? { + Some(m) => m, None => return Ok(None), }; - let name = activation_name_from_metadata(device)?; - - match unlock_method { - Some(UnlockMethod::Keyring) => { - activate(Either::Left(( - device, - metadata_handle.encryption_info().key_description() - .ok_or_else(|| { - StratisError::Msg( - "Unlock action was specified to be keyring but not key description is present in the metadata".to_string(), - ) - })?, - )), &name)? - } - Some(UnlockMethod::Clevis) => activate(Either::Right(physical_path), &name)?, - None => { - if let Err(_) | Ok(CryptStatusInfo::Inactive | CryptStatusInfo::Invalid) = libcryptsetup_rs::status(Some(device), &name.to_string()) { - return Err(StratisError::Msg( - "Found a crypt device but it is not activated and no unlock method was provided".to_string(), - )); - } - }, - }; - - match CryptHandle::new_with_metadata_handle(metadata_handle) { - Ok(h) => Ok(Some(h)), - Err(e) => { - if let Err(err) = ensure_inactive(device, &name) { - Err(StratisError::NoActionRollbackError { - causal_error: Box::new(e), - rollback_error: Box::new(err), - }) - } else { - Err(e) + if !vec![DEVICEMAPPER_PATH, &metadata.activation_name.to_string()] + .into_iter() + .collect::() + .exists() + { + match unlock_method { + Some(UnlockMethod::Keyring) => { + activate(Either::Left(( + device, + metadata.encryption_info.key_description() + .ok_or_else(|| { + StratisError::Msg( + "Unlock action was specified to be keyring but not key description is present in the metadata".to_string(), + ) + })?, + )), &metadata.activation_name)? } - } + Some(UnlockMethod::Clevis) => activate(Either::Right(physical_path), &metadata.activation_name)?, + None => (), + }; } + + Ok(Some(CryptHandle::new( + metadata.physical_path, + metadata.identifiers.pool_uuid, + metadata.identifiers.device_uuid, + metadata.encryption_info, + metadata.pool_name, + metadata.device, + ))) } /// Create a device handle and load the LUKS2 header into memory from diff --git a/src/engine/strat_engine/backstore/devices.rs b/src/engine/strat_engine/backstore/devices.rs index 7a311cb355..e5a67960ef 100644 --- a/src/engine/strat_engine/backstore/devices.rs +++ b/src/engine/strat_engine/backstore/devices.rs @@ -24,20 +24,19 @@ use crate::{ strat_engine::{ backstore::{ blockdev::{StratBlockDev, UnderlyingDevice}, - crypt::{CryptHandle, CryptInitializer}, + crypt::CryptHandle, }, device::{blkdev_logical_sector_size, blkdev_physical_sector_size, blkdev_size}, metadata::{ device_identifiers, disown_device, BlockdevSize, MDADataSize, StratisIdentifiers, BDA, }, - names::KeyDescription, udev::{ block_device_apply, decide_ownership, get_udev_property, UdevOwnership, STRATIS_FS_TYPE, }, }, - types::{ClevisInfo, DevUuid, DevicePath, EncryptionInfo, Name, PoolUuid}, + types::{DevUuid, DevicePath, EncryptionInfo, Name, PoolUuid}, }, stratis::{StratisError, StratisResult}, }; @@ -520,11 +519,15 @@ pub fn initialize_devices( pool_name: Name, pool_uuid: PoolUuid, dev_uuid: DevUuid, - key_description: Option<&KeyDescription>, - enable_clevis: Option<&ClevisInfo>, + encryption_info: &EncryptionInfo, ) -> StratisResult<(CryptHandle, Device, Sectors)> { - let handle = CryptInitializer::new(DevicePath::new(physical_path)?, pool_uuid, dev_uuid) - .initialize(pool_name, key_description, enable_clevis)?; + let handle = CryptHandle::initialize( + physical_path, + pool_uuid, + dev_uuid, + pool_name, + encryption_info, + )?; let device_size = match handle.logical_device_size() { Ok(size) => size, @@ -651,31 +654,25 @@ pub fn initialize_devices( ) -> StratisResult { let dev_uuid = DevUuid::new_v4(); let (handle, devno, blockdev_size) = if let Some(ei) = encryption_info { - initialize_encrypted( - &dev_info.devnode, - pool_name, - pool_uuid, - dev_uuid, - ei.key_description(), - ei.clevis_info(), - ) - .map(|(handle, devno, devsize)| { - debug!( - "Info on physical device {}, logical device {}", - &dev_info.devnode.display(), - handle.activated_device_path().display(), - ); - debug!( - "Physical device size: {}, logical device size: {}", - dev_info.size, - devsize.bytes(), - ); - debug!( - "Physical device numbers: {}, logical device numbers: {}", - dev_info.devno, devno, - ); - (Some(handle), devno, devsize) - })? + initialize_encrypted(&dev_info.devnode, pool_name, pool_uuid, dev_uuid, ei).map( + |(handle, devno, devsize)| { + debug!( + "Info on physical device {}, logical device {}", + &dev_info.devnode.display(), + handle.activated_device_path().display(), + ); + debug!( + "Physical device size: {}, logical device size: {}", + dev_info.size, + devsize.bytes(), + ); + debug!( + "Physical device numbers: {}, logical device numbers: {}", + dev_info.devno, devno, + ); + (Some(handle), devno, devsize) + }, + )? } else { (None, dev_info.devno, dev_info.size.sectors()) }; @@ -813,10 +810,13 @@ pub fn wipe_blockdevs(blockdevs: &mut [StratBlockDev]) -> StratisResult<()> { mod tests { use std::{error::Error, fs::OpenOptions}; - use crate::engine::strat_engine::{ - backstore::crypt::CryptHandle, - metadata::device_identifiers, - tests::{crypt, loopbacked, real}, + use crate::engine::{ + strat_engine::{ + backstore::crypt::CryptHandle, + metadata::device_identifiers, + tests::{crypt, loopbacked, real}, + }, + types::KeyDescription, }; use super::*; @@ -966,7 +966,7 @@ mod tests { for path in paths { if key_description.is_some() { - if CryptHandle::setup(path)?.is_some() { + if CryptHandle::load_metadata(path)?.is_some() { return Err(Box::new(StratisError::Msg( "LUKS2 metadata on Stratis devices was not successfully wiped".to_string(), ))); @@ -1117,7 +1117,7 @@ mod tests { // identifiers as all the other paths that were initialized. for path in paths { if key_desc.is_some() { - if CryptHandle::setup(path)?.is_some() { + if CryptHandle::load_metadata(path)?.is_some() { return Err(Box::new(StratisError::Msg(format!( "Device {} should have no LUKS2 metadata", path.display() diff --git a/src/engine/strat_engine/backstore/mod.rs b/src/engine/strat_engine/backstore/mod.rs index 0032498d4b..ae51a8d187 100644 --- a/src/engine/strat_engine/backstore/mod.rs +++ b/src/engine/strat_engine/backstore/mod.rs @@ -17,10 +17,7 @@ mod transaction; pub use self::{ backstore::Backstore, blockdev::{StratBlockDev, UnderlyingDevice}, - crypt::{ - crypt_metadata_size, set_up_crypt_logging, CryptActivationHandle, CryptHandle, - CryptMetadataHandle, CLEVIS_TANG_TRUST_URL, - }, + crypt::{crypt_metadata_size, set_up_crypt_logging, CryptHandle, CLEVIS_TANG_TRUST_URL}, devices::{ find_stratis_devs_by_uuid, initialize_devices, BlockSizes, ProcessedPathInfos, UnownedDevices, diff --git a/src/engine/strat_engine/liminal/identify.rs b/src/engine/strat_engine/liminal/identify.rs index 6eed0baba9..45bbb7c11f 100644 --- a/src/engine/strat_engine/liminal/identify.rs +++ b/src/engine/strat_engine/liminal/identify.rs @@ -52,7 +52,7 @@ use devicemapper::Device; use crate::engine::{ strat_engine::{ - backstore::{CryptMetadataHandle, StratBlockDev}, + backstore::{CryptHandle, StratBlockDev}, metadata::{static_header, StratisIdentifiers, BDA}, udev::{ block_enumerator, decide_ownership, UdevOwnership, CRYPTO_FS_TYPE, FS_TYPE_KEY, @@ -264,35 +264,25 @@ pub fn bda_wrapper(devnode: &Path) -> Result, String>, String /// Process a device which udev information indicates is a LUKS device. fn process_luks_device(dev: &UdevEngineDevice) -> Option { match dev.devnode() { - Some(devnode) => match device_to_devno_wrapper(dev) { + Some(devnode) => match CryptHandle::load_metadata(devnode) { + Ok(None) => None, Err(err) => { warn!( - "udev identified device {} as a Stratis device but {}, disregarding the device", - devnode.display(), - err - ); - None - } - Ok(device_number) => match CryptMetadataHandle::setup(devnode) { - Ok(None) => None, - Err(err) => { - warn!( "udev identified device {} as a LUKS device, but could not read LUKS header from the device, disregarding the device: {}", devnode.display(), err, ); - None - } - Ok(Some(handle)) => Some(LuksInfo { - dev_info: StratisDevInfo { - device_number, - devnode: handle.luks2_device_path().to_path_buf(), - }, - identifiers: *handle.device_identifiers(), - encryption_info: handle.encryption_info().to_owned(), - pool_name: handle.pool_name().cloned(), - }), - }, + None + } + Ok(Some(metadata)) => Some(LuksInfo { + dev_info: StratisDevInfo { + device_number: metadata.device, + devnode: metadata.physical_path.to_path_buf(), + }, + identifiers: metadata.identifiers, + encryption_info: metadata.encryption_info, + pool_name: metadata.pool_name, + }), }, None => { warn!("udev identified a device as a LUKS2 device, but the udev entry for the device had no device node, disregarding device"); diff --git a/src/engine/strat_engine/liminal/liminal.rs b/src/engine/strat_engine/liminal/liminal.rs index 4b78bc5e21..b2a2c8548b 100644 --- a/src/engine/strat_engine/liminal/liminal.rs +++ b/src/engine/strat_engine/liminal/liminal.rs @@ -17,10 +17,11 @@ use crate::{ engine::{ engine::{DumpState, Pool, StateDiff}, strat_engine::{ - backstore::{ - find_stratis_devs_by_uuid, CryptActivationHandle, CryptHandle, StratBlockDev, + backstore::{find_stratis_devs_by_uuid, CryptHandle, StratBlockDev}, + dm::{ + has_leftover_devices, list_of_crypt_devices, remove_optional_devices, + stop_partially_constructed_pool, }, - dm::{has_leftover_devices, stop_partially_constructed_pool}, liminal::{ device_info::{ reconstruct_stratis_infos, split_stratis_infos, stratis_infos_ref, DeviceSet, @@ -96,9 +97,7 @@ impl LiminalDevices { luks_info: &LLuksInfo, unlock_method: UnlockMethod, ) -> StratisResult { - if let Some(h) = - CryptActivationHandle::setup(&luks_info.dev_info.devnode, unlock_method)? - { + if let Some(h) = CryptHandle::setup(&luks_info.dev_info.devnode, Some(unlock_method))? { Ok(h) } else { Err(StratisError::Msg(format!( @@ -142,7 +141,7 @@ impl LiminalDevices { e, unlocked .into_iter() - .map(|(_, handle)| handle) + .map(|(dev_uuid, _)| dev_uuid) .collect::>(), )); } @@ -217,20 +216,24 @@ impl LiminalDevices { (Err(e), _) => return Err(e), }; - let uuids = unlocked_devices - .iter() - .map(|(dev_uuid, _)| *dev_uuid) - .collect::>(); - let handles = unlocked_devices - .into_iter() - .map(|(_, h)| h) - .collect::>(); + let (uuids, handles) = unlocked_devices.into_iter().fold( + (Vec::new(), HashMap::new()), + |(mut uuids, mut handles), (uuid, handle)| { + uuids.push(uuid); + handles.insert(uuid, handle); + (uuids, handles) + }, + ); let mut stopped_pool = self .stopped_pools .remove(&pool_uuid) .or_else(|| self.partially_constructed_pools.remove(&pool_uuid)) .expect("Checked above"); + let all_uuids = stopped_pool + .iter() + .map(|(dev_uuid, _)| *dev_uuid) + .collect::>(); match find_stratis_devs_by_uuid(pool_uuid, uuids) { Ok(infos) => infos.into_iter().for_each(|(dev_uuid, (path, devno))| { if let Ok(Ok(Some(bda))) = bda_wrapper(&path) { @@ -253,14 +256,14 @@ impl LiminalDevices { }), Err(e) => { warn!("Failed to scan for newly unlocked Stratis devices: {}", e); - let err = handle_unlock_rollback(e, handles); + let err = handle_unlock_rollback(e, all_uuids); return Err(err); } }; - match self.try_setup_pool(pools, pool_uuid, stopped_pool) { + match self.try_setup_pool(pools, pool_uuid, stopped_pool, handles) { Ok((name, pool)) => Ok((name, pool_uuid, pool)), - Err(e) => Err(handle_unlock_rollback(e, handles)), + Err(e) => Err(handle_unlock_rollback(e, all_uuids)), } } @@ -537,6 +540,7 @@ impl LiminalDevices { pools: &Table, pool_uuid: PoolUuid, device_set: DeviceSet, + handles: HashMap, ) -> StratisResult<(Name, StratPool)> { fn try_setup_pool_failure( pools: &Table, @@ -544,6 +548,7 @@ impl LiminalDevices { luks_info: StratisResult<(Option, MaybeInconsistent>)>, infos: &HashMap, bdas: HashMap, + handles: HashMap, meta_res: StratisResult<(DateTime, PoolSave)>, ) -> BDARecordResult<(Name, StratPool)> { let (timestamp, metadata) = match meta_res { @@ -552,7 +557,7 @@ impl LiminalDevices { }; setup_pool( - pools, pool_uuid, luks_info, infos, bdas, timestamp, metadata, + pools, pool_uuid, luks_info, infos, bdas, handles, timestamp, metadata, ) } @@ -576,7 +581,7 @@ impl LiminalDevices { let res = load_stratis_metadata(pool_uuid, stratis_infos_ref(&infos)); let (infos, bdas) = split_stratis_infos(infos); - match try_setup_pool_failure(pools, pool_uuid, luks_info, &infos, bdas, res) { + match try_setup_pool_failure(pools, pool_uuid, luks_info, &infos, bdas, handles, res) { Ok((name, pool)) => { self.uuid_lookup = self .uuid_lookup @@ -633,8 +638,22 @@ impl LiminalDevices { Err(e) => return Err((e, bdas)), }; if let Some(true) | None = metadata.started { + let mut handles = HashMap::default(); + for (dev_uuid, info) in infos { + if let Some((dev_uuid, handle)) = match info.luks.as_ref() { + Some(l) => match CryptHandle::setup(&l.dev_info.devnode, None) { + Ok(Some(handle)) => Some((dev_uuid, handle)), + Ok(None) => None, + Err(e) => return Err((e, bdas)), + }, + None => None, + } { + handles.insert(*dev_uuid, handle); + } + } + setup_pool( - pools, pool_uuid, luks_info, infos, bdas, timestamp, metadata, + pools, pool_uuid, luks_info, infos, bdas, handles, timestamp, metadata, ) .map(Either::Left) } else { @@ -1023,12 +1042,14 @@ fn load_stratis_metadata( /// /// If there is a name conflict between the set of devices in devices /// and some existing pool, return an error. +#[allow(clippy::too_many_arguments)] fn setup_pool( pools: &Table, pool_uuid: PoolUuid, luks_info: StratisResult<(Option, MaybeInconsistent>)>, infos: &HashMap, bdas: HashMap, + handles: HashMap, timestamp: DateTime, metadata: PoolSave, ) -> BDARecordResult<(Name, StratPool)> { @@ -1042,7 +1063,7 @@ fn setup_pool( )), bdas)); } - let (datadevs, cachedevs) = match get_blockdevs(&metadata.backstore, infos, bdas) { + let (datadevs, cachedevs) = match get_blockdevs(&metadata.backstore, infos, bdas, handles) { Err((err, bdas)) => return Err( (StratisError::Chained( format!( @@ -1116,18 +1137,17 @@ fn setup_pool( /// Rollback an unlock operation for some or all devices of a pool that have been /// unlocked prior to the failure occurring. -fn handle_unlock_rollback(causal_error: StratisError, handles: Vec) -> StratisError { - for handle in handles { - if let Err(e) = handle.deactivate() { - warn!("Failed to roll back encrypted pool unlock; some previously locked encrypted devices may be left in an unlocked state"); - return StratisError::NoActionRollbackError { +fn handle_unlock_rollback(causal_error: StratisError, uuids: Vec) -> StratisError { + let devices = list_of_crypt_devices(&uuids); + if let Err(e) = remove_optional_devices(devices) { + warn!("Failed to roll back encrypted pool unlock; some previously locked encrypted devices may be left in an unlocked state"); + return StratisError::NoActionRollbackError { causal_error: Box::new(causal_error), rollback_error: Box::new(StratisError::Chained( "Failed to roll back encrypted pool unlock; some previously locked encrypted devices may be left in an unlocked state".to_string(), Box::new(e), )), }; - } } causal_error diff --git a/src/engine/strat_engine/liminal/setup.rs b/src/engine/strat_engine/liminal/setup.rs index 02806fb8d9..e8080df766 100644 --- a/src/engine/strat_engine/liminal/setup.rs +++ b/src/engine/strat_engine/liminal/setup.rs @@ -137,6 +137,7 @@ pub fn get_blockdevs( backstore_save: &BackstoreSave, infos: &HashMap, mut bdas: HashMap, + mut handles: HashMap, ) -> BDARecordResult<(Vec, Vec)> { let recorded_data_map: HashMap = backstore_save .data_tier @@ -184,6 +185,7 @@ pub fn get_blockdevs( fn get_blockdev( info: &LStratisDevInfo, bda: BDA, + handle: Option, data_map: &HashMap, cache_map: &HashMap, segment_table: &HashMap>, @@ -247,10 +249,6 @@ pub fn get_blockdevs( Some(luks) => &luks.dev_info.devnode, None => &info.dev_info.devnode, }; - let handle = match CryptHandle::setup(physical_path) { - Ok(h) => h, - Err(e) => return Err((e, bda)), - }; let underlying_device = match handle { Some(handle) => UnderlyingDevice::Encrypted(handle), None => UnderlyingDevice::Unencrypted(match DevicePath::new(physical_path) { @@ -278,6 +276,7 @@ pub fn get_blockdevs( match get_blockdev( infos.get(dev_uuid).expect("bdas.keys() == infos.keys()"), bdas.remove(dev_uuid).expect("bdas.keys() == infos.keys()"), + handles.remove(dev_uuid), &recorded_data_map, &recorded_cache_map, &segment_table, diff --git a/src/engine/strat_engine/shared.rs b/src/engine/strat_engine/shared.rs index 3588f8f2e0..403f5e5500 100644 --- a/src/engine/strat_engine/shared.rs +++ b/src/engine/strat_engine/shared.rs @@ -2,13 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use std::{collections::HashMap, path::Path}; +use std::collections::HashMap; use crate::engine::{ - strat_engine::{ - backstore::{CryptActivationHandle, StratBlockDev}, - metadata::BDA, - }, + strat_engine::{backstore::StratBlockDev, metadata::BDA}, types::DevUuid, }; @@ -32,10 +29,3 @@ pub fn tiers_to_bdas( .chain(bda.map(|bda| (bda.dev_uuid(), bda))) .collect::>() } - -/// Check that the registered key description and Clevis information for these -/// block devices can unlock at least one of the existing block devices registered. -/// Precondition: self.block_devs must have at least one device. -pub fn can_unlock(physical_path: &Path, try_unlock_keyring: bool, try_unlock_clevis: bool) -> bool { - CryptActivationHandle::can_unlock(physical_path, try_unlock_keyring, try_unlock_clevis) -}