diff --git a/Cargo.lock b/Cargo.lock index 0dc844ffef..2edac18526 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -784,16 +784,16 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libcryptsetup-rs" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99a61d3782d841dca88244f582cfd95d96da9d175fb06616d50a480058647e39" +checksum = "704059b8d81fc01bed7ea77d4572ce2ef08408ace42d95fe781d339eebf64dec" dependencies = [ "bitflags 2.4.0", "either", - "lazy_static", "libc", "libcryptsetup-rs-sys", "log", + "once_cell", "pkg-config", "semver", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 04efea6ee5..12eef039c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,7 +142,7 @@ version = "0.2.147" optional = true [dependencies.libcryptsetup-rs] -version = "0.9.3" +version = "0.10.0" features = ["mutex"] optional = true diff --git a/src/dbus_api/pool/pool_3_0/api.rs b/src/dbus_api/pool/pool_3_0/api.rs index 8a5aa5083c..6c3caa6e13 100644 --- a/src/dbus_api/pool/pool_3_0/api.rs +++ b/src/dbus_api/pool/pool_3_0/api.rs @@ -130,7 +130,7 @@ pub fn add_cachedevs_method(f: &Factory, TData>) -> Method, TData>) -> Property, TData> { f.property::(consts::POOL_ENCRYPTED_PROP, ()) .access(Access::Read) - .emits_changed(EmitsChangedSignal::Const) + .emits_changed(EmitsChangedSignal::True) .on_get(get_pool_encrypted) } diff --git a/src/engine/engine.rs b/src/engine/engine.rs index 66d01ef8fb..31f27feee8 100644 --- a/src/engine/engine.rs +++ b/src/engine/engine.rs @@ -21,11 +21,12 @@ use crate::{ structures::{AllLockReadGuard, AllLockWriteGuard, SomeLockReadGuard, SomeLockWriteGuard}, types::{ ActionAvailability, BlockDevTier, Clevis, CreateAction, DeleteAction, DevUuid, - EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, LockedPoolsInfo, - MappingCreateAction, MappingDeleteAction, Name, PoolDiff, PoolEncryptionInfo, - PoolIdentifier, PoolUuid, RegenAction, RenameAction, ReportType, SetCreateAction, - SetDeleteAction, SetUnlockAction, StartAction, StopAction, StoppedPoolsInfo, - StratFilesystemDiff, StratSigblockVersion, UdevEngineEvent, UnlockMethod, + EncryptedDevice, EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, + LockedPoolsInfo, MappingCreateAction, MappingDeleteAction, Name, PoolDiff, + PoolEncryptionInfo, PoolIdentifier, PoolUuid, RegenAction, RenameAction, ReportType, + SetCreateAction, SetDeleteAction, SetUnlockAction, StartAction, StopAction, + StoppedPoolsInfo, StratFilesystemDiff, StratSigblockVersion, UdevEngineEvent, + UnlockMethod, }, }, stratis::StratisResult, @@ -342,6 +343,13 @@ pub trait Pool: Debug + Send + Sync { limit: Option, ) -> StratisResult>>; + /// Encrypted an unencrypted pool. + fn encrypt_pool( + &mut self, + pool_uuid: PoolUuid, + encryption_info: &EncryptionInfo, + ) -> StratisResult>; + /// Return the metadata that would be written if metadata were written. fn current_metadata(&self, pool_name: &Name) -> StratisResult; diff --git a/src/engine/sim_engine/pool.rs b/src/engine/sim_engine/pool.rs index 45f45eb0f6..88a11a34b7 100644 --- a/src/engine/sim_engine/pool.rs +++ b/src/engine/sim_engine/pool.rs @@ -24,8 +24,8 @@ use crate::{ structures::Table, types::{ ActionAvailability, BlockDevTier, Clevis, CreateAction, DeleteAction, DevUuid, - EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, Name, PoolDiff, - PoolEncryptionInfo, PoolUuid, RegenAction, RenameAction, SetCreateAction, + EncryptedDevice, EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, Name, + PoolDiff, PoolEncryptionInfo, PoolUuid, RegenAction, RenameAction, SetCreateAction, SetDeleteAction, StratSigblockVersion, }, PropChangeAction, @@ -746,6 +746,15 @@ impl Pool for SimPool { } } + fn encrypt_pool( + &mut self, + _pool_uuid: PoolUuid, + enc: &EncryptionInfo, + ) -> StratisResult> { + self.encryption_info = Some(enc.clone()); + Ok(CreateAction::Created(EncryptedDevice)) + } + fn current_metadata(&self, pool_name: &Name) -> StratisResult { serde_json::to_string(&self.record(pool_name)).map_err(|e| e.into()) } diff --git a/src/engine/strat_engine/backstore/backstore/v2.rs b/src/engine/strat_engine/backstore/backstore/v2.rs index 3249ddfacc..ccfa964507 100644 --- a/src/engine/strat_engine/backstore/backstore/v2.rs +++ b/src/engine/strat_engine/backstore/backstore/v2.rs @@ -4,7 +4,7 @@ // Code to handle the backing store of a pool. -use std::{cmp, collections::HashMap, iter::once, path::PathBuf}; +use std::{cmp, collections::HashMap, iter::once, path::Path, path::PathBuf}; use chrono::{DateTime, Utc}; use either::Either; @@ -29,6 +29,7 @@ use crate::{ names::{format_backstore_ids, CacheRole}, serde_structs::{BackstoreSave, CapSave, PoolFeatures, PoolSave, Recordable}, shared::bds_to_bdas, + thinpool::ThinPool, types::BDARecordResult, writing::wipe_sectors, }, @@ -1106,6 +1107,38 @@ impl Backstore { self.data_tier.grow(dev) } + pub fn encrypt( + &mut self, + pool_uuid: PoolUuid, + thinpool: &mut ThinPool, + encryption_info: &EncryptionInfo, + ) -> StratisResult { + let (dm_name, _) = format_backstore_ids(pool_uuid, CacheRole::Cache); + match self.enc.as_ref() { + Some(inner) => { + let enc = match inner { + Either::Left(enc) => enc, + Either::Right(handle) => handle.encryption_info(), + }; + if enc != encryption_info { + Err(StratisError::Msg("Encryption information does not match the existing encryption information for encrypted pool".to_string())) + } else { + Ok(false) + } + } + None => { + let handle = CryptHandle::encrypt( + pool_uuid, + thinpool, + Path::new(&format!("/dev/mapper/{}", &dm_name.to_string())), + encryption_info, + )?; + self.enc = Some(Either::Right(handle)); + Ok(true) + } + } + } + /// A summary of block sizes pub fn block_size_summary(&self, tier: BlockDevTier) -> Option { match tier { diff --git a/src/engine/strat_engine/crypt/handle/v1.rs b/src/engine/strat_engine/crypt/handle/v1.rs index b31a716223..a667d1d0ff 100644 --- a/src/engine/strat_engine/crypt/handle/v1.rs +++ b/src/engine/strat_engine/crypt/handle/v1.rs @@ -31,7 +31,7 @@ use crate::{ engine::MAX_STRATIS_PASS_SIZE, strat_engine::{ backstore::get_devno_from_path, - cmd::{clevis_decrypt, clevis_luks_bind, clevis_luks_regen, clevis_luks_unbind}, + cmd::{clevis_luks_bind, clevis_luks_regen, clevis_luks_unbind}, crypt::{ consts::{ CLEVIS_LUKS_TOKEN_ID, DEFAULT_CRYPT_KEYSLOTS_SIZE, DEFAULT_CRYPT_METADATA_SIZE, @@ -42,8 +42,8 @@ use crate::{ }, shared::{ acquire_crypt_device, activate, add_keyring_keyslot, check_luks2_token, - clevis_info_from_metadata, device_from_physical_path, ensure_inactive, - ensure_wiped, get_keyslot_number, interpret_clevis_config, + clevis_decrypt, clevis_info_from_metadata, device_from_physical_path, + ensure_inactive, ensure_wiped, get_keyslot_number, interpret_clevis_config, key_desc_from_metadata, luks2_token_type_is_valid, read_key, wipe_fallback, }, }, @@ -956,7 +956,7 @@ impl CryptHandle { /// Add a keyring binding to the underlying LUKS2 volume. pub fn bind_keyring(&mut self, key_desc: &KeyDescription) -> StratisResult<()> { let mut device = self.acquire_crypt_device()?; - let key = Self::clevis_decrypt(&mut device)?.ok_or_else(|| { + let key = clevis_decrypt(&mut device)?.ok_or_else(|| { StratisError::Msg( "The Clevis token appears to have been wiped outside of \ Stratis; cannot add a keyring key binding without an existing \ @@ -1030,24 +1030,6 @@ impl CryptHandle { replace_pool_name(&mut device, pool_name) } - /// Decrypt a Clevis passphrase and return it securely. - fn clevis_decrypt(device: &mut CryptDevice) -> StratisResult> { - let mut token = match device.token_handle().json_get(CLEVIS_LUKS_TOKEN_ID).ok() { - Some(t) => t, - None => return Ok(None), - }; - let jwe = token - .as_object_mut() - .and_then(|map| map.remove("jwe")) - .ok_or_else(|| { - StratisError::Msg(format!( - "Token slot {CLEVIS_LUKS_TOKEN_ID} is occupied but does not appear to be a Clevis \ - token; aborting" - )) - })?; - clevis_decrypt(&jwe).map(Some) - } - /// Deactivate the device referenced by the current device handle. pub fn deactivate(&self) -> StratisResult<()> { ensure_inactive(&mut self.acquire_crypt_device()?, self.activation_name()) @@ -1097,7 +1079,7 @@ impl CryptHandle { StratisError::Msg("Failed to find key with key description".to_string()) })? } else if self.encryption_info().clevis_info().is_some() { - Self::clevis_decrypt(&mut crypt)?.expect("Already checked token exists") + clevis_decrypt(&mut crypt)?.expect("Already checked token exists") } else { unreachable!("Must be encrypted") }; diff --git a/src/engine/strat_engine/crypt/handle/v2.rs b/src/engine/strat_engine/crypt/handle/v2.rs index e3b5a885ac..988918fb21 100644 --- a/src/engine/strat_engine/crypt/handle/v2.rs +++ b/src/engine/strat_engine/crypt/handle/v2.rs @@ -4,7 +4,8 @@ use std::{ fmt::Debug, - fs::File, + fs::{File, OpenOptions}, + io::Write, iter::once, path::{Path, PathBuf}, }; @@ -13,14 +14,19 @@ use either::Either; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use serde_json::Value; -use devicemapper::{Device, DmName, DmNameBuf, Sectors}; +use devicemapper::{Bytes, Device, DmName, DmNameBuf, Sectors, IEC}; +use libblkid_rs::BlkidProbe; use libcryptsetup_rs::{ c_uint, consts::{ - flags::{CryptActivate, CryptVolumeKey}, - vals::{EncryptionFormat, KeyslotsSize, MetadataSize}, + flags::{CryptActivate, CryptReencrypt, CryptVolumeKey}, + vals::{ + CryptReencryptDirectionInfo, CryptReencryptModeInfo, EncryptionFormat, KeyslotsSize, + MetadataSize, + }, }, - CryptDevice, CryptInit, CryptParamsLuks2, CryptParamsLuks2Ref, SafeMemHandle, TokenInput, + CryptDevice, CryptInit, CryptParamsLuks2, CryptParamsLuks2Ref, CryptParamsReencrypt, + SafeMemHandle, TokenInput, }; #[cfg(test)] @@ -29,23 +35,24 @@ use crate::{ engine::{ engine::MAX_STRATIS_PASS_SIZE, strat_engine::{ - backstore::get_devno_from_path, - cmd::{clevis_decrypt, clevis_luks_bind, clevis_luks_regen, clevis_luks_unbind}, + backstore::{backstore::v2, get_devno_from_path}, + cmd::{clevis_luks_bind, clevis_luks_regen, clevis_luks_unbind}, crypt::{ consts::{ CLEVIS_LUKS_TOKEN_ID, DEFAULT_CRYPT_KEYSLOTS_SIZE, DEFAULT_CRYPT_METADATA_SIZE, LUKS2_TOKEN_ID, STRATIS_MEK_SIZE, }, shared::{ - acquire_crypt_device, activate, add_keyring_keyslot, clevis_info_from_metadata, - device_from_physical_path, ensure_wiped, get_keyslot_number, - interpret_clevis_config, key_desc_from_metadata, luks2_token_type_is_valid, - wipe_fallback, + acquire_crypt_device, activate, add_keyring_keyslot, clevis_decrypt, + clevis_info_from_metadata, device_from_physical_path, ensure_wiped, + get_keyslot_number, get_passphrase, interpret_clevis_config, + key_desc_from_metadata, luks2_token_type_is_valid, wipe_fallback, }, }, device::blkdev_size, dm::DEVICEMAPPER_PATH, names::format_crypt_backstore_name, + thinpool::ThinPool, }, types::{ DevicePath, EncryptionInfo, KeyDescription, PoolUuid, SizedKeyMemory, UnlockMethod, @@ -404,10 +411,10 @@ impl CryptHandle { Ok(()) } - fn initialize_with_err( + /// Format the device and initialize the unlock methods. + fn initialize_unlock_methods( device: &mut CryptDevice, physical_path: &Path, - pool_uuid: PoolUuid, encryption_info: &EncryptionInfo, luks2_params: Option<&CryptParamsLuks2>, ) -> StratisResult<()> { @@ -440,6 +447,20 @@ impl CryptHandle { } }; + Ok(()) + } + + /// Format the device and initialize the unlock methods, activating the device once it is + /// successfully set up. + fn initialize_with_err( + device: &mut CryptDevice, + physical_path: &Path, + pool_uuid: PoolUuid, + encryption_info: &EncryptionInfo, + luks2_params: Option<&CryptParamsLuks2>, + ) -> StratisResult<()> { + Self::initialize_unlock_methods(device, physical_path, encryption_info, luks2_params)?; + let activation_name = format_crypt_backstore_name(&pool_uuid); activate( device, @@ -639,7 +660,7 @@ impl CryptHandle { /// Add a keyring binding to the underlying LUKS2 volume. pub fn bind_keyring(&mut self, key_desc: &KeyDescription) -> StratisResult<()> { let mut device = self.acquire_crypt_device()?; - let key = Self::clevis_decrypt(&mut device)?.ok_or_else(|| { + let key = clevis_decrypt(&mut device)?.ok_or_else(|| { StratisError::Msg( "The Clevis token appears to have been wiped outside of \ Stratis; cannot add a keyring key binding without an existing \ @@ -707,22 +728,134 @@ impl CryptHandle { Ok(()) } - /// Decrypt a Clevis passphrase and return it securely. - fn clevis_decrypt(device: &mut CryptDevice) -> StratisResult> { - let mut token = match device.token_handle().json_get(CLEVIS_LUKS_TOKEN_ID).ok() { - Some(t) => t, - None => return Ok(None), + /// Encrypt an unencrypted pool. + pub fn encrypt( + pool_uuid: PoolUuid, + thinpool: &mut ThinPool, + unencrypted_path: &Path, + encryption_info: &EncryptionInfo, + ) -> StratisResult { + let tmp_header = format!("/tmp/temp-header-{pool_uuid}"); + { + let mut file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(&tmp_header)?; + file.write_all(&[0; 4096])?; + } + + let mut device = CryptInit::init(Path::new(&tmp_header))?; + let data_offset = Bytes::from(16 * IEC::Mi).sectors(); + device.set_data_offset(*data_offset)?; + + let sectors = thinpool + .filesystems() + .iter() + .map(|(_, _, fs)| fs.block_size()) + .collect::>>()?; + let min_sector = sectors.iter().min(); + let sector_size = match min_sector { + Some(min) => convert_int!(*min, u64, u32)?, + None => { + let mut probe = BlkidProbe::new_from_filename(unencrypted_path)?; + let top = probe.get_topology()?; + convert_int!(top.get_logical_sector_size(), u64, u32)? + } }; - let jwe = token - .as_object_mut() - .and_then(|map| map.remove("jwe")) - .ok_or_else(|| { - StratisError::Msg(format!( - "Token slot {CLEVIS_LUKS_TOKEN_ID} is occupied but does not appear to be a Clevis \ - token; aborting" - )) - })?; - clevis_decrypt(&jwe).map(Some) + let params = CryptParamsLuks2 { + data_alignment: 0, + data_device: None, + integrity: None, + integrity_params: None, + pbkdf: None, + label: None, + sector_size, + subsystem: None, + }; + + Self::initialize_unlock_methods( + &mut device, + unencrypted_path, + encryption_info, + Some(¶ms), + )?; + let (keyslot, key) = get_passphrase(&mut device, encryption_info)?; + device.reencrypt_handle().reencrypt_init_by_passphrase( + None, + key.as_ref(), + None, + keyslot, + ("aes", "xts-plain"), + CryptParamsReencrypt { + mode: CryptReencryptModeInfo::Encrypt, + direction: CryptReencryptDirectionInfo::Forward, + resilience: "checksum".to_string(), + hash: "sha256".to_string(), + data_shift: 0, + max_hotzone_size: 0, + device_size: 0, + luks2: CryptParamsLuks2 { + data_alignment: 0, + data_device: None, + integrity: None, + integrity_params: None, + pbkdf: None, + label: None, + sector_size, + subsystem: None, + }, + flags: CryptReencrypt::INITIALIZE_ONLY, + }, + )?; + + let mut device = CryptInit::init(unencrypted_path)?; + device + .backup_handle() + .header_restore(Some(EncryptionFormat::Luks2), Path::new(&tmp_header))?; + + let activation_name = &format_crypt_backstore_name(&pool_uuid).to_string(); + device.activate_handle().activate_by_passphrase( + Some(activation_name), + None, + key.as_ref(), + CryptActivate::SHARED, + )?; + + let devno = get_devno_from_path(Path::new(&format!("/dev/mapper/{activation_name}")))?; + thinpool.set_device(devno)?; + + device.reencrypt_handle().reencrypt_init_by_passphrase( + Some(activation_name), + key.as_ref(), + None, + keyslot, + ("aes", "xts-plain"), + CryptParamsReencrypt { + mode: CryptReencryptModeInfo::Encrypt, + direction: CryptReencryptDirectionInfo::Forward, + resilience: "checksum".to_string(), + hash: "sha256".to_string(), + data_shift: 0, + max_hotzone_size: 0, + device_size: 0, + luks2: CryptParamsLuks2 { + data_alignment: 0, + data_device: None, + integrity: None, + integrity_params: None, + pbkdf: None, + label: None, + sector_size, + subsystem: None, + }, + flags: CryptReencrypt::RESUME_ONLY, + }, + )?; + device.reencrypt_handle().reencrypt2::<()>(None, None)?; + + CryptHandle::setup(unencrypted_path, pool_uuid, UnlockMethod::Any, None) + .map(|h| h.expect("should have crypt device after online encrypt")) } /// Deactivate the device referenced by the current device handle. diff --git a/src/engine/strat_engine/crypt/shared.rs b/src/engine/strat_engine/crypt/shared.rs index 630f663f74..f99dd677ba 100644 --- a/src/engine/strat_engine/crypt/shared.rs +++ b/src/engine/strat_engine/crypt/shared.rs @@ -31,7 +31,7 @@ use libcryptsetup_rs::{ use crate::{ engine::{ strat_engine::{ - cmd::clevis_decrypt, + cmd, crypt::consts::{ CLEVIS_LUKS_TOKEN_ID, CLEVIS_RECURSION_LIMIT, CLEVIS_TANG_TRUST_URL, CLEVIS_TOKEN_NAME, DEFAULT_CRYPT_KEYSLOTS_SIZE, DEFAULT_CRYPT_METADATA_SIZE, @@ -42,6 +42,7 @@ use crate::{ keys, }, types::{KeyDescription, SizedKeyMemory, UnlockMethod}, + EncryptionInfo, }, stratis::{StratisError, StratisResult}, }; @@ -800,7 +801,7 @@ fn open_safe(device: &mut CryptDevice, token: libc::c_int) -> StratisResult StratisResult<()> { )?; Ok(()) } + +/// Decrypt a Clevis passphrase and return it securely. +pub fn clevis_decrypt(device: &mut CryptDevice) -> StratisResult> { + let mut token = match device.token_handle().json_get(CLEVIS_LUKS_TOKEN_ID).ok() { + Some(t) => t, + None => return Ok(None), + }; + let jwe = token + .as_object_mut() + .and_then(|map| map.remove("jwe")) + .ok_or_else(|| { + StratisError::Msg(format!( + "Token slot {CLEVIS_LUKS_TOKEN_ID} is occupied but does not appear to be a Clevis \ + token; aborting" + )) + })?; + cmd::clevis_decrypt(&jwe).map(Some) +} + +/// Get one of the passphrases for the encrypted device. +pub fn get_passphrase( + device: &mut CryptDevice, + encryption_info: &EncryptionInfo, +) -> StratisResult<(c_uint, SizedKeyMemory)> { + match encryption_info { + EncryptionInfo::KeyDesc(kd) => Ok(( + get_keyslot_number(device, LUKS2_TOKEN_ID)? + .expect("encryption info specified that there is a keyring binding"), + read_key(kd)?.ok_or_else(|| { + StratisError::Msg("Key description {kd} was not found in the keyring".to_string()) + })?, + )), + EncryptionInfo::ClevisInfo(_) => Ok(( + get_keyslot_number(device, CLEVIS_LUKS_TOKEN_ID)? + .expect("encryption info specified that there is a Clevis binding"), + clevis_decrypt(device)? + .expect("encryption info specified that there is a Clevis binding"), + )), + EncryptionInfo::Both(kd, _) => match read_key(kd) { + Ok(Some(key)) => Ok(( + get_keyslot_number(device, LUKS2_TOKEN_ID)? + .expect("encryption info specified that there is a keyring binding"), + key, + )), + Ok(None) => { + warn!( + "Key description {} not found in keyring; falling back on Clevis", + kd.as_application_str() + ); + Ok(( + get_keyslot_number(device, CLEVIS_LUKS_TOKEN_ID)? + .expect("encryption info specified that there is a Clevis binding"), + clevis_decrypt(device)? + .expect("encryption info specified that there is a Clevis binding"), + )) + } + Err(_) => { + warn!( + "Fetching key description {} failed; falling back on Clevis", + kd.as_application_str() + ); + Ok(( + get_keyslot_number(device, CLEVIS_LUKS_TOKEN_ID)? + .expect("encryption info specified that there is a Clevis binding"), + clevis_decrypt(device)? + .expect("encryption info specified that there is a Clevis binding"), + )) + } + }, + } +} diff --git a/src/engine/strat_engine/pool/dispatch.rs b/src/engine/strat_engine/pool/dispatch.rs index 3c17b2e361..e295b663d5 100644 --- a/src/engine/strat_engine/pool/dispatch.rs +++ b/src/engine/strat_engine/pool/dispatch.rs @@ -14,9 +14,9 @@ use crate::{ strat_engine::pool::{v1, v2}, types::{ ActionAvailability, BlockDevTier, Clevis, CreateAction, DeleteAction, DevUuid, - FilesystemUuid, GrowAction, Key, KeyDescription, Name, PoolDiff, PoolEncryptionInfo, - PoolUuid, PropChangeAction, RegenAction, RenameAction, SetCreateAction, - SetDeleteAction, StratSigblockVersion, + EncryptedDevice, EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, Name, + PoolDiff, PoolEncryptionInfo, PoolUuid, PropChangeAction, RegenAction, RenameAction, + SetCreateAction, SetDeleteAction, StratSigblockVersion, }, }, stratis::StratisResult, @@ -328,6 +328,17 @@ impl Pool for AnyPool { } } + fn encrypt_pool( + &mut self, + pool_uuid: PoolUuid, + encryption_info: &EncryptionInfo, + ) -> StratisResult> { + match self { + AnyPool::V1(p) => p.encrypt_pool(pool_uuid, encryption_info), + AnyPool::V2(p) => p.encrypt_pool(pool_uuid, encryption_info), + } + } + fn current_metadata(&self, pool_name: &Name) -> StratisResult { match self { AnyPool::V1(p) => p.current_metadata(pool_name), diff --git a/src/engine/strat_engine/pool/v1.rs b/src/engine/strat_engine/pool/v1.rs index 932dbe3849..d018a703b2 100644 --- a/src/engine/strat_engine/pool/v1.rs +++ b/src/engine/strat_engine/pool/v1.rs @@ -11,13 +11,10 @@ use devicemapper::{Bytes, DmNameBuf, Sectors}; use stratisd_proc_macros::strat_pool_impl_gen; #[cfg(any(test, feature = "test_extras"))] -use crate::engine::{ - strat_engine::{ - backstore::UnownedDevices, - metadata::MDADataSize, - thinpool::{ThinPoolSizeParams, DATA_BLOCK_SIZE}, - }, - types::EncryptionInfo, +use crate::engine::strat_engine::{ + backstore::UnownedDevices, + metadata::MDADataSize, + thinpool::{ThinPoolSizeParams, DATA_BLOCK_SIZE}, }; use crate::{ engine::{ @@ -41,9 +38,10 @@ use crate::{ }, types::{ ActionAvailability, BlockDevTier, Clevis, Compare, CreateAction, DeleteAction, DevUuid, - Diff, FilesystemUuid, GrowAction, Key, KeyDescription, Name, PoolDiff, - PoolEncryptionInfo, PoolUuid, RegenAction, RenameAction, SetCreateAction, - SetDeleteAction, StratFilesystemDiff, StratPoolDiff, StratSigblockVersion, + Diff, EncryptedDevice, EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, + Name, PoolDiff, PoolEncryptionInfo, PoolUuid, RegenAction, RenameAction, + SetCreateAction, SetDeleteAction, StratFilesystemDiff, StratPoolDiff, + StratSigblockVersion, }, PropChangeAction, }, @@ -1279,6 +1277,16 @@ impl Pool for StratPool { } } + fn encrypt_pool( + &mut self, + _: PoolUuid, + _: &EncryptionInfo, + ) -> StratisResult> { + Err(StratisError::Msg( + "Encrypting an unencrypted device is only supported in V2 of the metadata".to_string(), + )) + } + fn current_metadata(&self, pool_name: &Name) -> StratisResult { serde_json::to_string(&self.record(pool_name)).map_err(|e| e.into()) } diff --git a/src/engine/strat_engine/pool/v2.rs b/src/engine/strat_engine/pool/v2.rs index e82c6cfbca..c003c904fa 100644 --- a/src/engine/strat_engine/pool/v2.rs +++ b/src/engine/strat_engine/pool/v2.rs @@ -32,10 +32,10 @@ use crate::{ }, types::{ ActionAvailability, BlockDevTier, Clevis, Compare, CreateAction, DeleteAction, DevUuid, - Diff, EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, Name, PoolDiff, - PoolEncryptionInfo, PoolUuid, PropChangeAction, RegenAction, RenameAction, - SetCreateAction, SetDeleteAction, SizedKeyMemory, StratFilesystemDiff, StratPoolDiff, - StratSigblockVersion, UnlockMethod, + Diff, EncryptedDevice, EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, + Name, PoolDiff, PoolEncryptionInfo, PoolUuid, PropChangeAction, RegenAction, + RenameAction, SetCreateAction, SetDeleteAction, SizedKeyMemory, StratFilesystemDiff, + StratPoolDiff, StratSigblockVersion, UnlockMethod, }, }, stratis::{StratisError, StratisResult}, @@ -1186,6 +1186,19 @@ impl Pool for StratPool { } } + #[pool_mutating_action("NoRequests")] + fn encrypt_pool( + &mut self, + pool_uuid: PoolUuid, + enc: &EncryptionInfo, + ) -> StratisResult> { + match self.backstore.encrypt(pool_uuid, &mut self.thin_pool, enc) { + Ok(true) => Ok(CreateAction::Created(EncryptedDevice)), + Ok(false) => Ok(CreateAction::Identity), + Err(e) => Err(e), + } + } + fn current_metadata(&self, pool_name: &Name) -> StratisResult { serde_json::to_string(&self.record(pool_name)).map_err(|e| e.into()) } diff --git a/src/engine/strat_engine/thinpool/filesystem.rs b/src/engine/strat_engine/thinpool/filesystem.rs index ba3748a507..6e0f6f6a73 100644 --- a/src/engine/strat_engine/thinpool/filesystem.rs +++ b/src/engine/strat_engine/thinpool/filesystem.rs @@ -18,6 +18,7 @@ use devicemapper::{ Bytes, DevId, DmDevice, DmName, DmOptions, DmUuid, Sectors, ThinDev, ThinDevId, ThinPoolDev, ThinStatus, }; +use libblkid_rs::BlkidProbe; use nix::{ mount::{mount, umount, MsFlags}, @@ -450,6 +451,13 @@ impl StratFilesystem { self.origin = None; changed } + + /// Get the sector size reported by libblkid for this filesystem. + pub fn block_size(&self) -> StratisResult { + let mut probe = BlkidProbe::new_from_filename(&self.devnode())?; + let top = probe.get_topology()?; + Ok(top.get_logical_sector_size()) + } } impl Filesystem for StratFilesystem { diff --git a/src/engine/strat_engine/thinpool/thinpool.rs b/src/engine/strat_engine/thinpool/thinpool.rs index 1dcfb5e389..e84da20e56 100644 --- a/src/engine/strat_engine/thinpool/thinpool.rs +++ b/src/engine/strat_engine/thinpool/thinpool.rs @@ -850,72 +850,6 @@ impl ThinPool { backstore: PhantomData, }) } - - /// Set the device on all DM devices - pub fn set_device(&mut self, backstore_device: Device) -> StratisResult { - if backstore_device == self.backstore_device { - return Ok(false); - } - - let xform_target_line = - |line: &TargetLine| -> TargetLine { - let new_params = match line.params { - LinearDevTargetParams::Linear(ref params) => LinearDevTargetParams::Linear( - LinearTargetParams::new(backstore_device, params.start_offset), - ), - LinearDevTargetParams::Flakey(ref params) => { - let feature_args = params.feature_args.iter().cloned().collect::>(); - LinearDevTargetParams::Flakey(FlakeyTargetParams::new( - backstore_device, - params.start_offset, - params.up_interval, - params.down_interval, - feature_args, - )) - } - }; - - TargetLine::new(line.start, line.length, new_params) - }; - - let meta_table = self - .thin_pool - .meta_dev() - .table() - .table - .clone() - .iter() - .map(&xform_target_line) - .collect::>(); - - let data_table = self - .thin_pool - .data_dev() - .table() - .table - .clone() - .iter() - .map(&xform_target_line) - .collect::>(); - - let mdv_table = self - .mdv - .device() - .table() - .table - .clone() - .iter() - .map(&xform_target_line) - .collect::>(); - - self.thin_pool.set_meta_table(get_dm(), meta_table)?; - self.thin_pool.set_data_table(get_dm(), data_table)?; - self.mdv.set_table(mdv_table)?; - - self.backstore_device = backstore_device; - - Ok(true) - } } impl ThinPool { @@ -1715,6 +1649,72 @@ where } Ok(changed) } + + /// Set the device on all DM devices + pub fn set_device(&mut self, backstore_device: Device) -> StratisResult { + if backstore_device == self.backstore_device { + return Ok(false); + } + + let xform_target_line = + |line: &TargetLine| -> TargetLine { + let new_params = match line.params { + LinearDevTargetParams::Linear(ref params) => LinearDevTargetParams::Linear( + LinearTargetParams::new(backstore_device, params.start_offset), + ), + LinearDevTargetParams::Flakey(ref params) => { + let feature_args = params.feature_args.iter().cloned().collect::>(); + LinearDevTargetParams::Flakey(FlakeyTargetParams::new( + backstore_device, + params.start_offset, + params.up_interval, + params.down_interval, + feature_args, + )) + } + }; + + TargetLine::new(line.start, line.length, new_params) + }; + + let meta_table = self + .thin_pool + .meta_dev() + .table() + .table + .clone() + .iter() + .map(&xform_target_line) + .collect::>(); + + let data_table = self + .thin_pool + .data_dev() + .table() + .table + .clone() + .iter() + .map(&xform_target_line) + .collect::>(); + + let mdv_table = self + .mdv + .device() + .table() + .table + .clone() + .iter() + .map(&xform_target_line) + .collect::>(); + + self.thin_pool.set_meta_table(get_dm(), meta_table)?; + self.thin_pool.set_data_table(get_dm(), data_table)?; + self.mdv.set_table(mdv_table)?; + + self.backstore_device = backstore_device; + + Ok(true) + } } impl<'a, B> Into for &'a ThinPool { diff --git a/src/engine/types/actions.rs b/src/engine/types/actions.rs index ca8088404a..4a9c5b1029 100644 --- a/src/engine/types/actions.rs +++ b/src/engine/types/actions.rs @@ -23,6 +23,9 @@ pub struct Key; /// Return value indicating clevis operation pub struct Clevis; +/// Return value indicating an encrypt operation on the pool +pub struct EncryptedDevice; + /// A trait for a generic kind of action. Defines the type of the thing to /// be changed, and also a method to indicate what changed. pub trait EngineAction { @@ -137,6 +140,19 @@ where } } +impl Display for CreateAction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CreateAction::Created(_) => { + write!(f, "Unencrypted pool successfully encrypted") + } + CreateAction::Identity => { + write!(f, "The requested pool was already encrypted") + } + } + } +} + /// Idempotent type representing a create action for a mapping from a key to a value #[derive(Debug, PartialEq, Eq)] pub enum MappingCreateAction { diff --git a/src/engine/types/mod.rs b/src/engine/types/mod.rs index ba108a21af..fbea9c2d3d 100644 --- a/src/engine/types/mod.rs +++ b/src/engine/types/mod.rs @@ -23,9 +23,9 @@ pub use crate::engine::{ structures::Lockable, types::{ actions::{ - Clevis, CreateAction, DeleteAction, EngineAction, GrowAction, Key, MappingCreateAction, - MappingDeleteAction, PropChangeAction, RegenAction, RenameAction, SetCreateAction, - SetDeleteAction, SetUnlockAction, StartAction, StopAction, ToDisplay, + Clevis, CreateAction, DeleteAction, EncryptedDevice, EngineAction, GrowAction, Key, + MappingCreateAction, MappingDeleteAction, PropChangeAction, RegenAction, RenameAction, + SetCreateAction, SetDeleteAction, SetUnlockAction, StartAction, StopAction, ToDisplay, }, diff::{ Compare, Diff, PoolDiff, StratBlockDevDiff, StratFilesystemDiff, StratPoolDiff,