diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5c43e6a1c8..c523caec6d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -244,7 +244,7 @@ jobs: matrix: include: - rev: HEAD - options: --post-test-check=verify-filesystem-symlinks --post-test-check=verify-pool-metadata --highest-revision-number=7 + options: --post-test-check=verify-filesystem-symlinks --post-test-check=verify-pool-metadata --highest-revision-number=8 - rev: v3.6.2 options: --highest-revision-number=6 runs-on: ubuntu-22.04 diff --git a/Cargo.lock b/Cargo.lock index 978f0b4291..03e63f1ef5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1295,7 +1295,7 @@ dependencies = [ [[package]] name = "stratisd" -version = "3.7.0" +version = "3.8.0" dependencies = [ "assert_cmd", "assert_matches", diff --git a/Cargo.toml b/Cargo.toml index 5210af82c2..0c6fff7e68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stratisd" -version = "3.7.0" +version = "3.8.0" authors.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/src/dbus_api/api/mod.rs b/src/dbus_api/api/mod.rs index 8a92d980f6..7eff21b81e 100644 --- a/src/dbus_api/api/mod.rs +++ b/src/dbus_api/api/mod.rs @@ -150,6 +150,20 @@ pub fn get_base_tree<'a>( .add_p(manager_3_0::version_property(&f)) .add_p(manager_3_7::stopped_pools_property(&f)), ) + .add( + f.interface(consts::MANAGER_INTERFACE_NAME_3_8, ()) + .add_m(manager_3_5::create_pool_method(&f)) + .add_m(manager_3_0::set_key_method(&f)) + .add_m(manager_3_0::unset_key_method(&f)) + .add_m(manager_3_0::list_keys_method(&f)) + .add_m(manager_3_0::destroy_pool_method(&f)) + .add_m(manager_3_0::engine_state_report_method(&f)) + .add_m(manager_3_7::start_pool_method(&f)) + .add_m(manager_3_6::stop_pool_method(&f)) + .add_m(manager_3_2::refresh_state_method(&f)) + .add_p(manager_3_0::version_property(&f)) + .add_p(manager_3_7::stopped_pools_property(&f)), + ) .add( f.interface(consts::REPORT_INTERFACE_NAME_3_0, ()) .add_m(report_3_0::get_report_method(&f)), @@ -181,6 +195,10 @@ pub fn get_base_tree<'a>( .add( f.interface(consts::REPORT_INTERFACE_NAME_3_7, ()) .add_m(report_3_0::get_report_method(&f)), + ) + .add( + f.interface(consts::REPORT_INTERFACE_NAME_3_8, ()) + .add_m(report_3_0::get_report_method(&f)), ); let path = obj_path.get_name().to_owned(); diff --git a/src/dbus_api/blockdev/mod.rs b/src/dbus_api/blockdev/mod.rs index cd5ebadf74..55be64eb33 100644 --- a/src/dbus_api/blockdev/mod.rs +++ b/src/dbus_api/blockdev/mod.rs @@ -138,6 +138,19 @@ pub fn create_dbus_blockdev<'a>( .add_p(blockdev_3_0::physical_path_property(&f)) .add_p(blockdev_3_0::size_property(&f)) .add_p(blockdev_3_3::new_size_property(&f)), + ) + .add( + f.interface(consts::BLOCKDEV_INTERFACE_NAME_3_8, ()) + .add_p(blockdev_3_0::devnode_property(&f)) + .add_p(blockdev_3_0::hardware_info_property(&f)) + .add_p(blockdev_3_0::initialization_time_property(&f)) + .add_p(blockdev_3_0::pool_property(&f)) + .add_p(blockdev_3_0::tier_property(&f)) + .add_p(blockdev_3_3::user_info_property(&f)) + .add_p(blockdev_3_0::uuid_property(&f)) + .add_p(blockdev_3_0::physical_path_property(&f)) + .add_p(blockdev_3_0::size_property(&f)) + .add_p(blockdev_3_3::new_size_property(&f)), ); let path = object_path.get_name().to_owned(); @@ -236,6 +249,18 @@ pub fn get_blockdev_properties( consts::BLOCKDEV_NEW_SIZE_PROP => shared::blockdev_new_size_prop(dev) }, consts::BLOCKDEV_INTERFACE_NAME_3_7 => { + consts::BLOCKDEV_DEVNODE_PROP => shared::blockdev_devnode_prop(dev), + consts::BLOCKDEV_HARDWARE_INFO_PROP => shared::blockdev_hardware_info_prop(dev), + consts::BLOCKDEV_USER_INFO_PROP => shared::blockdev_user_info_prop(dev), + consts::BLOCKDEV_INIT_TIME_PROP => shared::blockdev_init_time_prop(dev), + consts::BLOCKDEV_POOL_PROP => parent.clone(), + consts::BLOCKDEV_UUID_PROP => uuid_to_string!(dev_uuid), + consts::BLOCKDEV_TIER_PROP => shared::blockdev_tier_prop(tier), + consts::BLOCKDEV_PHYSICAL_PATH_PROP => shared::blockdev_physical_path_prop(dev), + consts::BLOCKDEV_TOTAL_SIZE_PROP => shared::blockdev_size_prop(dev), + consts::BLOCKDEV_NEW_SIZE_PROP => shared::blockdev_new_size_prop(dev) + }, + consts::BLOCKDEV_INTERFACE_NAME_3_8 => { consts::BLOCKDEV_DEVNODE_PROP => shared::blockdev_devnode_prop(dev), consts::BLOCKDEV_HARDWARE_INFO_PROP => shared::blockdev_hardware_info_prop(dev), consts::BLOCKDEV_USER_INFO_PROP => shared::blockdev_user_info_prop(dev), diff --git a/src/dbus_api/consts.rs b/src/dbus_api/consts.rs index 16ea2377d5..fc825adffb 100644 --- a/src/dbus_api/consts.rs +++ b/src/dbus_api/consts.rs @@ -15,6 +15,7 @@ pub const MANAGER_INTERFACE_NAME_3_4: &str = "org.storage.stratis3.Manager.r4"; pub const MANAGER_INTERFACE_NAME_3_5: &str = "org.storage.stratis3.Manager.r5"; pub const MANAGER_INTERFACE_NAME_3_6: &str = "org.storage.stratis3.Manager.r6"; pub const MANAGER_INTERFACE_NAME_3_7: &str = "org.storage.stratis3.Manager.r7"; +pub const MANAGER_INTERFACE_NAME_3_8: &str = "org.storage.stratis3.Manager.r8"; pub const REPORT_INTERFACE_NAME_3_0: &str = "org.storage.stratis3.Report.r0"; pub const REPORT_INTERFACE_NAME_3_1: &str = "org.storage.stratis3.Report.r1"; pub const REPORT_INTERFACE_NAME_3_2: &str = "org.storage.stratis3.Report.r2"; @@ -23,6 +24,7 @@ pub const REPORT_INTERFACE_NAME_3_4: &str = "org.storage.stratis3.Report.r4"; pub const REPORT_INTERFACE_NAME_3_5: &str = "org.storage.stratis3.Report.r5"; pub const REPORT_INTERFACE_NAME_3_6: &str = "org.storage.stratis3.Report.r6"; pub const REPORT_INTERFACE_NAME_3_7: &str = "org.storage.stratis3.Report.r7"; +pub const REPORT_INTERFACE_NAME_3_8: &str = "org.storage.stratis3.Report.r8"; pub const LOCKED_POOLS_PROP: &str = "LockedPools"; pub const STOPPED_POOLS_PROP: &str = "StoppedPools"; @@ -35,6 +37,7 @@ pub const POOL_INTERFACE_NAME_3_4: &str = "org.storage.stratis3.pool.r4"; pub const POOL_INTERFACE_NAME_3_5: &str = "org.storage.stratis3.pool.r5"; pub const POOL_INTERFACE_NAME_3_6: &str = "org.storage.stratis3.pool.r6"; pub const POOL_INTERFACE_NAME_3_7: &str = "org.storage.stratis3.pool.r7"; +pub const POOL_INTERFACE_NAME_3_8: &str = "org.storage.stratis3.pool.r8"; pub const POOL_NAME_PROP: &str = "Name"; pub const POOL_UUID_PROP: &str = "Uuid"; pub const POOL_HAS_CACHE_PROP: &str = "HasCache"; @@ -58,6 +61,7 @@ pub const FILESYSTEM_INTERFACE_NAME_3_4: &str = "org.storage.stratis3.filesystem pub const FILESYSTEM_INTERFACE_NAME_3_5: &str = "org.storage.stratis3.filesystem.r5"; pub const FILESYSTEM_INTERFACE_NAME_3_6: &str = "org.storage.stratis3.filesystem.r6"; pub const FILESYSTEM_INTERFACE_NAME_3_7: &str = "org.storage.stratis3.filesystem.r7"; +pub const FILESYSTEM_INTERFACE_NAME_3_8: &str = "org.storage.stratis3.filesystem.r8"; pub const FILESYSTEM_NAME_PROP: &str = "Name"; pub const FILESYSTEM_UUID_PROP: &str = "Uuid"; pub const FILESYSTEM_USED_PROP: &str = "Used"; @@ -76,6 +80,7 @@ pub const BLOCKDEV_INTERFACE_NAME_3_4: &str = "org.storage.stratis3.blockdev.r4" pub const BLOCKDEV_INTERFACE_NAME_3_5: &str = "org.storage.stratis3.blockdev.r5"; pub const BLOCKDEV_INTERFACE_NAME_3_6: &str = "org.storage.stratis3.blockdev.r6"; pub const BLOCKDEV_INTERFACE_NAME_3_7: &str = "org.storage.stratis3.blockdev.r7"; +pub const BLOCKDEV_INTERFACE_NAME_3_8: &str = "org.storage.stratis3.blockdev.r8"; pub const BLOCKDEV_DEVNODE_PROP: &str = "Devnode"; pub const BLOCKDEV_HARDWARE_INFO_PROP: &str = "HardwareInfo"; pub const BLOCKDEV_USER_INFO_PROP: &str = "UserInfo"; @@ -98,6 +103,7 @@ pub fn standard_pool_interfaces() -> Vec { POOL_INTERFACE_NAME_3_5, POOL_INTERFACE_NAME_3_6, POOL_INTERFACE_NAME_3_7, + POOL_INTERFACE_NAME_3_8, ] .iter() .map(|s| (*s).to_string()) @@ -115,6 +121,7 @@ pub fn standard_filesystem_interfaces() -> Vec { FILESYSTEM_INTERFACE_NAME_3_5, FILESYSTEM_INTERFACE_NAME_3_6, FILESYSTEM_INTERFACE_NAME_3_7, + FILESYSTEM_INTERFACE_NAME_3_8, ] .iter() .map(|s| (*s).to_string()) @@ -132,6 +139,7 @@ pub fn standard_blockdev_interfaces() -> Vec { BLOCKDEV_INTERFACE_NAME_3_5, BLOCKDEV_INTERFACE_NAME_3_6, BLOCKDEV_INTERFACE_NAME_3_7, + BLOCKDEV_INTERFACE_NAME_3_8, ] .iter() .map(|s| (*s).to_string()) diff --git a/src/dbus_api/filesystem/mod.rs b/src/dbus_api/filesystem/mod.rs index 095a436cea..5c2d268ee4 100644 --- a/src/dbus_api/filesystem/mod.rs +++ b/src/dbus_api/filesystem/mod.rs @@ -127,6 +127,19 @@ pub fn create_dbus_filesystem<'a>( .add_p(filesystem_3_0::used_property(&f)) .add_p(filesystem_3_6::size_limit_property(&f)) .add_p(filesystem_3_7::origin_property(&f)), + ) + .add( + f.interface(consts::FILESYSTEM_INTERFACE_NAME_3_8, ()) + .add_m(filesystem_3_0::rename_method(&f)) + .add_p(filesystem_3_0::devnode_property(&f)) + .add_p(filesystem_3_0::name_property(&f)) + .add_p(filesystem_3_0::pool_property(&f)) + .add_p(filesystem_3_0::uuid_property(&f)) + .add_p(filesystem_3_0::created_property(&f)) + .add_p(filesystem_3_0::size_property(&f)) + .add_p(filesystem_3_0::used_property(&f)) + .add_p(filesystem_3_6::size_limit_property(&f)) + .add_p(filesystem_3_7::origin_property(&f)), ); let path = object_path.get_name().to_owned(); @@ -209,6 +222,17 @@ pub fn get_fs_properties( consts::FILESYSTEM_SIZE_LIMIT_PROP => shared::fs_size_limit_prop(fs) }, consts::FILESYSTEM_INTERFACE_NAME_3_7 => { + consts::FILESYSTEM_NAME_PROP => shared::fs_name_prop(fs_name), + consts::FILESYSTEM_UUID_PROP => uuid_to_string!(fs_uuid), + consts::FILESYSTEM_DEVNODE_PROP => shared::fs_devnode_prop(fs, pool_name, fs_name), + consts::FILESYSTEM_POOL_PROP => parent.clone(), + consts::FILESYSTEM_CREATED_PROP => shared::fs_created_prop(fs), + consts::FILESYSTEM_SIZE_PROP => shared::fs_size_prop(fs), + consts::FILESYSTEM_USED_PROP => shared::fs_used_prop(fs), + consts::FILESYSTEM_SIZE_LIMIT_PROP => shared::fs_size_limit_prop(fs), + consts::FILESYSTEM_ORIGIN_PROP => shared::fs_origin_prop(fs) + }, + consts::FILESYSTEM_INTERFACE_NAME_3_8 => { consts::FILESYSTEM_NAME_PROP => shared::fs_name_prop(fs_name), consts::FILESYSTEM_UUID_PROP => uuid_to_string!(fs_uuid), consts::FILESYSTEM_DEVNODE_PROP => shared::fs_devnode_prop(fs, pool_name, fs_name), diff --git a/src/dbus_api/pool/mod.rs b/src/dbus_api/pool/mod.rs index 1667b58f63..bc2e55b08c 100644 --- a/src/dbus_api/pool/mod.rs +++ b/src/dbus_api/pool/mod.rs @@ -19,6 +19,7 @@ mod pool_3_3; mod pool_3_5; mod pool_3_6; mod pool_3_7; +mod pool_3_8; pub mod prop_conv; mod shared; @@ -275,6 +276,40 @@ pub fn create_dbus_pool<'a>( .add_p(pool_3_1::enable_overprov_property(&f)) .add_p(pool_3_1::no_alloc_space_property(&f)) .add_p(pool_3_7::metadata_version_property(&f)), + ) + .add( + f.interface(consts::POOL_INTERFACE_NAME_3_8, ()) + .add_m(pool_3_6::create_filesystems_method(&f)) + .add_m(pool_3_7::destroy_filesystems_method(&f)) + .add_m(pool_3_0::snapshot_filesystem_method(&f)) + .add_m(pool_3_0::add_blockdevs_method(&f)) + .add_m(pool_3_0::bind_clevis_method(&f)) + .add_m(pool_3_0::unbind_clevis_method(&f)) + .add_m(pool_3_5::init_cache_method(&f)) + .add_m(pool_3_0::add_cachedevs_method(&f)) + .add_m(pool_3_0::bind_keyring_method(&f)) + .add_m(pool_3_0::unbind_keyring_method(&f)) + .add_m(pool_3_0::rebind_keyring_method(&f)) + .add_m(pool_3_0::rebind_clevis_method(&f)) + .add_m(pool_3_0::rename_method(&f)) + .add_m(pool_3_3::grow_physical_device_method(&f)) + .add_m(pool_3_7::get_metadata_method(&f)) + .add_m(pool_3_8::encrypt_pool_method(&f)) + .add_m(pool_3_8::reencrypt_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)) + .add_p(pool_3_0::avail_actions_property(&f)) + .add_p(pool_3_0::key_desc_property(&f)) + .add_p(pool_3_0::clevis_info_property(&f)) + .add_p(pool_3_0::has_cache_property(&f)) + .add_p(pool_3_0::alloc_size_property(&f)) + .add_p(pool_3_0::used_size_property(&f)) + .add_p(pool_3_0::total_size_property(&f)) + .add_p(pool_3_1::fs_limit_property(&f)) + .add_p(pool_3_1::enable_overprov_property(&f)) + .add_p(pool_3_1::no_alloc_space_property(&f)) + .add_p(pool_3_7::metadata_version_property(&f)), ); let path = object_path.get_name().to_owned(); @@ -407,6 +442,22 @@ pub fn get_pool_properties( consts::POOL_OVERPROV_PROP => shared::pool_overprov_enabled(pool), consts::POOL_NO_ALLOCABLE_SPACE_PROP => shared::pool_no_alloc_space(pool), consts::POOL_METADATA_VERSION_PROP => shared::pool_metadata_version(pool) + }, + consts::POOL_INTERFACE_NAME_3_8 => { + consts::POOL_NAME_PROP => shared::pool_name_prop(pool_name), + consts::POOL_UUID_PROP => uuid_to_string!(pool_uuid), + consts::POOL_ENCRYPTED_PROP => shared::pool_enc_prop(pool), + consts::POOL_AVAIL_ACTIONS_PROP => shared::pool_avail_actions_prop(pool), + consts::POOL_KEY_DESC_PROP => shared::pool_key_desc_prop(pool), + consts::POOL_CLEVIS_INFO_PROP => shared::pool_clevis_info_prop(pool), + consts::POOL_HAS_CACHE_PROP => shared::pool_has_cache_prop(pool), + consts::POOL_ALLOC_SIZE_PROP => shared::pool_allocated_size(pool), + consts::POOL_TOTAL_USED_PROP => shared::pool_used_size(pool), + consts::POOL_TOTAL_SIZE_PROP => shared::pool_total_size(pool), + consts::POOL_FS_LIMIT_PROP => shared::pool_fs_limit(pool), + consts::POOL_OVERPROV_PROP => shared::pool_overprov_enabled(pool), + consts::POOL_NO_ALLOCABLE_SPACE_PROP => shared::pool_no_alloc_space(pool), + consts::POOL_METADATA_VERSION_PROP => shared::pool_metadata_version(pool) } } } 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 new file mode 100644 index 0000000000..f924a21183 --- /dev/null +++ b/src/dbus_api/pool/pool_3_8/api.rs @@ -0,0 +1,45 @@ +// 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 dbus_tree::{Factory, MTSync, Method}; + +use crate::dbus_api::{ + pool::pool_3_8::methods::{encrypt_pool, reencrypt_pool}, + types::TData, +}; + +pub fn encrypt_pool_method(f: &Factory, TData>) -> Method, TData> { + f.method("EncryptPool", (), encrypt_pool) + // Optional key description of key in the kernel keyring + // b: true if the pool should be encrypted and able to be + // unlocked with a passphrase associated with this key description. + // s: key description + // + // Rust representation: (bool, String) + .in_arg(("key_desc", "(bs)")) + // Optional Clevis information for binding on initialization. + // b: true if the pool should be encrypted and able to be unlocked + // using Clevis. + // s: pin name + // s: JSON config for Clevis use + // + // Rust representation: (bool, (String, String)) + .in_arg(("clevis_info", "(b(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")) +} + +pub fn reencrypt_pool_method(f: &Factory, TData>) -> Method, TData> { + f.method("ReencryptPool", (), reencrypt_pool) + // b: true if successful + // + // 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 new file mode 100644 index 0000000000..d4047c977c --- /dev/null +++ b/src/dbus_api/pool/pool_3_8/methods.rs @@ -0,0 +1,132 @@ +// 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::iter::once; + +use dbus::Message; +use dbus_tree::{MTSync, MethodInfo, MethodResult}; + +use crate::{ + dbus_api::{ + types::{DbusErrorEnum, TData, OK_STRING}, + util::{engine_to_dbus_err_tuple, get_next_arg, tuple_to_option}, + }, + engine::{CreateAction, EncryptionInfo, KeyDescription, PoolEncryptionInfo}, + stratis::StratisError, +}; + +pub fn encrypt_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { + let message: &Message = m.msg; + let mut iter = message.iter_init(); + + let key_desc_tuple: (bool, String) = get_next_arg(&mut iter, 0)?; + let clevis_tuple: (bool, (String, String)) = get_next_arg(&mut iter, 0)?; + + 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_desc = match tuple_to_option(key_desc_tuple) { + Some(kds) => match KeyDescription::try_from(kds) { + Ok(kd) => Some(kd), + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&e); + return Ok(vec![return_message.append3(default_return, rc, rs)]); + } + }, + None => None, + }; + let clevis_info = match tuple_to_option(clevis_tuple) { + Some((pin, json_string)) => match serde_json::from_str(json_string.as_str()) { + Ok(j) => Some((pin, j)), + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&StratisError::Serde(e)); + return Ok(vec![return_message.append3(default_return, rc, rs)]); + } + }, + None => None, + }; + let encryption_info = match EncryptionInfo::from_options((key_desc, clevis_info)) { + Some(enc) => enc, + None => { + let (rc, rs) = ( + DbusErrorEnum::ERROR as u16, + "Either a key description or Clevis info is required for this method".to_string(), + ); + 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, &encryption_info), + dbus_context, + pool_path.get_name() + ); + let msg = match result { + Ok(CreateAction::Created(_)) => { + let pool_enc_info = PoolEncryptionInfo::from(once(&encryption_info)); + dbus_context + .push_pool_key_desc_change(pool_path.get_name(), Some(pool_enc_info.clone())); + dbus_context.push_pool_clevis_info_change(pool_path.get_name(), Some(pool_enc_info)); + 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]) +} + +pub fn reencrypt_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { + let message: &Message = m.msg; + + 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 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 (_, _, pool) = guard.as_mut_tuple(); + + let result = handle_action!(pool.reencrypt_pool(), dbus_context, pool_path.get_name()); + let msg = match result { + Ok(_) => return_message.append3(true, 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 new file mode 100644 index 0000000000..88c1a7af24 --- /dev/null +++ b/src/dbus_api/pool/pool_3_8/mod.rs @@ -0,0 +1,8 @@ +// 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/. + +mod api; +mod methods; + +pub use api::{encrypt_pool_method, reencrypt_pool_method}; diff --git a/src/dbus_api/tree.rs b/src/dbus_api/tree.rs index 1fadb300a6..e0269a63da 100644 --- a/src/dbus_api/tree.rs +++ b/src/dbus_api/tree.rs @@ -194,6 +194,11 @@ impl DbusTreeHandler { vec![consts::FILESYSTEM_DEVNODE_PROP.into()], consts::FILESYSTEM_NAME_PROP.to_string() => Variant(new_name.box_clone()) + }, + consts::FILESYSTEM_INTERFACE_NAME_3_8 => { + vec![consts::FILESYSTEM_DEVNODE_PROP.into()], + consts::FILESYSTEM_NAME_PROP.to_string() => + Variant(new_name.box_clone()) } }, ) @@ -213,6 +218,11 @@ impl DbusTreeHandler { vec![], consts::FILESYSTEM_ORIGIN_PROP.to_string() => box_variant!(origin_prop.clone()) + }, + consts::FILESYSTEM_INTERFACE_NAME_3_8 => { + vec![], + consts::FILESYSTEM_ORIGIN_PROP.to_string() => + box_variant!(origin_prop.clone()) } }, ) @@ -272,6 +282,11 @@ impl DbusTreeHandler { Vec::new(), consts::POOL_NAME_PROP.to_string() => Variant(new_name.box_clone()) + }, + consts::POOL_INTERFACE_NAME_3_8 => { + Vec::new(), + consts::POOL_NAME_PROP.to_string() => + Variant(new_name.box_clone()) } }, ) @@ -319,6 +334,9 @@ impl DbusTreeHandler { }, consts::FILESYSTEM_INTERFACE_NAME_3_7 => { vec![consts::FILESYSTEM_DEVNODE_PROP.into()] + }, + consts::FILESYSTEM_INTERFACE_NAME_3_8 => { + vec![consts::FILESYSTEM_DEVNODE_PROP.into()] } }, ) @@ -377,6 +395,11 @@ impl DbusTreeHandler { box_variant!(avail_prop.clone()) }, consts::POOL_INTERFACE_NAME_3_7 => { + Vec::new(), + consts::POOL_AVAIL_ACTIONS_PROP.to_string() => + box_variant!(avail_prop.clone()) + }, + consts::POOL_INTERFACE_NAME_3_8 => { Vec::new(), consts::POOL_AVAIL_ACTIONS_PROP.to_string() => box_variant!(avail_prop) @@ -432,6 +455,11 @@ impl DbusTreeHandler { box_variant!(kd_prop.clone()) }, consts::POOL_INTERFACE_NAME_3_7 => { + Vec::new(), + consts::POOL_KEY_DESC_PROP.to_string() => + box_variant!(kd_prop.clone()) + }, + consts::POOL_INTERFACE_NAME_3_8 => { Vec::new(), consts::POOL_KEY_DESC_PROP.to_string() => box_variant!(kd_prop) @@ -487,6 +515,11 @@ impl DbusTreeHandler { box_variant!(ci_prop.clone()) }, consts::POOL_INTERFACE_NAME_3_7 => { + Vec::new(), + consts::POOL_CLEVIS_INFO_PROP.to_string() => + box_variant!(ci_prop.clone()) + }, + consts::POOL_INTERFACE_NAME_3_8 => { Vec::new(), consts::POOL_CLEVIS_INFO_PROP.to_string() => box_variant!(ci_prop) @@ -536,6 +569,10 @@ impl DbusTreeHandler { consts::POOL_INTERFACE_NAME_3_7 => { Vec::new(), consts::POOL_HAS_CACHE_PROP.to_string() => box_variant!(b) + }, + consts::POOL_INTERFACE_NAME_3_8 => { + Vec::new(), + consts::POOL_HAS_CACHE_PROP.to_string() => box_variant!(b) } }, ) @@ -604,6 +641,11 @@ impl DbusTreeHandler { Vec::new(), consts::STOPPED_POOLS_PROP.to_string() => box_variant!(stopped_pools_to_prop(&stopped_pools, true)) + }, + consts::MANAGER_INTERFACE_NAME_3_8 => { + Vec::new(), + consts::STOPPED_POOLS_PROP.to_string() => + box_variant!(stopped_pools_to_prop(&stopped_pools, true)) } }, ) @@ -691,6 +733,14 @@ impl DbusTreeHandler { consts::FILESYSTEM_SIZE_PROP.to_string(), fs_size_to_prop, new_size + }, + consts::FILESYSTEM_INTERFACE_NAME_3_8 => { + consts::FILESYSTEM_USED_PROP.to_string(), + fs_used_to_prop, + new_used, + consts::FILESYSTEM_SIZE_PROP.to_string(), + fs_size_to_prop, + new_size } ); } @@ -795,6 +845,17 @@ impl DbusTreeHandler { consts::POOL_NO_ALLOCABLE_SPACE_PROP.to_string(), |x| x, new_no_space + }, + consts::POOL_INTERFACE_NAME_3_8 => { + consts::POOL_TOTAL_USED_PROP.to_string(), + pool_used_to_prop, + new_used, + consts::POOL_ALLOC_SIZE_PROP.to_string(), + pool_alloc_to_prop, + new_alloc, + consts::POOL_NO_ALLOCABLE_SPACE_PROP.to_string(), + |x| x, + new_no_space } ); } @@ -838,6 +899,11 @@ impl DbusTreeHandler { Vec::new(), consts::POOL_FS_LIMIT_PROP.to_string() => box_variant!(new_fs_limit) + }, + consts::POOL_INTERFACE_NAME_3_8 => { + Vec::new(), + consts::POOL_FS_LIMIT_PROP.to_string() => + box_variant!(new_fs_limit) } ), ) { @@ -860,6 +926,11 @@ impl DbusTreeHandler { box_variant!(size_limit.clone()) }, consts::FILESYSTEM_INTERFACE_NAME_3_7 => { + Vec::new(), + consts::FILESYSTEM_SIZE_LIMIT_PROP.to_string() => + box_variant!(size_limit.clone()) + }, + consts::FILESYSTEM_INTERFACE_NAME_3_8 => { Vec::new(), consts::FILESYSTEM_SIZE_LIMIT_PROP.to_string() => box_variant!(size_limit) @@ -900,6 +971,11 @@ impl DbusTreeHandler { box_variant!(user_info_prop.clone()) }, consts::BLOCKDEV_INTERFACE_NAME_3_7 => { + Vec::new(), + consts::BLOCKDEV_USER_INFO_PROP.to_string() => + box_variant!(user_info_prop.clone()) + }, + consts::BLOCKDEV_INTERFACE_NAME_3_8 => { Vec::new(), consts::BLOCKDEV_USER_INFO_PROP.to_string() => box_variant!(user_info_prop) @@ -961,6 +1037,11 @@ impl DbusTreeHandler { box_variant!(total_physical_size_prop.clone()) }, consts::BLOCKDEV_INTERFACE_NAME_3_7 => { + Vec::new(), + consts::BLOCKDEV_TOTAL_SIZE_PROP.to_string() => + box_variant!(total_physical_size_prop.clone()) + }, + consts::BLOCKDEV_INTERFACE_NAME_3_8 => { Vec::new(), consts::BLOCKDEV_TOTAL_SIZE_PROP.to_string() => box_variant!(total_physical_size_prop) @@ -974,6 +1055,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( @@ -1013,6 +1153,11 @@ impl DbusTreeHandler { Vec::new(), consts::POOL_OVERPROV_PROP.to_string() => box_variant!(new_mode) + }, + consts::POOL_INTERFACE_NAME_3_8 => { + Vec::new(), + consts::POOL_OVERPROV_PROP.to_string() => + box_variant!(new_mode) } ), ) { @@ -1144,6 +1289,20 @@ impl DbusTreeHandler { consts::POOL_NO_ALLOCABLE_SPACE_PROP.to_string(), |x| x, new_no_space + }, + consts::POOL_INTERFACE_NAME_3_8 => { + consts::POOL_TOTAL_USED_PROP.to_string(), + pool_used_to_prop, + new_used, + consts::POOL_ALLOC_SIZE_PROP.to_string(), + pool_alloc_to_prop, + new_alloc, + consts::POOL_TOTAL_SIZE_PROP.to_string(), + pool_size_to_prop, + new_size, + consts::POOL_NO_ALLOCABLE_SPACE_PROP.to_string(), + |x| x, + new_no_space } ); } @@ -1184,6 +1343,11 @@ impl DbusTreeHandler { consts::BLOCKDEV_NEW_SIZE_PROP.to_string(), blockdev_new_size_to_prop, new_size + }, + consts::BLOCKDEV_INTERFACE_NAME_3_8 => { + consts::BLOCKDEV_NEW_SIZE_PROP.to_string(), + blockdev_new_size_to_prop, + new_size } ) } @@ -1274,6 +1438,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 af345c152c..20d47ab949 100644 --- a/src/dbus_api/types.rs +++ b/src/dbus_api/types.rs @@ -108,6 +108,7 @@ pub enum DbusAction { BlockdevTotalPhysicalSizeChange(Path<'static>, Sectors), FsOriginChange(Path<'static>), FsSizeLimitChange(Path<'static>, Option), + PoolEncryptionChange(Path<'static>, bool), FsBackgroundChange( FilesystemUuid, SignalChange>, @@ -465,6 +466,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 4f96493cf5..5ed5cd5390 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, Reencrypt, RegenAction, RenameAction, + ReportType, SetCreateAction, SetDeleteAction, SetUnlockAction, StartAction, StopAction, + StoppedPoolsInfo, StratFilesystemDiff, StratSigblockVersion, UdevEngineEvent, + UnlockMethod, }, }, stratis::StratisResult, @@ -342,6 +343,17 @@ 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: &EncryptionInfo, + ) -> StratisResult>; + + /// Reencrypt an encrypted pool. + fn reencrypt_pool(&mut self) -> 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 8676a82819..4d2228d063 100644 --- a/src/engine/sim_engine/pool.rs +++ b/src/engine/sim_engine/pool.rs @@ -24,9 +24,9 @@ use crate::{ structures::Table, types::{ ActionAvailability, BlockDevTier, Clevis, CreateAction, DeleteAction, DevUuid, - EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, Name, PoolDiff, - PoolEncryptionInfo, PoolUuid, RegenAction, RenameAction, SetCreateAction, - SetDeleteAction, StratSigblockVersion, + EncryptedDevice, EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, Name, + PoolDiff, PoolEncryptionInfo, PoolUuid, Reencrypt, RegenAction, RenameAction, + SetCreateAction, SetDeleteAction, StratSigblockVersion, }, PropChangeAction, }, @@ -746,6 +746,20 @@ impl Pool for SimPool { } } + fn encrypt_pool( + &mut self, + _: &Name, + _: PoolUuid, + enc: &EncryptionInfo, + ) -> StratisResult> { + self.encryption_info = Some(enc.clone()); + Ok(CreateAction::Created(EncryptedDevice)) + } + + fn reencrypt_pool(&mut self) -> StratisResult { + Ok(Reencrypt) + } + 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/v1.rs b/src/engine/strat_engine/backstore/backstore/v1.rs index 07066c85c5..a201616eb3 100644 --- a/src/engine/strat_engine/backstore/backstore/v1.rs +++ b/src/engine/strat_engine/backstore/backstore/v1.rs @@ -874,6 +874,26 @@ impl Backstore { } } + /// Reencrypt all encrypted devices in the pool. + /// + /// Returns: + /// * Ok(()) if successful + /// * Err(_) if an operation fails while reencrypting the devices. + pub fn reencrypt(&mut self) -> StratisResult<()> { + if self.encryption_info().is_none() { + return Err(StratisError::Msg( + "Requested pool does not appear to be encrypted".to_string(), + )); + }; + + // Keys are not the same but key description is present + operation_loop( + self.blockdevs_mut().into_iter().map(|(_, _, bd)| bd), + |blockdev| blockdev.reencrypt(), + )?; + Ok(()) + } + /// Regenerate the Clevis bindings with the block devices in this pool using /// the same configuration. /// diff --git a/src/engine/strat_engine/backstore/backstore/v2.rs b/src/engine/strat_engine/backstore/backstore/v2.rs index 5d4d6f99eb..31a20cafc4 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, }, @@ -1108,6 +1109,37 @@ 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); + 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(()) + } + + pub fn reencrypt(&self) -> StratisResult<()> { + match self.enc { + Some(Either::Left(_)) => { + Err(StratisError::Msg("Encrypted pool where the encrypted device has not yet been created cannot be reencrypted".to_string())) + } + Some(Either::Right(ref handle)) => { + handle.reencrypt() + } + None => { + Err(StratisError::Msg("Unencrypted device cannot be reencrypted".to_string())) + } + } + } + /// A summary of block sizes pub fn block_size_summary(&self, tier: BlockDevTier) -> Option { match tier { diff --git a/src/engine/strat_engine/backstore/blockdev/v1.rs b/src/engine/strat_engine/backstore/blockdev/v1.rs index d6faceabd7..3f9953a7d1 100644 --- a/src/engine/strat_engine/backstore/blockdev/v1.rs +++ b/src/engine/strat_engine/backstore/blockdev/v1.rs @@ -327,6 +327,15 @@ impl StratBlockDev { } } + /// Reencrypt an individual block device in a pool. + pub fn reencrypt(&self) -> StratisResult<()> { + let crypt_handle = self + .underlying_device + .crypt_handle() + .expect("Checked that pool is encrypted"); + crypt_handle.reencrypt() + } + #[cfg(test)] pub fn invariant(&self) { assert!(self.total_size() == self.used.size()); diff --git a/src/engine/strat_engine/crypt/handle/v1.rs b/src/engine/strat_engine/crypt/handle/v1.rs index b31a716223..374517b4f8 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,9 +42,10 @@ 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, - key_desc_from_metadata, luks2_token_type_is_valid, read_key, wipe_fallback, + 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, reencrypt_shared, + wipe_fallback, }, }, dm::DEVICEMAPPER_PATH, @@ -956,7 +957,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 \ @@ -1024,30 +1025,17 @@ impl CryptHandle { Ok(()) } + /// Encrypt an unencrypted pool. + pub fn reencrypt(&self) -> StratisResult<()> { + reencrypt_shared(self.luks2_device_path(), self.encryption_info()) + } + /// Rename the pool in the LUKS2 token. pub fn rename_pool_in_metadata(&mut self, pool_name: Name) -> StratisResult<()> { let mut device = self.acquire_crypt_device()?; 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 +1085,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..53430ede5d 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,25 @@ 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, + 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, reencrypt_shared, wipe_fallback, }, }, device::blkdev_size, dm::DEVICEMAPPER_PATH, names::format_crypt_backstore_name, + thinpool::ThinPool, }, types::{ DevicePath, EncryptionInfo, KeyDescription, PoolUuid, SizedKeyMemory, UnlockMethod, @@ -404,10 +412,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 +448,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 +661,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 +729,136 @@ 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, + )?; + + 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")) + } + + /// Encrypt an unencrypted pool. + pub fn reencrypt(&self) -> StratisResult<()> { + reencrypt_shared(self.luks2_device_path(), self.encryption_info()) } /// 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..813a935336 100644 --- a/src/engine/strat_engine/crypt/shared.rs +++ b/src/engine/strat_engine/crypt/shared.rs @@ -20,18 +20,20 @@ use devicemapper::{Bytes, DevId, DmName, DmOptions}; use libcryptsetup_rs::{ c_uint, consts::{ - flags::{CryptActivate, CryptVolumeKey, CryptWipe}, + flags::{CryptActivate, CryptReencrypt, CryptVolumeKey, CryptWipe}, vals::{ - CryptDebugLevel, CryptLogLevel, CryptStatusInfo, CryptWipePattern, EncryptionFormat, + CryptDebugLevel, CryptLogLevel, CryptReencryptDirectionInfo, CryptReencryptModeInfo, + CryptStatusInfo, CryptWipePattern, EncryptionFormat, }, }, - register, set_debug_level, set_log_callback, CryptDevice, CryptInit, LibcryptErr, + get_sector_size, register, set_debug_level, set_log_callback, CryptDevice, CryptInit, + CryptParamsLuks2, CryptParamsReencrypt, LibcryptErr, }; 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 +44,7 @@ use crate::{ keys, }, types::{KeyDescription, SizedKeyMemory, UnlockMethod}, + EncryptionInfo, }, stratis::{StratisError, StratisResult}, }; @@ -800,7 +803,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"), + )) + } + }, + } +} + +pub fn reencrypt_shared(luks2_path: &Path, encryption_info: &EncryptionInfo) -> StratisResult<()> { + let mut device = CryptInit::init(luks2_path)?; + + let (keyslot, key) = get_passphrase(&mut device, encryption_info)?; + let new_keyslot = + device + .keyslot_handle() + .add_by_key(None, None, key.as_ref(), CryptVolumeKey::NO_SEGMENT)?; + + let cipher = device.status_handle().get_cipher()?; + let cipher_mode = device.status_handle().get_cipher_mode()?; + let sector_size = convert_int!(get_sector_size(Some(&mut device)), i32, u32)?; + device.reencrypt_handle().reencrypt_init_by_passphrase( + None, + key.as_ref(), + Some(keyslot), + new_keyslot, + (&cipher, &cipher_mode), + CryptParamsReencrypt { + mode: CryptReencryptModeInfo::Reencrypt, + 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, + }, + )?; + + device.reencrypt_handle().reencrypt2::<()>(None, None)?; + + Ok(()) +} diff --git a/src/engine/strat_engine/pool/dispatch.rs b/src/engine/strat_engine/pool/dispatch.rs index a99db97e4b..5135533ef7 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, Reencrypt, RegenAction, + RenameAction, SetCreateAction, SetDeleteAction, StratSigblockVersion, }, }, stratis::StratisResult, @@ -328,6 +328,25 @@ impl Pool for AnyPool { } } + fn encrypt_pool( + &mut self, + name: &Name, + pool_uuid: PoolUuid, + encryption_info: &EncryptionInfo, + ) -> 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 reencrypt_pool(&mut self) -> StratisResult { + match self { + AnyPool::V1(p) => p.reencrypt_pool(), + AnyPool::V2(p) => p.reencrypt_pool(), + } + } + 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 d90e268d2c..142303c6ac 100644 --- a/src/engine/strat_engine/pool/v1.rs +++ b/src/engine/strat_engine/pool/v1.rs @@ -16,13 +16,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::{ @@ -46,9 +43,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, OffsetDirection, PoolDiff, PoolEncryptionInfo, PoolUuid, Reencrypt, RegenAction, + RenameAction, SetCreateAction, SetDeleteAction, StratFilesystemDiff, StratPoolDiff, + StratSigblockVersion, }, PropChangeAction, }, @@ -664,10 +662,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()?; @@ -1284,6 +1286,22 @@ impl Pool for StratPool { } } + fn encrypt_pool( + &mut self, + _: &Name, + _: PoolUuid, + _: &EncryptionInfo, + ) -> StratisResult> { + Err(StratisError::Msg( + "Encrypting an unencrypted device is only supported in V2 of the metadata".to_string(), + )) + } + + fn reencrypt_pool(&mut self) -> StratisResult { + self.backstore.reencrypt()?; + Ok(Reencrypt) + } + 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 2a45991d76..a164466769 100644 --- a/src/engine/strat_engine/pool/v2.rs +++ b/src/engine/strat_engine/pool/v2.rs @@ -27,6 +27,7 @@ use crate::{ blockdev::{v2::StratBlockDev, InternalBlockDev}, ProcessedPathInfos, UnownedDevices, }, + crypt::crypt_metadata_size, liminal::DeviceSet, metadata::{MDADataSize, BDA}, serde_structs::{FlexDevsSave, PoolFeatures, PoolSave, Recordable}, @@ -36,10 +37,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, OffsetDirection, PoolDiff, PoolEncryptionInfo, PoolUuid, PropChangeAction, + Reencrypt, RegenAction, RenameAction, SetCreateAction, SetDeleteAction, SizedKeyMemory, + StratFilesystemDiff, StratPoolDiff, StratSigblockVersion, UnlockMethod, }, }, stratis::{StratisError, StratisResult}, @@ -1190,6 +1191,53 @@ impl Pool for StratPool { } } + #[pool_mutating_action("NoRequests")] + fn encrypt_pool( + &mut self, + name: &Name, + pool_uuid: PoolUuid, + encryption_info: &EncryptionInfo, + ) -> StratisResult> { + match self.backstore.encryption_info() { + Some(enc) => { + if enc != encryption_info { + Err(StratisError::Msg("Encryption information does not match the existing encryption information for encrypted pool".to_string())) + } else { + 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)) + } + } + } + + #[pool_mutating_action("NoRequests")] + fn reencrypt_pool(&mut self) -> StratisResult { + self.thin_pool.suspend()?; + let encrypt_res = self.backstore.reencrypt(); + self.thin_pool.resume()?; + encrypt_res.map(|_| Reencrypt) + } + 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 5c380d6dad..df8ad96414 100644 --- a/src/engine/strat_engine/thinpool/thinpool.rs +++ b/src/engine/strat_engine/thinpool/thinpool.rs @@ -38,7 +38,10 @@ use crate::{ writing::wipe_sectors, }, structures::Table, - types::{Compare, Diff, FilesystemUuid, Name, PoolUuid, StratFilesystemDiff, ThinPoolDiff}, + types::{ + Compare, Diff, FilesystemUuid, Name, OffsetDirection, PoolUuid, StratFilesystemDiff, + ThinPoolDiff, + }, }, stratis::{StratisError, StratisResult}, }; @@ -888,72 +891,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 { @@ -1753,6 +1690,111 @@ where } Ok(changed) } + + /// Set the device on all DM devices + pub fn set_device( + &mut self, + backstore_device: Device, + offset: Sectors, + dir: OffsetDirection, + ) -> 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, + match dir { + 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( + backstore_device, + match dir { + 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) + }; + + 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)?; + + for (start, _) in self.segments.meta_segments.iter_mut() { + match dir { + OffsetDirection::Forwards => *start += offset, + OffsetDirection::Backwards => *start -= offset, + } + } + for (start, _) in self.segments.meta_spare_segments.iter_mut() { + match dir { + OffsetDirection::Forwards => *start += offset, + OffsetDirection::Backwards => *start -= offset, + } + } + for (start, _) in self.segments.data_segments.iter_mut() { + match dir { + OffsetDirection::Forwards => *start += offset, + OffsetDirection::Backwards => *start -= offset, + } + } + for (start, _) in self.segments.mdv_segments.iter_mut() { + match dir { + OffsetDirection::Forwards => *start += offset, + OffsetDirection::Backwards => *start -= offset, + } + } + + self.backstore_device = backstore_device; + + Ok(true) + } } impl<'a, B> Into for &'a ThinPool { @@ -2706,7 +2748,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 ca8088404a..8f625280d2 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 { @@ -822,3 +838,12 @@ impl EngineAction for PropChangeAction { } } } + +/// Return value indicating a successful reencrypt operation on the pool +pub struct Reencrypt; + +impl Display for Reencrypt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Reencryption operation was completed successfully") + } +} diff --git a/src/engine/types/mod.rs b/src/engine/types/mod.rs index ba108a21af..3a28c575d3 100644 --- a/src/engine/types/mod.rs +++ b/src/engine/types/mod.rs @@ -23,9 +23,10 @@ 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, Reencrypt, RegenAction, + RenameAction, SetCreateAction, SetDeleteAction, SetUnlockAction, StartAction, + StopAction, ToDisplay, }, diff::{ Compare, Diff, PoolDiff, StratBlockDevDiff, StratFilesystemDiff, StratPoolDiff, @@ -538,3 +539,8 @@ impl From for u8 { } } } + +pub enum OffsetDirection { + Backwards, + Forwards, +} diff --git a/stratisd.conf b/stratisd.conf index c74414508f..6597138d20 100644 --- a/stratisd.conf +++ b/stratisd.conf @@ -40,6 +40,9 @@ + + @@ -80,6 +83,10 @@ send_interface="org.storage.stratis3.Manager.r7" send_member="EngineStateReport"/> + + @@ -112,6 +119,9 @@ send_interface="org.storage.stratis3.Manager.r7" send_member="ListKeys"/> + diff --git a/tests/client-dbus/src/stratisd_client_dbus/_constants.py b/tests/client-dbus/src/stratisd_client_dbus/_constants.py index 7834e1ac42..246ba8821c 100644 --- a/tests/client-dbus/src/stratisd_client_dbus/_constants.py +++ b/tests/client-dbus/src/stratisd_client_dbus/_constants.py @@ -18,7 +18,7 @@ SERVICE = "org.storage.stratis3" TOP_OBJECT = "/org/storage/stratis3" -REVISION_NUMBER = 7 +REVISION_NUMBER = 8 REVISION = f"r{REVISION_NUMBER}" diff --git a/tests/client-dbus/src/stratisd_client_dbus/_introspect.py b/tests/client-dbus/src/stratisd_client_dbus/_introspect.py index 973c53a2db..d050fca4ad 100644 --- a/tests/client-dbus/src/stratisd_client_dbus/_introspect.py +++ b/tests/client-dbus/src/stratisd_client_dbus/_introspect.py @@ -6,8 +6,8 @@ """, - "org.storage.stratis3.Manager.r7": """ - + "org.storage.stratis3.Manager.r8": """ + @@ -72,8 +72,8 @@ """, - "org.storage.stratis3.Report.r7": """ - + "org.storage.stratis3.Report.r8": """ + @@ -82,8 +82,8 @@ """, - "org.storage.stratis3.blockdev.r7": """ - + "org.storage.stratis3.blockdev.r8": """ + @@ -110,8 +110,8 @@ """, - "org.storage.stratis3.filesystem.r7": """ - + "org.storage.stratis3.filesystem.r8": """ + @@ -137,8 +137,8 @@ """, - "org.storage.stratis3.pool.r7": """ - + "org.storage.stratis3.pool.r8": """ +