diff --git a/src/dbus_api/api/manager_3_8/methods.rs b/src/dbus_api/api/manager_3_8/methods.rs index 512d52068b..d9b1fed254 100644 --- a/src/dbus_api/api/manager_3_8/methods.rs +++ b/src/dbus_api/api/manager_3_8/methods.rs @@ -19,7 +19,7 @@ use crate::{ blockdev::create_dbus_blockdev, filesystem::create_dbus_filesystem, pool::create_dbus_pool, - types::{DbusErrorEnum, TData, OK_STRING}, + types::{DbusErrorEnum, EncryptionInfos, TData, OK_STRING}, util::{engine_to_dbus_err_tuple, get_next_arg, tuple_to_option}, }, engine::{ @@ -29,11 +29,6 @@ use crate::{ stratis::StratisError, }; -type EncryptionInfos<'a> = ( - Vec<((bool, u32), &'a str)>, - Vec<((bool, u32), &'a str, &'a str)>, -); - pub fn start_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { let base_path = m.path.get_name(); let message: &Message = m.msg; diff --git a/src/dbus_api/pool/mod.rs b/src/dbus_api/pool/mod.rs index c414b62dd6..339869e984 100644 --- a/src/dbus_api/pool/mod.rs +++ b/src/dbus_api/pool/mod.rs @@ -294,6 +294,7 @@ pub fn create_dbus_pool<'a>( .add_m(pool_3_3::grow_physical_device_method(&f)) .add_m(pool_3_7::get_metadata_method(&f)) .add_m(pool_3_7::get_fs_metadata_method(&f)) + .add_m(pool_3_8::encrypt_pool_method(&f)) .add_p(pool_3_0::name_property(&f)) .add_p(pool_3_0::uuid_property(&f)) .add_p(pool_3_0::encrypted_property(&f)) 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/dbus_api/pool/pool_3_8/api.rs b/src/dbus_api/pool/pool_3_8/api.rs index a590bf68fd..ec4db87dd8 100644 --- a/src/dbus_api/pool/pool_3_8/api.rs +++ b/src/dbus_api/pool/pool_3_8/api.rs @@ -9,8 +9,8 @@ use crate::dbus_api::{ consts, pool_3_8::{ methods::{ - bind_clevis, bind_keyring, rebind_clevis, rebind_keyring, unbind_clevis, - unbind_keyring, + bind_clevis, bind_keyring, encrypt_pool, rebind_clevis, rebind_keyring, + unbind_clevis, unbind_keyring, }, props::{get_pool_clevis_infos, get_pool_key_descs}, }, @@ -101,3 +101,30 @@ pub fn clevis_infos_property(f: &Factory, TData>) -> Property, TData>) -> Method, TData> { + f.method("EncryptPool", (), encrypt_pool) + // Optional key descriptions of key in the kernel keyring + // a: array of zero or more elements + // b: true if a token slot is specified + // i: token slot + // s: key description + // + // Rust representation: Vec<((bool, u32), String)> + .in_arg(("key_desc", "a((bu)s)")) + // Optional Clevis infos for binding on initialization. + // a: array of zero or more elements + // b: true if a token slot is specified + // i: token slot + // s: pin name + // s: JSON config for Clevis use + // + // Rust representation: Vec<((bool, u32), String, String)> + .in_arg(("clevis_info", "a((bu)ss)")) + // b: true if pool was newly encrypted + // + // Rust representation: bool + .out_arg(("results", "b")) + .out_arg(("return_code", "q")) + .out_arg(("return_string", "s")) +} diff --git a/src/dbus_api/pool/pool_3_8/methods.rs b/src/dbus_api/pool/pool_3_8/methods.rs index f1ac66b536..7f3519d5b6 100644 --- a/src/dbus_api/pool/pool_3_8/methods.rs +++ b/src/dbus_api/pool/pool_3_8/methods.rs @@ -4,14 +4,18 @@ use dbus::Message; use dbus_tree::{MTSync, MethodInfo, MethodResult}; -use serde_json::Value; +use either::Either; +use serde_json::{from_str, Value}; use crate::{ dbus_api::{ - types::{DbusErrorEnum, TData, OK_STRING}, + types::{DbusErrorEnum, EncryptionInfos, TData, OK_STRING}, util::{engine_to_dbus_err_tuple, get_next_arg, tuple_to_option}, }, - engine::{CreateAction, DeleteAction, KeyDescription, OptionalTokenSlotInput, RenameAction}, + engine::{ + CreateAction, DeleteAction, InputEncryptionInfo, KeyDescription, OptionalTokenSlotInput, + RenameAction, + }, stratis::StratisError, }; @@ -397,3 +401,115 @@ pub fn rebind_clevis(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { }; Ok(vec![msg]) } + +pub fn encrypt_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { + let message: &Message = m.msg; + let mut iter = message.iter_init(); + + let (key_desc_array, clevis_array): EncryptionInfos<'_> = + (get_next_arg(&mut iter, 0)?, get_next_arg(&mut iter, 1)?); + + let dbus_context = m.tree.get_data(); + let object_path = m.path.get_name(); + let return_message = message.method_return(); + let default_return = false; + + let key_descs = + match key_desc_array + .into_iter() + .try_fold(Vec::new(), |mut vec, (ts_opt, kd_str)| { + let token_slot = tuple_to_option(ts_opt); + let kd = KeyDescription::try_from(kd_str.to_string())?; + vec.push((token_slot, kd)); + Ok(vec) + }) { + Ok(kds) => kds, + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&e); + return Ok(vec![return_message.append3(default_return, rc, rs)]); + } + }; + + let clevis_infos = + match clevis_array + .into_iter() + .try_fold(Vec::new(), |mut vec, (ts_opt, pin, json_str)| { + let token_slot = tuple_to_option(ts_opt); + let json = from_str(json_str)?; + vec.push((token_slot, (pin.to_owned(), json))); + Ok(vec) + }) { + Ok(cis) => cis, + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&e); + return Ok(vec![return_message.append3(default_return, rc, rs)]); + } + }; + + let ei = match InputEncryptionInfo::new(key_descs, clevis_infos) { + Ok(Some(opt)) => opt, + Ok(None) => { + let (rc, rs) = engine_to_dbus_err_tuple(&StratisError::Msg( + "Need at least one unlock method to encrypt pool".to_string(), + )); + return Ok(vec![return_message.append3(default_return, rc, rs)]); + } + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&e); + return Ok(vec![return_message.append3(default_return, rc, rs)]); + } + }; + + let pool_path = m + .tree + .get(object_path) + .expect("implicit argument must be in tree"); + let pool_uuid = typed_uuid!( + get_data!(pool_path; default_return; return_message).uuid; + Pool; + default_return; + return_message + ); + + let mut guard = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message); + let (name, _, pool) = guard.as_mut_tuple(); + + let result = handle_action!( + pool.encrypt_pool(&name, pool_uuid, &ei), + dbus_context, + pool_path.get_name() + ); + let msg = match result { + Ok(CreateAction::Created(_)) => { + let encryption_info = match pool.encryption_info().clone() { + Some(Either::Left(ei)) => ei, + Some(Either::Right(_)) => { + unreachable!("online reencryption disabled on metadata V1") + } + None => unreachable!("Must have succeeded"), + }; + if encryption_info.all_key_descriptions().count() > 0 { + dbus_context.push_pool_key_desc_change( + pool_path.get_name(), + Some(Either::Left((true, encryption_info.clone()))), + ); + } + if encryption_info.all_clevis_infos().count() > 0 { + dbus_context.push_pool_clevis_info_change( + pool_path.get_name(), + Some(Either::Left((true, encryption_info.clone()))), + ); + } + dbus_context.push_pool_encryption_status_change(pool_path.get_name(), true); + return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) + } + Ok(CreateAction::Identity) => { + return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) + } + Err(err) => { + let (rc, rs) = engine_to_dbus_err_tuple(&err); + return_message.append3(default_return, rc, rs) + } + }; + Ok(vec![msg]) +} diff --git a/src/dbus_api/pool/pool_3_8/mod.rs b/src/dbus_api/pool/pool_3_8/mod.rs index 4ec70ddccb..28f1a2588b 100644 --- a/src/dbus_api/pool/pool_3_8/mod.rs +++ b/src/dbus_api/pool/pool_3_8/mod.rs @@ -7,6 +7,7 @@ mod methods; mod props; pub use api::{ - bind_clevis_method, bind_keyring_method, clevis_infos_property, key_descs_property, - rebind_clevis_method, rebind_keyring_method, unbind_clevis_method, unbind_keyring_method, + bind_clevis_method, bind_keyring_method, clevis_infos_property, encrypt_pool_method, + key_descs_property, rebind_clevis_method, rebind_keyring_method, unbind_clevis_method, + unbind_keyring_method, }; diff --git a/src/dbus_api/tree.rs b/src/dbus_api/tree.rs index c094493c88..a819d3c27b 100644 --- a/src/dbus_api/tree.rs +++ b/src/dbus_api/tree.rs @@ -1117,6 +1117,65 @@ impl DbusTreeHandler { } } + /// Send a signal indicating that the pool encryption status has changed. + fn handle_pool_encryption_change(&self, path: Path<'static>, new_encryption: bool) { + if let Err(e) = self.property_changed_invalidated_signal( + &path, + prop_hashmap!( + consts::POOL_INTERFACE_NAME_3_0 => { + Vec::new(), + consts::POOL_ENCRYPTED_PROP.to_string() => + box_variant!(new_encryption) + }, + consts::POOL_INTERFACE_NAME_3_1 => { + Vec::new(), + consts::POOL_ENCRYPTED_PROP.to_string() => + box_variant!(new_encryption) + }, + consts::POOL_INTERFACE_NAME_3_2 => { + Vec::new(), + consts::POOL_ENCRYPTED_PROP.to_string() => + box_variant!(new_encryption) + }, + consts::POOL_INTERFACE_NAME_3_3 => { + Vec::new(), + consts::POOL_ENCRYPTED_PROP.to_string() => + box_variant!(new_encryption) + }, + consts::POOL_INTERFACE_NAME_3_4 => { + Vec::new(), + consts::POOL_ENCRYPTED_PROP.to_string() => + box_variant!(new_encryption) + }, + consts::POOL_INTERFACE_NAME_3_5 => { + Vec::new(), + consts::POOL_ENCRYPTED_PROP.to_string() => + box_variant!(new_encryption) + }, + consts::POOL_INTERFACE_NAME_3_6 => { + Vec::new(), + consts::POOL_ENCRYPTED_PROP.to_string() => + box_variant!(new_encryption) + }, + consts::POOL_INTERFACE_NAME_3_7 => { + Vec::new(), + consts::POOL_ENCRYPTED_PROP.to_string() => + box_variant!(new_encryption) + }, + consts::POOL_INTERFACE_NAME_3_8 => { + Vec::new(), + consts::POOL_ENCRYPTED_PROP.to_string() => + box_variant!(new_encryption) + } + ), + ) { + warn!( + "Failed to send a signal over D-Bus indicating blockdev total physical size change: {}", + e + ); + } + } + /// Send a signal indicating that the pool overprovisioning mode has changed. fn handle_pool_overprov_mode_change(&self, path: Path<'static>, new_mode: bool) { if let Err(e) = self.property_changed_invalidated_signal( @@ -1445,6 +1504,10 @@ impl DbusTreeHandler { self.handle_blockdev_total_physical_size_change(path, new_total_physical_size); Ok(true) } + DbusAction::PoolEncryptionChange(path, encryption_change) => { + self.handle_pool_encryption_change(path, encryption_change); + Ok(true) + } DbusAction::PoolForegroundChange(item, new_used, new_alloc, new_size, new_no_space) => { self.handle_pool_foreground_change( item, diff --git a/src/dbus_api/types.rs b/src/dbus_api/types.rs index bfa2d66afc..9af9dfa527 100644 --- a/src/dbus_api/types.rs +++ b/src/dbus_api/types.rs @@ -35,6 +35,12 @@ use crate::{ }, }; +/// Type for encryption input for multiple token slots. +pub type EncryptionInfos<'a> = ( + Vec<((bool, u32), &'a str)>, + Vec<((bool, u32), &'a str, &'a str)>, +); + /// Type for lockable D-Bus tree object. pub type LockableTree = Lockable, TData>>>>; @@ -116,6 +122,7 @@ pub enum DbusAction { FsOriginChange(Path<'static>, Option), FsSizeLimitChange(Path<'static>, Option), FsMergeScheduledChange(Path<'static>, bool), + PoolEncryptionChange(Path<'static>, bool), FsBackgroundChange( FilesystemUuid, SignalChange>, @@ -477,6 +484,19 @@ impl DbusContext { } } + /// Send changed signal for changed encryption status of pool. + pub fn push_pool_encryption_status_change(&self, path: &Path<'static>, encrypted: bool) { + if let Err(e) = self + .sender + .send(DbusAction::PoolEncryptionChange(path.clone(), encrypted)) + { + warn!( + "Encryption status change event could not be sent to the processing thread; no signal will be sent out for the encryption status state change: {}", + e, + ) + } + } + /// Send changed signal for changed pool properties when blockdevs are /// added. pub fn push_pool_foreground_change( diff --git a/src/engine/engine.rs b/src/engine/engine.rs index 03d6c815ff..216d452eb3 100644 --- a/src/engine/engine.rs +++ b/src/engine/engine.rs @@ -22,13 +22,13 @@ use crate::{ structures::{AllLockReadGuard, AllLockWriteGuard, SomeLockReadGuard, SomeLockWriteGuard}, types::{ ActionAvailability, BlockDevTier, Clevis, CreateAction, DeleteAction, DevUuid, - EncryptionInfo, FilesystemUuid, GrowAction, InputEncryptionInfo, IntegritySpec, Key, - KeyDescription, LockedPoolsInfo, MappingCreateAction, MappingDeleteAction, Name, - OptionalTokenSlotInput, PoolDiff, PoolEncryptionInfo, PoolIdentifier, PoolUuid, - PropChangeAction, RegenAction, RenameAction, ReportType, SetCreateAction, - SetDeleteAction, SetUnlockAction, StartAction, StopAction, StoppedPoolsInfo, - StratBlockDevDiff, StratFilesystemDiff, StratSigblockVersion, TokenUnlockMethod, - UdevEngineEvent, UnlockMethod, + EncryptedDevice, EncryptionInfo, FilesystemUuid, GrowAction, InputEncryptionInfo, + IntegritySpec, Key, KeyDescription, LockedPoolsInfo, MappingCreateAction, + MappingDeleteAction, Name, OptionalTokenSlotInput, PoolDiff, PoolEncryptionInfo, + PoolIdentifier, PoolUuid, PropChangeAction, RegenAction, RenameAction, ReportType, + SetCreateAction, SetDeleteAction, SetUnlockAction, StartAction, StopAction, + StoppedPoolsInfo, StratBlockDevDiff, StratFilesystemDiff, StratSigblockVersion, + TokenUnlockMethod, UdevEngineEvent, UnlockMethod, }, }, stratis::StratisResult, @@ -388,6 +388,14 @@ pub trait Pool: Debug + Send + Sync { limit: Option, ) -> StratisResult>>; + /// Encrypted an unencrypted pool. + fn encrypt_pool( + &mut self, + name: &Name, + pool_uuid: PoolUuid, + encryption_info: &InputEncryptionInfo, + ) -> 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/engine.rs b/src/engine/sim_engine/engine.rs index 3f7b8a8f06..ec08a823cf 100644 --- a/src/engine/sim_engine/engine.rs +++ b/src/engine/sim_engine/engine.rs @@ -24,17 +24,19 @@ use crate::{ SomeLockWriteGuard, Table, }, types::{ - CreateAction, DeleteAction, DevUuid, EncryptionInfo, Features, FilesystemUuid, - InputEncryptionInfo, IntegritySpec, LockedPoolsInfo, Name, PoolDevice, PoolDiff, - PoolIdentifier, PoolUuid, RenameAction, ReportType, SetUnlockAction, StartAction, - StopAction, StoppedPoolInfo, StoppedPoolsInfo, StratFilesystemDiff, TokenUnlockMethod, - UdevEngineEvent, UnlockMechanism, UnlockMethod, ValidatedIntegritySpec, + CreateAction, DeleteAction, DevUuid, Features, FilesystemUuid, InputEncryptionInfo, + IntegritySpec, LockedPoolsInfo, Name, PoolDevice, PoolDiff, PoolIdentifier, PoolUuid, + RenameAction, ReportType, SetUnlockAction, StartAction, StopAction, StoppedPoolInfo, + StoppedPoolsInfo, StratFilesystemDiff, TokenUnlockMethod, UdevEngineEvent, + UnlockMethod, ValidatedIntegritySpec, }, StratSigblockVersion, }, stratis::{StratisError, StratisResult}, }; +use super::shared::convert_encryption_info; + #[derive(Debug)] pub struct SimEngine { pools: AllOrSomeLock, @@ -139,30 +141,7 @@ impl Engine for SimEngine { let integrity_spec = ValidatedIntegritySpec::try_from(integrity_spec)?; - let converted_ei = encryption_info - .cloned() - .map(|ei| { - ei.into_iter().try_fold( - EncryptionInfo::new(), - |mut info, (token_slot, unlock_mechanism)| { - let ts = match token_slot { - Some(t) => t, - None => info.free_token_slot(), - }; - if let UnlockMechanism::KeyDesc(ref kd) = unlock_mechanism { - if !self.key_handler.contains_key(kd) { - return Err(StratisError::Msg(format!( - "Key {} was not found in the keyring", - kd.as_application_str() - ))); - } - } - info.add_info(ts, unlock_mechanism)?; - Ok(info) - }, - ) - }) - .transpose()?; + let converted_ei = convert_encryption_info(encryption_info, Some(&self.key_handler))?; let guard = self.pools.read(PoolIdentifier::Name(name.clone())).await; match guard.as_ref().map(|g| g.as_tuple()) { diff --git a/src/engine/sim_engine/mod.rs b/src/engine/sim_engine/mod.rs index 93aa975bc1..4b4c973df3 100644 --- a/src/engine/sim_engine/mod.rs +++ b/src/engine/sim_engine/mod.rs @@ -9,3 +9,4 @@ mod engine; mod filesystem; mod keys; mod pool; +mod shared; diff --git a/src/engine/sim_engine/pool.rs b/src/engine/sim_engine/pool.rs index 587f79d370..454ef55c0c 100644 --- a/src/engine/sim_engine/pool.rs +++ b/src/engine/sim_engine/pool.rs @@ -20,16 +20,17 @@ use crate::{ init_cache_idempotent_or_err, validate_filesystem_size, validate_filesystem_size_specs, validate_name, validate_paths, }, - sim_engine::{blockdev::SimDev, filesystem::SimFilesystem}, + sim_engine::{ + blockdev::SimDev, filesystem::SimFilesystem, shared::convert_encryption_info, + }, structures::Table, types::{ ActionAvailability, BlockDevTier, Clevis, CreateAction, DeleteAction, DevUuid, - EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, Name, - OptionalTokenSlotInput, PoolDiff, PoolEncryptionInfo, PoolUuid, RegenAction, - RenameAction, SetCreateAction, SetDeleteAction, StratSigblockVersion, UnlockMechanism, - ValidatedIntegritySpec, + EncryptedDevice, EncryptionInfo, FilesystemUuid, GrowAction, InputEncryptionInfo, Key, + KeyDescription, Name, OptionalTokenSlotInput, PoolDiff, PoolEncryptionInfo, PoolUuid, + PropChangeAction, RegenAction, RenameAction, SetCreateAction, SetDeleteAction, + StratSigblockVersion, UnlockMechanism, ValidatedIntegritySpec, }, - PropChangeAction, }, stratis::{StratisError, StratisResult}, }; @@ -843,6 +844,16 @@ impl Pool for SimPool { } } + fn encrypt_pool( + &mut self, + _: &Name, + _: PoolUuid, + enc: &InputEncryptionInfo, + ) -> StratisResult> { + self.encryption_info = convert_encryption_info(Some(enc), None)?; + 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/sim_engine/shared.rs b/src/engine/sim_engine/shared.rs new file mode 100644 index 0000000000..7dc7edc9dc --- /dev/null +++ b/src/engine/sim_engine/shared.rs @@ -0,0 +1,42 @@ +// 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 crate::{ + engine::types::{EncryptionInfo, InputEncryptionInfo, UnlockMechanism}, + stratis::{StratisError, StratisResult}, +}; + +use super::keys::SimKeyActions; + +pub fn convert_encryption_info( + encryption_info: Option<&InputEncryptionInfo>, + key_handler: Option<&SimKeyActions>, +) -> StratisResult> { + encryption_info + .cloned() + .map(|ei| { + ei.into_iter().try_fold( + EncryptionInfo::new(), + |mut info, (token_slot, unlock_mechanism)| { + let ts = match token_slot { + Some(t) => t, + None => info.free_token_slot(), + }; + if let UnlockMechanism::KeyDesc(ref kd) = unlock_mechanism { + if let Some(kh) = key_handler { + if !kh.contains_key(kd) { + return Err(StratisError::Msg(format!( + "Key {} was not found in the keyring", + kd.as_application_str() + ))); + } + } + } + info.add_info(ts, unlock_mechanism)?; + Ok(info) + }, + ) + }) + .transpose() +} diff --git a/src/engine/strat_engine/backstore/backstore/v2.rs b/src/engine/strat_engine/backstore/backstore/v2.rs index 9510af2092..f25fa9c20c 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, }, @@ -1240,6 +1241,23 @@ impl Backstore { self.data_tier.grow(dev) } + pub fn encrypt( + &mut self, + pool_uuid: PoolUuid, + thinpool: &mut ThinPool, + encryption_info: &InputEncryptionInfo, + ) -> StratisResult<()> { + let (dm_name, _) = format_backstore_ids(pool_uuid, CacheRole::Cache); + 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(()) + } + /// 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/v2.rs b/src/engine/strat_engine/crypt/handle/v2.rs index 5a0c0f0455..cbb178e482 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,7 +35,7 @@ use crate::{ engine::{ engine::MAX_STRATIS_PASS_SIZE, strat_engine::{ - backstore::get_devno_from_path, + backstore::{backstore::v2, get_devno_from_path}, cmd::{clevis_luks_bind, clevis_luks_regen, clevis_luks_unbind}, crypt::{ consts::{ @@ -45,6 +51,7 @@ use crate::{ device::blkdev_size, dm::DEVICEMAPPER_PATH, names::format_crypt_backstore_name, + thinpool::ThinPool, }, types::{ DevicePath, EncryptionInfo, InputEncryptionInfo, KeyDescription, PoolUuid, @@ -330,13 +337,13 @@ impl CryptHandle { }) } - 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: &InputEncryptionInfo, luks2_params: Option<&CryptParamsLuks2>, - ) -> StratisResult<()> { + ) -> StratisResult { let mut luks2_params_ref: Option> = luks2_params.map(|lp| lp.try_into()).transpose()?; @@ -433,6 +440,21 @@ impl CryptHandle { let encryption_info = encryption_info_from_metadata(device)?; + Ok(encryption_info) + } + + /// 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: &InputEncryptionInfo, + luks2_params: Option<&CryptParamsLuks2>, + ) -> StratisResult<()> { + let encryption_info = + Self::initialize_unlock_methods(device, physical_path, encryption_info, luks2_params)?; + let activation_name = format_crypt_backstore_name(&pool_uuid); activate(device, &encryption_info, None, None, &activation_name) } @@ -707,6 +729,133 @@ impl CryptHandle { Ok(()) } + /// Encrypt an unencrypted pool. + pub fn encrypt( + pool_uuid: PoolUuid, + thinpool: &mut ThinPool, + unencrypted_path: &Path, + encryption_info: &InputEncryptionInfo, + ) -> 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 params = CryptParamsLuks2 { + data_alignment: 0, + data_device: None, + integrity: None, + integrity_params: None, + pbkdf: None, + label: None, + sector_size, + subsystem: None, + }; + + let encryption_info = 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, + )?; + + 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, TokenUnlockMethod::Any, None) + .map(|h| h.expect("should have crypt device after online encrypt")) + } + /// Deactivate the device referenced by the current device handle. #[cfg(test)] pub fn deactivate(&self) -> StratisResult<()> { diff --git a/src/engine/strat_engine/crypt/shared.rs b/src/engine/strat_engine/crypt/shared.rs index b19d40c55a..4f4e547816 100644 --- a/src/engine/strat_engine/crypt/shared.rs +++ b/src/engine/strat_engine/crypt/shared.rs @@ -42,8 +42,7 @@ use crate::{ dm::get_dm, keys, }, - types::{KeyDescription, SizedKeyMemory, UnlockMechanism}, - EncryptionInfo, + types::{EncryptionInfo, KeyDescription, SizedKeyMemory, UnlockMechanism}, }, stratis::{StratisError, StratisResult}, }; diff --git a/src/engine/strat_engine/pool/dispatch.rs b/src/engine/strat_engine/pool/dispatch.rs index 861114683c..244a328606 100644 --- a/src/engine/strat_engine/pool/dispatch.rs +++ b/src/engine/strat_engine/pool/dispatch.rs @@ -15,9 +15,10 @@ use crate::{ strat_engine::pool::{v1, v2}, types::{ ActionAvailability, BlockDevTier, Clevis, CreateAction, DeleteAction, DevUuid, - EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, Name, - OptionalTokenSlotInput, PoolDiff, PoolEncryptionInfo, PoolUuid, PropChangeAction, - RegenAction, RenameAction, SetCreateAction, SetDeleteAction, StratSigblockVersion, + EncryptedDevice, EncryptionInfo, FilesystemUuid, GrowAction, InputEncryptionInfo, Key, + KeyDescription, Name, OptionalTokenSlotInput, PoolDiff, PoolEncryptionInfo, PoolUuid, + PropChangeAction, RegenAction, RenameAction, SetCreateAction, SetDeleteAction, + StratSigblockVersion, }, }, stratis::StratisResult, @@ -340,6 +341,18 @@ impl Pool for AnyPool { } } + fn encrypt_pool( + &mut self, + name: &Name, + pool_uuid: PoolUuid, + encryption_info: &InputEncryptionInfo, + ) -> StratisResult> { + match self { + AnyPool::V1(p) => p.encrypt_pool(name, pool_uuid, encryption_info), + AnyPool::V2(p) => p.encrypt_pool(name, 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 a3f86b1ca6..92c23a0114 100644 --- a/src/engine/strat_engine/pool/v1.rs +++ b/src/engine/strat_engine/pool/v1.rs @@ -17,13 +17,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::InputEncryptionInfo, +use crate::engine::strat_engine::{ + backstore::UnownedDevices, + metadata::MDADataSize, + thinpool::{ThinPoolSizeParams, DATA_BLOCK_SIZE}, }; use crate::{ engine::{ @@ -48,11 +45,12 @@ use crate::{ }, types::{ ActionAvailability, BlockDevTier, Clevis, Compare, CreateAction, DeleteAction, DevUuid, - Diff, FilesystemUuid, GrowAction, Key, KeyDescription, Name, OptionalTokenSlotInput, - PoolDiff, PoolEncryptionInfo, PoolUuid, RegenAction, RenameAction, SetCreateAction, - SetDeleteAction, StratFilesystemDiff, StratPoolDiff, StratSigblockVersion, + Diff, EncryptedDevice, EncryptionInfo, FilesystemUuid, GrowAction, InputEncryptionInfo, + Key, KeyDescription, Name, OffsetDirection, OptionalTokenSlotInput, PoolDiff, + PoolEncryptionInfo, PoolUuid, PropChangeAction, RegenAction, RenameAction, + SetCreateAction, SetDeleteAction, StratFilesystemDiff, StratPoolDiff, + StratSigblockVersion, }, - EncryptionInfo, PropChangeAction, }, stratis::{StratisError, StratisResult}, }; @@ -666,10 +664,14 @@ impl Pool for StratPool { ) .and_then(|bdi| { self.thin_pool - .set_device(self.backstore.device().expect( - "Since thin pool exists, space must have been allocated \ + .set_device( + self.backstore.device().expect( + "Since thin pool exists, space must have been allocated \ from the backstore, so backstore must have a cap device", - )) + ), + Sectors(0), + OffsetDirection::Forwards, + ) .and(Ok(bdi)) }); self.thin_pool.resume()?; @@ -1293,6 +1295,17 @@ impl Pool for StratPool { } } + fn encrypt_pool( + &mut self, + _: &Name, + _: PoolUuid, + _: &InputEncryptionInfo, + ) -> 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 f76fa832b2..602a989f6b 100644 --- a/src/engine/strat_engine/pool/v2.rs +++ b/src/engine/strat_engine/pool/v2.rs @@ -28,6 +28,7 @@ use crate::{ blockdev::{v2::StratBlockDev, InternalBlockDev}, ProcessedPathInfos, UnownedDevices, }, + crypt::crypt_metadata_size, liminal::DeviceSet, metadata::{MDADataSize, BDA}, serde_structs::{FlexDevsSave, PoolFeatures, PoolSave, Recordable}, @@ -37,11 +38,11 @@ use crate::{ }, types::{ ActionAvailability, BlockDevTier, Clevis, Compare, CreateAction, DeleteAction, DevUuid, - Diff, EncryptionInfo, FilesystemUuid, GrowAction, InputEncryptionInfo, Key, - KeyDescription, Name, OptionalTokenSlotInput, PoolDiff, PoolEncryptionInfo, PoolUuid, - PropChangeAction, RegenAction, RenameAction, SetCreateAction, SetDeleteAction, - SizedKeyMemory, StratFilesystemDiff, StratPoolDiff, StratSigblockVersion, - TokenUnlockMethod, ValidatedIntegritySpec, + Diff, EncryptedDevice, EncryptionInfo, FilesystemUuid, GrowAction, InputEncryptionInfo, + Key, KeyDescription, Name, OffsetDirection, OptionalTokenSlotInput, PoolDiff, + PoolEncryptionInfo, PoolUuid, PropChangeAction, RegenAction, RenameAction, + SetCreateAction, SetDeleteAction, SizedKeyMemory, StratFilesystemDiff, StratPoolDiff, + StratSigblockVersion, TokenUnlockMethod, ValidatedIntegritySpec, }, }, stratis::{StratisError, StratisResult}, @@ -1165,6 +1166,39 @@ impl Pool for StratPool { } } + #[pool_mutating_action("NoRequests")] + fn encrypt_pool( + &mut self, + name: &Name, + pool_uuid: PoolUuid, + encryption_info: &InputEncryptionInfo, + ) -> StratisResult> { + match self.backstore.encryption_info() { + Some(_) => Ok(CreateAction::Identity), + None => { + self.thin_pool.suspend()?; + let encrypt_res = self + .backstore + .encrypt(pool_uuid, &mut self.thin_pool, encryption_info) + .map(|_| { + self.thin_pool.set_device( + self.backstore.device().expect( + "Since thin pool exists, space must have been allocated \ + from the backstore, so backstore must have a cap device", + ), + crypt_metadata_size().sectors(), + OffsetDirection::Backwards, + ) + }); + self.thin_pool.resume()?; + let metadata_res = self.write_metadata(name); + let _ = encrypt_res?; + metadata_res?; + 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/thinpool/dm_structs.rs b/src/engine/strat_engine/thinpool/dm_structs.rs index f79709e681..c50d1d2135 100644 --- a/src/engine/strat_engine/thinpool/dm_structs.rs +++ b/src/engine/strat_engine/thinpool/dm_structs.rs @@ -80,6 +80,8 @@ pub mod linear_table { LinearTargetParams, Sectors, TargetLine, }; + use crate::engine::types::OffsetDirection; + /// Transform a list of segments belonging to a single device into a /// list of target lines for a linear device. pub fn segs_to_table( @@ -106,33 +108,46 @@ pub mod linear_table { pub fn set_target_device( table: &LinearDevTargetTable, device: Device, + offset: Sectors, + offset_direction: OffsetDirection, ) -> Vec> { - let xform_target_line = - |line: &TargetLine| -> TargetLine { - let new_params = match line.params { - LinearDevTargetParams::Linear(ref params) => LinearDevTargetParams::Linear( - LinearTargetParams::new(device, params.start_offset), - ), - LinearDevTargetParams::Flakey(ref params) => { - let feature_args = params.feature_args.iter().cloned().collect::>(); - LinearDevTargetParams::Flakey(FlakeyTargetParams::new( - device, - params.start_offset, - params.up_interval, - params.down_interval, - feature_args, - )) - } - }; - - TargetLine::new(line.start, line.length, new_params) + let xform_target_line = |line: &TargetLine, + offset, + offset_direction| + -> TargetLine { + let new_params = match line.params { + LinearDevTargetParams::Linear(ref params) => { + LinearDevTargetParams::Linear(LinearTargetParams::new( + device, + match offset_direction { + OffsetDirection::Forwards => params.start_offset + offset, + OffsetDirection::Backwards => params.start_offset - offset, + }, + )) + } + LinearDevTargetParams::Flakey(ref params) => { + let feature_args = params.feature_args.iter().cloned().collect::>(); + LinearDevTargetParams::Flakey(FlakeyTargetParams::new( + device, + match offset_direction { + OffsetDirection::Forwards => params.start_offset + offset, + OffsetDirection::Backwards => params.start_offset - offset, + }, + params.up_interval, + params.down_interval, + feature_args, + )) + } }; + TargetLine::new(line.start, line.length, new_params) + }; + table .table .clone() .iter() - .map(&xform_target_line) + .map(|line| xform_target_line(line, offset, offset_direction)) .collect::>() } } diff --git a/src/engine/strat_engine/thinpool/filesystem.rs b/src/engine/strat_engine/thinpool/filesystem.rs index d92f9673dd..f1ca9371c2 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}, @@ -473,6 +474,13 @@ impl StratFilesystem { pub fn thin_id(&self) -> ThinDevId { self.thin_dev.id() } + + /// 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 b7f2356986..1bb59f00e4 100644 --- a/src/engine/strat_engine/thinpool/thinpool.rs +++ b/src/engine/strat_engine/thinpool/thinpool.rs @@ -45,8 +45,8 @@ use crate::{ }, structures::Table, types::{ - Compare, Diff, FilesystemUuid, Name, PoolUuid, SetDeleteAction, StratFilesystemDiff, - ThinPoolDiff, + Compare, Diff, FilesystemUuid, Name, OffsetDirection, PoolUuid, SetDeleteAction, + StratFilesystemDiff, ThinPoolDiff, }, }, stratis::{StratisError, StratisResult}, @@ -820,28 +820,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 meta_table = - linear_table::set_target_device(self.thin_pool.meta_dev().table(), backstore_device); - let data_table = - linear_table::set_target_device(self.thin_pool.data_dev().table(), backstore_device); - let mdv_table = - linear_table::set_target_device(self.mdv.device().table(), backstore_device); - - 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 { @@ -1921,6 +1899,70 @@ where Ok(SetDeleteAction::new(removed, updated_origins)) } + + /// Set the device on all DM devices + pub fn set_device( + &mut self, + backstore_device: Device, + offset: Sectors, + offset_direction: OffsetDirection, + ) -> StratisResult { + if backstore_device == self.backstore_device { + return Ok(false); + } + + let meta_table = linear_table::set_target_device( + self.thin_pool.meta_dev().table(), + backstore_device, + offset, + offset_direction, + ); + let data_table = linear_table::set_target_device( + self.thin_pool.data_dev().table(), + backstore_device, + offset, + offset_direction, + ); + let mdv_table = linear_table::set_target_device( + self.mdv.device().table(), + backstore_device, + offset, + offset_direction, + ); + + for (start, _) in self.segments.mdv_segments.iter_mut() { + match offset_direction { + OffsetDirection::Forwards => *start += offset, + OffsetDirection::Backwards => *start -= offset, + } + } + for (start, _) in self.segments.data_segments.iter_mut() { + match offset_direction { + OffsetDirection::Forwards => *start += offset, + OffsetDirection::Backwards => *start -= offset, + } + } + for (start, _) in self.segments.meta_segments.iter_mut() { + match offset_direction { + OffsetDirection::Forwards => *start += offset, + OffsetDirection::Backwards => *start -= offset, + } + } + for (start, _) in self.segments.meta_spare_segments.iter_mut() { + match offset_direction { + OffsetDirection::Forwards => *start += offset, + OffsetDirection::Backwards => *start -= offset, + } + } + + 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 Into for &ThinPool { @@ -2871,7 +2913,8 @@ mod tests { .device() .expect("Space already allocated from backstore, backstore must have device"); assert_ne!(old_device, new_device); - pool.set_device(new_device).unwrap(); + pool.set_device(new_device, Sectors(0), OffsetDirection::Forwards) + .unwrap(); pool.resume().unwrap(); let mut buf = [0u8; 10]; diff --git a/src/engine/types/actions.rs b/src/engine/types/actions.rs index 5737d5949d..a4ea0ff733 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 40fc1a8e57..18a7f2d55a 100644 --- a/src/engine/types/mod.rs +++ b/src/engine/types/mod.rs @@ -27,7 +27,7 @@ pub use crate::{ structures::Lockable, types::{ actions::{ - Clevis, CreateAction, DeleteAction, EngineAction, GrowAction, Key, + Clevis, CreateAction, DeleteAction, EncryptedDevice, EngineAction, GrowAction, Key, MappingCreateAction, MappingDeleteAction, PropChangeAction, RegenAction, RenameAction, SetCreateAction, SetDeleteAction, SetUnlockAction, StartAction, StopAction, ToDisplay, @@ -486,6 +486,35 @@ pub enum StratSigblockVersion { V2 = 2, } +impl TryFrom for StratSigblockVersion { + type Error = StratisError; + + fn try_from(value: u8) -> Result { + match value { + 1u8 => Ok(StratSigblockVersion::V1), + 2u8 => Ok(StratSigblockVersion::V2), + _ => Err(StratisError::Msg(format!( + "Unknown sigblock version: {value}" + ))), + } + } +} + +impl From for u8 { + fn from(version: StratSigblockVersion) -> Self { + match version { + StratSigblockVersion::V1 => 1u8, + StratSigblockVersion::V2 => 2u8, + } + } +} + +#[derive(Clone, Copy)] +pub enum OffsetDirection { + Backwards, + Forwards, +} + /// A way to specify an integrity tag size. It is possible for the specification /// to be non-numeric but translatable to some number of bits. #[derive(