From 78b992c5fbe8d0fcc5e233613c50d4030504c143 Mon Sep 17 00:00:00 2001 From: John Baublitz Date: Mon, 30 Sep 2024 14:08:10 -0400 Subject: [PATCH 1/4] Add infrastructure for supporting multiple token slots per unlock method --- Cargo.lock | 4 +- Cargo.toml | 2 +- src/bin/stratis-legacy-pool.rs | 9 +- src/bin/stratis-min/stratis-min.rs | 295 ++++--- src/dbus_api/api/manager_3_0/methods.rs | 9 +- src/dbus_api/api/manager_3_2/methods.rs | 4 +- src/dbus_api/api/manager_3_4/methods.rs | 4 +- src/dbus_api/api/manager_3_5/methods.rs | 4 +- src/dbus_api/api/manager_3_8/methods.rs | 8 +- src/dbus_api/pool/pool_3_0/methods.rs | 38 +- src/dbus_api/pool/shared.rs | 4 +- src/engine/engine.rs | 95 ++- src/engine/mod.rs | 17 +- src/engine/sim_engine/engine.rs | 59 +- src/engine/sim_engine/pool.rs | 303 ++++--- .../strat_engine/backstore/backstore/v1.rs | 37 +- .../strat_engine/backstore/backstore/v2.rs | 414 +++++++--- .../strat_engine/backstore/blockdevmgr.rs | 15 +- src/engine/strat_engine/backstore/devices.rs | 14 +- src/engine/strat_engine/cmd.rs | 12 +- src/engine/strat_engine/crypt/consts.rs | 1 + src/engine/strat_engine/crypt/handle/v1.rs | 363 ++++----- src/engine/strat_engine/crypt/handle/v2.rs | 743 +++++++++--------- src/engine/strat_engine/crypt/mod.rs | 2 +- src/engine/strat_engine/crypt/shared.rs | 220 ++++-- src/engine/strat_engine/engine.rs | 147 ++-- .../strat_engine/liminal/device_info.rs | 43 +- src/engine/strat_engine/liminal/identify.rs | 17 +- src/engine/strat_engine/liminal/liminal.rs | 94 +-- src/engine/strat_engine/liminal/setup.rs | 8 +- src/engine/strat_engine/mod.rs | 3 +- src/engine/strat_engine/pool/dispatch.rs | 63 +- src/engine/strat_engine/pool/v1.rs | 98 ++- src/engine/strat_engine/pool/v2.rs | 97 ++- src/engine/types/keys.rs | 536 +++++++++++-- src/engine/types/mod.rs | 44 +- src/jsonrpc/client/pool.rs | 49 +- src/jsonrpc/interface.rs | 32 +- src/jsonrpc/server/pool.rs | 60 +- src/jsonrpc/server/server.rs | 31 +- tests/stratis_min.rs | 102 +-- 41 files changed, 2500 insertions(+), 1600 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c58ae9ffc..7770259f4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -698,9 +698,9 @@ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libcryptsetup-rs" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38696351545e14557882cd007c8d693c036a1fe5c8e605efb8e397fe2c69b45b" +checksum = "b5083eeb8843b0295aba9a3ddda83b35097bc0f367cb6d9db793a1435e3eee6c" dependencies = [ "bitflags 2.4.0", "either", diff --git a/Cargo.toml b/Cargo.toml index a40901c701..ddcce0eec6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,7 +142,7 @@ version = "0.2.155" optional = true [dependencies.libcryptsetup-rs] -version = "0.11.0" +version = "0.11.2" features = ["mutex"] optional = true diff --git a/src/bin/stratis-legacy-pool.rs b/src/bin/stratis-legacy-pool.rs index fb5dbaeea0..339acc227f 100644 --- a/src/bin/stratis-legacy-pool.rs +++ b/src/bin/stratis-legacy-pool.rs @@ -9,7 +9,7 @@ use serde_json::{json, Map, Value}; use stratisd::{ engine::{ - register_clevis_token, EncryptionInfo, KeyDescription, ProcessedPathInfos, StratPool, + register_clevis_token, InputEncryptionInfo, KeyDescription, ProcessedPathInfos, StratPool, CLEVIS_TANG_TRUST_URL, }, stratis::StratisResult, @@ -120,12 +120,7 @@ fn main() -> StratisResult<()> { )? .unpack() .1; - let encryption_info = match (key_desc, clevis_info) { - (Some(kd), Some(ci)) => Some(EncryptionInfo::Both(kd, ci)), - (Some(kd), _) => Some(EncryptionInfo::KeyDesc(kd)), - (_, Some(ci)) => Some(EncryptionInfo::ClevisInfo(ci)), - (_, _) => None, - }; + let encryption_info = InputEncryptionInfo::new_legacy(key_desc, clevis_info); register_clevis_token()?; StratPool::initialize(name.as_str(), unowned, encryption_info.as_ref())?; Ok(()) diff --git a/src/bin/stratis-min/stratis-min.rs b/src/bin/stratis-min/stratis-min.rs index 7a00f954bd..b02729830b 100644 --- a/src/bin/stratis-min/stratis-min.rs +++ b/src/bin/stratis-min/stratis-min.rs @@ -2,19 +2,18 @@ // 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::{error::Error, path::PathBuf}; +use std::{collections::HashMap, error::Error, path::PathBuf}; -use clap::{builder::PossibleValuesParser, Arg, ArgAction, ArgGroup, Command}; +use clap::{Arg, ArgAction, ArgGroup, Command}; use serde_json::{json, Map, Value}; -use strum::VariantNames; use stratisd::{ engine::{ - EncryptionInfo, KeyDescription, Name, PoolIdentifier, PoolUuid, UnlockMethod, - CLEVIS_TANG_TRUST_URL, + InputEncryptionInfo, KeyDescription, Name, OptionalTokenSlotInput, PoolIdentifier, + PoolUuid, TokenUnlockMethod, CLEVIS_TANG_TRUST_URL, }, jsonrpc::client::{filesystem, key, pool, report}, - stratis::VERSION, + stratis::{StratisError, VERSION}, }; fn parse_args() -> Command { @@ -42,18 +41,8 @@ fn parse_args() -> Command { Command::new("start") .arg(Arg::new("id").required(true)) .arg(Arg::new("name").long("name").num_args(0)) - .arg( - Arg::new("unlock_method") - .long("unlock-method") - .num_args(1) - .value_parser(PossibleValuesParser::new(UnlockMethod::VARIANTS)), - ) - .arg( - Arg::new("prompt") - .long("prompt") - .num_args(0) - .requires("unlock_method"), - ), + .arg(Arg::new("token_slot").long("token_slot").num_args(1)) + .arg(Arg::new("prompt").long("prompt").num_args(0)), Command::new("stop") .arg(Arg::new("id").required(true)) .arg(Arg::new("name").long("name").num_args(0)), @@ -65,28 +54,17 @@ fn parse_args() -> Command { .value_parser(clap::value_parser!(PathBuf)) .required(true), ) - .arg(Arg::new("key_desc").long("key-desc").num_args(1)) .arg( - Arg::new("clevis") - .long("clevis") + Arg::new("key_descs") + .long("key-descs") .num_args(1) - .value_parser(["nbde", "tang", "tpm2"]) - .requires_if("nbde", "tang_args") - .requires_if("tang", "tang_args"), + .action(ArgAction::Append), ) .arg( - Arg::new("tang_url") - .long("tang-url") + Arg::new("clevis_infos") + .long("clevis-infos") .num_args(1) - .required_if_eq("clevis", "nbde") - .required_if_eq("clevis", "tang"), - ) - .arg(Arg::new("thumbprint").long("thumbprint").num_args(1)) - .arg(Arg::new("trust_url").long("trust-url").num_args(0)) - .group( - ArgGroup::new("tang_args") - .arg("thumbprint") - .arg("trust_url"), + .action(ArgAction::Append), ), Command::new("init-cache") .arg(Arg::new("name").required(true)) @@ -128,9 +106,6 @@ fn parse_args() -> Command { Command::new("has-passphrase") .arg(Arg::new("name").long("name").num_args(0)) .arg(Arg::new("id").required(true)), - Command::new("clevis-pin") - .arg(Arg::new("name").long("name").num_args(0)) - .arg(Arg::new("id").required(true)), Command::new("bind") .subcommand_required(true) .subcommands(vec![ @@ -142,12 +117,14 @@ fn parse_args() -> Command { .long("key-desc") .num_args(1) .required(true), - ), + ) + .arg(Arg::new("token_slot").long("token-slot").num_args(1)), Command::new("nbde") .alias("tang") .arg(Arg::new("name").long("name").num_args(0)) .arg(Arg::new("id").required(true)) .arg(Arg::new("tang_url").required(true)) + .arg(Arg::new("token_slot").long("token-slot").num_args(1)) .arg(Arg::new("thumbprint").long("thumbprint").num_args(1)) .arg(Arg::new("trust_url").long("trust-url").num_args(0)) .group( @@ -157,17 +134,30 @@ fn parse_args() -> Command { ), Command::new("tpm2") .arg(Arg::new("name").long("name").num_args(0)) - .arg(Arg::new("id").required(true)), + .arg(Arg::new("id").required(true)) + .arg(Arg::new("token_slot").long("token-slot").num_args(1)), ]), Command::new("unbind") .subcommand_required(true) .subcommands(vec![ Command::new("keyring") .arg(Arg::new("name").long("name").num_args(0)) - .arg(Arg::new("id").required(true)), + .arg(Arg::new("id").required(true)) + .arg( + Arg::new("token_slot") + .long("token-slot") + .num_args(1) + .value_parser(clap::value_parser!(u32)), + ), Command::new("clevis") .arg(Arg::new("name").long("name").num_args(0)) - .arg(Arg::new("id").required(true)), + .arg(Arg::new("id").required(true)) + .arg( + Arg::new("token_slot") + .long("token-slot") + .num_args(1) + .value_parser(clap::value_parser!(u32)), + ), ]), Command::new("rebind") .subcommand_required(true) @@ -180,10 +170,22 @@ fn parse_args() -> Command { .long("key-desc") .num_args(1) .required(true), + ) + .arg( + Arg::new("token_slot") + .long("token-slot") + .num_args(1) + .value_parser(clap::value_parser!(u32)), ), Command::new("clevis") .arg(Arg::new("name").long("name").num_args(0)) - .arg(Arg::new("id").required(true)), + .arg(Arg::new("id").required(true)) + .arg( + Arg::new("token_slot") + .long("token-slot") + .num_args(1) + .value_parser(clap::value_parser!(u32)), + ), ]), ]), Command::new("filesystem").subcommands(vec![ @@ -241,11 +243,13 @@ fn main() -> Result<(), String> { .expect("required"), )?) }; - let unlock_method = args.get_one::("unlock_method").map(|s| { - UnlockMethod::try_from(s.as_str()).expect("parser ensures valid string value") - }); + let token_slot = match args.get_one::("token_slot").map(|s| s.as_str()) { + Some("any") => TokenUnlockMethod::Any, + Some(s) => TokenUnlockMethod::Token(s.parse::()?), + None => TokenUnlockMethod::None, + }; let prompt = args.get_flag("prompt"); - pool::pool_start(id, unlock_method, prompt)?; + pool::pool_start(id, token_slot, prompt)?; Ok(()) } else if let Some(args) = subcommand.subcommand_matches("stop") { let id = if args.get_flag("name") { @@ -262,43 +266,134 @@ fn main() -> Result<(), String> { pool::pool_stop(id)?; Ok(()) } else if let Some(args) = subcommand.subcommand_matches("create") { - let key_description = match args.get_one::("key_desc").map(|s| s.to_owned()) - { - Some(string) => Some(KeyDescription::try_from(string)?), - None => None, + let kds = match args.get_many::("key_descs") { + Some(key_descs) => { + key_descs.into_iter().try_fold(Vec::new(), |mut vec, s| { + let mut split = s.split(":"); + let kd = split.next().ok_or_else(|| { + StratisError::Msg("Key description required".to_string()) + })?; + let token_slot = split + .next() + .map(|i| { + i.parse::() + .map_err(|e| StratisError::Msg(e.to_string())) + }) + .transpose()?; + vec.push((token_slot, KeyDescription::try_from(kd.to_string())?)); + Result::<_, StratisError>::Ok(vec) + })? + } + None => vec![], }; - let pin = args.get_one::("clevis").map(|s| s.as_str()); - let clevis_info = match pin { - Some("nbde" | "tang") => { - let mut json = Map::new(); - json.insert( - "url".to_string(), - Value::from( - args.get_one::("tang_url") - .map(|s| s.as_str()) - .expect("Required"), - ), - ); - if args.get_flag("trust_url") { - json.insert(CLEVIS_TANG_TRUST_URL.to_string(), Value::from(true)); - } else if let Some(thp) = - args.get_one::("thumbprint").map(|s| s.as_str()) - { - json.insert("thp".to_string(), Value::from(thp)); - } - pin.map(|p| (p.to_string(), Value::from(json))) + let cis = match args.get_many::("clevis_infos") { + Some(clevis_infos) => { + clevis_infos + .into_iter() + .try_fold(Vec::new(), |mut vec, s| { + let pairs = s.split_whitespace().try_fold( + HashMap::<&str, Option<&str>>::default(), + |mut hm, pair| { + let mut key_value = pair.split("="); + match (key_value.next(), key_value.next()) { + (None, _) => { + return Err(StratisError::Msg(format!( + "Malformed value pair: {pair}" + ))); + } + (Some(k), None) => hm.insert(k, None), + (Some(k), Some(v)) => hm.insert(k, Some(v)), + }; + Ok(hm) + }, + )?; + let token_slot = if let Some(Some(t)) = pairs.get("token_slot") { + Some( + t.parse::() + .map_err(|e| StratisError::Msg(e.to_string()))?, + ) + } else if let Some(None) = pairs.get("token_slot") { + return Err(StratisError::Msg( + "token_slot requires argument".to_string(), + )); + } else { + None + }; + match pairs.get("pin") { + Some(Some("tang")) => { + let tang_url = if let Some(t) = pairs + .get("tang_url") + .and_then(|t| t.as_ref().map(|s| *s)) + { + t + } else { + return Err(StratisError::Msg( + "tang_url is required".to_string(), + )); + }; + let (thumbprint, trust_url) = + match (pairs.get("thumbprint"), pairs.get("trust_url")) + { + (Some(Some(t)), None) => (Some(*t), false), + (None, Some(None)) => (None, true), + (None, Some(Some(_))) => { + return Err(StratisError::Msg( + "trust_url takes no argument".to_string(), + )); + } + (None, None) => { + return Err(StratisError::Msg( + "Missing required argument trust_url or thumbprint=" + .to_string(), + )); + } + (_, _) => { + return Err(StratisError::Msg( + "thumbprint= cannot be used with trust_url" + .to_string(), + )); + } + }; + let mut json = Map::new(); + json.insert("url".to_string(), Value::from(tang_url)); + if trust_url { + json.insert( + CLEVIS_TANG_TRUST_URL.to_string(), + Value::from(true), + ); + } else if let Some(thp) = thumbprint { + json.insert("thp".to_string(), Value::from(thp)); + } + vec.push(( + token_slot, + ("tang".to_string(), Value::from(json)), + )); + Ok(vec) + } + Some(Some("tpm2")) => { + vec.push((token_slot, ("tpm2".to_string(), json!({})))); + Ok(vec) + } + Some(Some(s)) => { + Err(StratisError::Msg(format!("Invalid pin {s}"))) + } + Some(None) => Err(StratisError::Msg( + "Value required for key pin".to_string(), + )), + None => Err(StratisError::Msg("pin is required".to_string())), + } + })? } - Some("tpm2") => Some(("tpm2".to_string(), json!({}))), - Some(_) => unreachable!("Validated by parser"), - None => None, + None => vec![], }; + let ei = InputEncryptionInfo::new(kds, cis)?; pool::pool_create( args.get_one::("name").expect("required").to_owned(), args.get_many::("blockdevs") .expect("required") .cloned() .collect::>(), - EncryptionInfo::from_options((key_description, clevis_info)), + ei, )?; Ok(()) } else if let Some(args) = subcommand.subcommand_matches("destroy") { @@ -397,20 +492,6 @@ fn main() -> Result<(), String> { }; println!("{}", pool::pool_has_passphrase(id)?); Ok(()) - } else if let Some(args) = subcommand.subcommand_matches("clevis-pin") { - let id = if args.get_flag("name") { - PoolIdentifier::Name(Name::new( - args.get_one::("id").expect("required").to_owned(), - )) - } else { - PoolIdentifier::Uuid(PoolUuid::parse_str( - args.get_one::("id") - .map(|s| s.as_str()) - .expect("required"), - )?) - }; - println!("{}", pool::pool_clevis_pin(id)?); - Ok(()) } else if let Some(subcommand) = subcommand.subcommand_matches("bind") { if let Some(args) = subcommand.subcommand_matches("keyring") { let id = if args.get_flag("name") { @@ -427,7 +508,13 @@ fn main() -> Result<(), String> { let key_desc = KeyDescription::try_from( args.get_one::("key_desc").expect("required"), )?; - pool::pool_bind_keyring(id, key_desc)?; + let token_slot = match args.get_one::("token_slot").map(|s| s.as_str()) + { + Some("legacy") => OptionalTokenSlotInput::Legacy, + Some(s) => OptionalTokenSlotInput::Some(s.parse::()?), + None => OptionalTokenSlotInput::None, + }; + pool::pool_bind_keyring(id, token_slot, key_desc)?; Ok(()) } else if let Some(args) = subcommand.subcommand_matches("nbde") { let id = if args.get_flag("name") { @@ -460,7 +547,13 @@ fn main() -> Result<(), String> { } Value::from(json) }; - pool::pool_bind_clevis(id, "tang".to_string(), clevis_info)?; + let token_slot = match args.get_one::("token_slot").map(|s| s.as_str()) + { + Some("legacy") => OptionalTokenSlotInput::Legacy, + Some(s) => OptionalTokenSlotInput::Some(s.parse::()?), + None => OptionalTokenSlotInput::None, + }; + pool::pool_bind_clevis(id, token_slot, "tang".to_string(), clevis_info)?; Ok(()) } else if let Some(args) = subcommand.subcommand_matches("tpm2") { let id = if args.get_flag("name") { @@ -479,7 +572,13 @@ fn main() -> Result<(), String> { let json = Map::new(); Value::from(json) }; - pool::pool_bind_clevis(id, pin.to_string(), clevis_info)?; + let token_slot = match args.get_one::("token_slot").map(|s| s.as_str()) + { + Some("legacy") => OptionalTokenSlotInput::Legacy, + Some(s) => OptionalTokenSlotInput::Some(s.parse::()?), + None => OptionalTokenSlotInput::None, + }; + pool::pool_bind_clevis(id, token_slot, pin.to_string(), clevis_info)?; Ok(()) } else { unreachable!("Parser requires a subcommand") @@ -497,7 +596,8 @@ fn main() -> Result<(), String> { .expect("required"), )?) }; - pool::pool_unbind_keyring(id)?; + let token_slot = args.get_one::("token_slot").cloned(); + pool::pool_unbind_keyring(id, token_slot)?; Ok(()) } else if let Some(args) = subcommand.subcommand_matches("clevis") { let id = if args.get_flag("name") { @@ -511,7 +611,8 @@ fn main() -> Result<(), String> { .expect("required"), )?) }; - pool::pool_unbind_clevis(id)?; + let token_slot = args.get_one::("token_slot").cloned(); + pool::pool_unbind_clevis(id, token_slot)?; Ok(()) } else { unreachable!("Parser requires a subcommand") @@ -532,7 +633,8 @@ fn main() -> Result<(), String> { let key_desc = KeyDescription::try_from( args.get_one::("key_desc").expect("required"), )?; - pool::pool_rebind_keyring(id, key_desc)?; + let token_slot = args.get_one::("token_slot").cloned(); + pool::pool_rebind_keyring(id, token_slot, key_desc)?; Ok(()) } else if let Some(args) = subcommand.subcommand_matches("clevis") { let id = if args.get_flag("name") { @@ -546,7 +648,8 @@ fn main() -> Result<(), String> { .expect("required"), )?) }; - pool::pool_rebind_clevis(id)?; + let token_slot = args.get_one::("token_slot").cloned(); + pool::pool_rebind_clevis(id, token_slot)?; Ok(()) } else { unreachable!("Parser requires a subcommand") diff --git a/src/dbus_api/api/manager_3_0/methods.rs b/src/dbus_api/api/manager_3_0/methods.rs index 5b6ee29ab0..b6b97e44eb 100644 --- a/src/dbus_api/api/manager_3_0/methods.rs +++ b/src/dbus_api/api/manager_3_0/methods.rs @@ -22,9 +22,9 @@ use crate::{ util::{engine_to_dbus_err_tuple, get_next_arg, tuple_to_option}, }, engine::{ - CreateAction, DeleteAction, EncryptionInfo, EngineAction, IntegritySpec, KeyDescription, - MappingCreateAction, MappingDeleteAction, PoolIdentifier, PoolUuid, SetUnlockAction, - UnlockMethod, + CreateAction, DeleteAction, EngineAction, InputEncryptionInfo, IntegritySpec, + KeyDescription, MappingCreateAction, MappingDeleteAction, PoolIdentifier, PoolUuid, + SetUnlockAction, UnlockMethod, }, stratis::StratisError, }; @@ -322,12 +322,13 @@ pub fn create_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { }, None => None, }; + let encryption_info = InputEncryptionInfo::new_legacy(key_desc, clevis_info); let dbus_context = m.tree.get_data(); let create_result = handle_action!(block_on(dbus_context.engine.create_pool( name, &devs.map(Path::new).collect::>(), - EncryptionInfo::from_options((key_desc, clevis_info)).as_ref(), + encryption_info.as_ref(), IntegritySpec::default(), ))); match create_result { diff --git a/src/dbus_api/api/manager_3_2/methods.rs b/src/dbus_api/api/manager_3_2/methods.rs index b601e331b6..e77f8c1ff9 100644 --- a/src/dbus_api/api/manager_3_2/methods.rs +++ b/src/dbus_api/api/manager_3_2/methods.rs @@ -15,7 +15,7 @@ use crate::{ types::{DbusErrorEnum, TData, OK_STRING}, util::{engine_to_dbus_err_tuple, get_next_arg, tuple_to_option}, }, - engine::{PoolIdentifier, PoolUuid, StartAction, StopAction, UnlockMethod}, + engine::{PoolIdentifier, PoolUuid, StartAction, StopAction, TokenUnlockMethod, UnlockMethod}, stratis::StratisError, }; @@ -63,7 +63,7 @@ pub fn start_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { let ret = match handle_action!(block_on(dbus_context.engine.start_pool( PoolIdentifier::Uuid(pool_uuid), - unlock_method, + TokenUnlockMethod::from(unlock_method), None ))) { Ok(StartAction::Started(_)) => { diff --git a/src/dbus_api/api/manager_3_4/methods.rs b/src/dbus_api/api/manager_3_4/methods.rs index b0da65c317..4f67a58c63 100644 --- a/src/dbus_api/api/manager_3_4/methods.rs +++ b/src/dbus_api/api/manager_3_4/methods.rs @@ -14,7 +14,7 @@ use crate::{ types::{DbusErrorEnum, TData, OK_STRING}, util::{engine_to_dbus_err_tuple, get_next_arg, tuple_to_option}, }, - engine::{Name, PoolIdentifier, PoolUuid, StartAction, UnlockMethod}, + engine::{Name, PoolIdentifier, PoolUuid, StartAction, TokenUnlockMethod, UnlockMethod}, stratis::StratisError, }; @@ -69,7 +69,7 @@ pub fn start_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { let ret = match handle_action!(block_on(dbus_context.engine.start_pool( id.clone(), - unlock_method, + TokenUnlockMethod::from(unlock_method), None ))) { Ok(StartAction::Started(_)) => { diff --git a/src/dbus_api/api/manager_3_5/methods.rs b/src/dbus_api/api/manager_3_5/methods.rs index 573358cb60..03345ac161 100644 --- a/src/dbus_api/api/manager_3_5/methods.rs +++ b/src/dbus_api/api/manager_3_5/methods.rs @@ -15,7 +15,7 @@ use crate::{ types::{DbusErrorEnum, TData, OK_STRING}, util::{engine_to_dbus_err_tuple, get_next_arg, tuple_to_option}, }, - engine::{CreateAction, EncryptionInfo, IntegritySpec, KeyDescription, PoolIdentifier}, + engine::{CreateAction, InputEncryptionInfo, IntegritySpec, KeyDescription, PoolIdentifier}, stratis::StratisError, }; @@ -64,7 +64,7 @@ pub fn create_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { let create_result = handle_action!(block_on(dbus_context.engine.create_pool( name, &devs.map(Path::new).collect::>(), - EncryptionInfo::from_options((key_desc, clevis_info)).as_ref(), + InputEncryptionInfo::new_legacy(key_desc, clevis_info).as_ref(), IntegritySpec::default(), ))); match create_result { diff --git a/src/dbus_api/api/manager_3_8/methods.rs b/src/dbus_api/api/manager_3_8/methods.rs index c91d7eb211..a8c50e245b 100644 --- a/src/dbus_api/api/manager_3_8/methods.rs +++ b/src/dbus_api/api/manager_3_8/methods.rs @@ -23,8 +23,8 @@ use crate::{ util::{engine_to_dbus_err_tuple, get_next_arg, tuple_to_option}, }, engine::{ - CreateAction, EncryptionInfo, IntegritySpec, IntegrityTagSpec, KeyDescription, Name, - PoolIdentifier, PoolUuid, StartAction, UnlockMethod, + CreateAction, InputEncryptionInfo, IntegritySpec, IntegrityTagSpec, KeyDescription, Name, + PoolIdentifier, PoolUuid, StartAction, TokenUnlockMethod, UnlockMethod, }, stratis::StratisError, }; @@ -86,7 +86,7 @@ pub fn start_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { let ret = match handle_action!(block_on(dbus_context.engine.start_pool( id.clone(), - unlock_method, + TokenUnlockMethod::from(unlock_method), fd.map(|f| f.into_fd()), ))) { Ok(StartAction::Started(_)) => { @@ -208,7 +208,7 @@ pub fn create_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { let create_result = handle_action!(block_on(dbus_context.engine.create_pool( name, &devs.map(Path::new).collect::>(), - EncryptionInfo::from_options((key_desc, clevis_info)).as_ref(), + InputEncryptionInfo::new_legacy(key_desc, clevis_info).as_ref(), IntegritySpec { journal_size, tag_spec, diff --git a/src/dbus_api/pool/pool_3_0/methods.rs b/src/dbus_api/pool/pool_3_0/methods.rs index 69fb72eb36..30b7a19df0 100644 --- a/src/dbus_api/pool/pool_3_0/methods.rs +++ b/src/dbus_api/pool/pool_3_0/methods.rs @@ -20,8 +20,8 @@ use crate::{ util::{engine_to_dbus_err_tuple, get_next_arg, tuple_to_option}, }, engine::{ - CreateAction, DeleteAction, EngineAction, FilesystemUuid, KeyDescription, Name, PoolUuid, - RenameAction, + CreateAction, DeleteAction, EngineAction, FilesystemUuid, KeyDescription, Name, + OptionalTokenSlotInput, PoolUuid, RenameAction, }, stratis::StratisError, }; @@ -376,7 +376,7 @@ pub fn bind_clevis(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { } }; let msg = match handle_action!( - pool.bind_clevis(pin.as_str(), &json), + pool.bind_clevis(OptionalTokenSlotInput::Legacy, pin.as_str(), &json), dbus_context, pool_path.get_name() ) { @@ -384,7 +384,8 @@ pub fn bind_clevis(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Ok(CreateAction::Created(_)) => { - dbus_context.push_pool_clevis_info_change(pool_path.get_name(), pool.encryption_info()); + dbus_context + .push_pool_clevis_info_change(pool_path.get_name(), pool.encryption_info_legacy()); return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Err(e) => { @@ -416,12 +417,13 @@ pub fn unbind_clevis(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { let mut pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message); - let msg = match handle_action!(pool.unbind_clevis(), dbus_context, pool_path.get_name()) { + let msg = match handle_action!(pool.unbind_clevis(None), dbus_context, pool_path.get_name()) { Ok(DeleteAction::Identity) => { return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Ok(DeleteAction::Deleted(_)) => { - dbus_context.push_pool_clevis_info_change(pool_path.get_name(), pool.encryption_info()); + dbus_context + .push_pool_clevis_info_change(pool_path.get_name(), pool.encryption_info_legacy()); return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Err(e) => { @@ -464,7 +466,7 @@ pub fn bind_keyring(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { let mut pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message); let msg = match handle_action!( - pool.bind_keyring(&key_desc), + pool.bind_keyring(OptionalTokenSlotInput::Legacy, &key_desc), dbus_context, pool_path.get_name() ) { @@ -472,7 +474,8 @@ pub fn bind_keyring(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Ok(CreateAction::Created(_)) => { - dbus_context.push_pool_key_desc_change(pool_path.get_name(), pool.encryption_info()); + dbus_context + .push_pool_key_desc_change(pool_path.get_name(), pool.encryption_info_legacy()); return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Err(e) => { @@ -504,12 +507,17 @@ pub fn unbind_keyring(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult let mut pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message); - let msg = match handle_action!(pool.unbind_keyring(), dbus_context, pool_path.get_name()) { + let msg = match handle_action!( + pool.unbind_keyring(None), + dbus_context, + pool_path.get_name() + ) { Ok(DeleteAction::Identity) => { return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Ok(DeleteAction::Deleted(_)) => { - dbus_context.push_pool_key_desc_change(pool_path.get_name(), pool.encryption_info()); + dbus_context + .push_pool_key_desc_change(pool_path.get_name(), pool.encryption_info_legacy()); return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Err(e) => { @@ -552,7 +560,7 @@ pub fn rebind_keyring(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult let mut pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message); let msg = match handle_action!( - pool.rebind_keyring(&key_desc), + pool.rebind_keyring(None, &key_desc), dbus_context, pool_path.get_name() ) { @@ -560,7 +568,8 @@ pub fn rebind_keyring(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Ok(RenameAction::Renamed(_)) => { - dbus_context.push_pool_key_desc_change(pool_path.get_name(), pool.encryption_info()); + dbus_context + .push_pool_key_desc_change(pool_path.get_name(), pool.encryption_info_legacy()); return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Ok(RenameAction::NoSource) => { @@ -599,9 +608,10 @@ pub fn rebind_clevis(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { let mut pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message); - let msg = match handle_action!(pool.rebind_clevis(), dbus_context, pool_path.get_name()) { + let msg = match handle_action!(pool.rebind_clevis(None), dbus_context, pool_path.get_name()) { Ok(_) => { - dbus_context.push_pool_clevis_info_change(pool_path.get_name(), pool.encryption_info()); + dbus_context + .push_pool_clevis_info_change(pool_path.get_name(), pool.encryption_info_legacy()); return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Err(e) => { diff --git a/src/dbus_api/pool/shared.rs b/src/dbus_api/pool/shared.rs index 0da78b0867..41100fca0d 100644 --- a/src/dbus_api/pool/shared.rs +++ b/src/dbus_api/pool/shared.rs @@ -283,12 +283,12 @@ pub fn pool_avail_actions_prop(pool: &dyn Pool) -> String { /// Generate D-Bus representation of a pool key description property. pub fn pool_key_desc_prop(pool: &dyn Pool) -> (bool, (bool, String)) { - prop_conv::key_desc_to_prop(pool.encryption_info()) + prop_conv::key_desc_to_prop(pool.encryption_info_legacy()) } /// Generate D-Bus representation of a pool Clevis info property. pub fn pool_clevis_info_prop(pool: &dyn Pool) -> (bool, (bool, (String, String))) { - prop_conv::clevis_info_to_prop(pool.encryption_info()) + prop_conv::clevis_info_to_prop(pool.encryption_info_legacy()) } /// Generate D-Bus representation of a boolean indicating whether the pool diff --git a/src/engine/engine.rs b/src/engine/engine.rs index 1b192fedc0..6d0f543825 100644 --- a/src/engine/engine.rs +++ b/src/engine/engine.rs @@ -12,6 +12,7 @@ use std::{ use async_trait::async_trait; use chrono::{DateTime, Utc}; +use either::Either; use serde_json::Value; use devicemapper::{Bytes, Sectors}; @@ -21,19 +22,18 @@ use crate::{ structures::{AllLockReadGuard, AllLockWriteGuard, SomeLockReadGuard, SomeLockWriteGuard}, types::{ ActionAvailability, BlockDevTier, Clevis, CreateAction, DeleteAction, DevUuid, - EncryptionInfo, FilesystemUuid, GrowAction, IntegritySpec, Key, KeyDescription, - LockedPoolsInfo, MappingCreateAction, MappingDeleteAction, Name, PoolDiff, - PoolEncryptionInfo, PoolIdentifier, PoolUuid, RegenAction, RenameAction, ReportType, - SetCreateAction, SetDeleteAction, SetUnlockAction, StartAction, StopAction, - StoppedPoolsInfo, StratFilesystemDiff, StratSigblockVersion, UdevEngineEvent, - UnlockMethod, + 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, }; -use super::{types::StratBlockDevDiff, PropChangeAction}; - pub const DEV_PATH: &str = "/dev/stratis"; /// The maximum size of pool passphrases stored in the kernel keyring pub const MAX_STRATIS_PASS_SIZE: usize = 512 / 8; @@ -180,30 +180,70 @@ pub trait Pool: Debug + Send + Sync { tier: BlockDevTier, ) -> StratisResult<(SetCreateAction, Option)>; - /// Bind all devices in the given pool for automated unlocking - /// using clevis. + /// V1: Binds all devices in the pool to a given Clevis config. + /// * token_slot is always Legacy + /// + /// V2: Binds crypt device to a given Clevis config. + /// * if token slot is Legacy bind with the assumption that there is only one Clevis token + /// slot allowed. + /// * if token_slot is Some(_): bind to specific token slot + /// * if token_slot is None: bind to any available token_slot fn bind_clevis( &mut self, + token_slot: OptionalTokenSlotInput, pin: &str, clevis_info: &Value, ) -> StratisResult>; - /// Unbind all devices in the given pool from using clevis. - fn unbind_clevis(&mut self) -> StratisResult>; + /// V1: Binds all devices in the pool to a given key description. + /// * token_slot is always None + /// + /// V2: Binds crypt device to a given key description. + /// * if token slot is Legacy bind with the assumption that there is only one keyring token + /// slot allowed. + /// * if token_slot is Some(_): bind to specific token slot + /// * if token_slot is None: bind to any available token slot + fn bind_keyring( + &mut self, + token_slot: OptionalTokenSlotInput, + key_desc: &KeyDescription, + ) -> StratisResult>; - /// Bind all devices in the given pool for unlocking using a passphrase - /// in the kernel keyring. - fn bind_keyring(&mut self, key_desc: &KeyDescription) -> StratisResult>; + /// V1: Rebinds all devices in the pool to a given key description. + /// * token_slot is always None + /// + /// V2: Rebinds crypt device to a given key description. + /// * if token_slot is Some(_): rebind specific token slot + /// * if token_slot is None: rebind first keyring token slot + fn rebind_keyring( + &mut self, + token_slot: Option, + new_key_desc: &KeyDescription, + ) -> StratisResult>; - /// Unbind all devices in the given pool from the registered keyring passphrase. - fn unbind_keyring(&mut self) -> StratisResult>; + /// V1: Rebinds all devices in the pool to a given Clevis config. + /// * token_slot is always None + /// + /// V2: Rebinds crypt device to a given Clevis config. + /// * if token_slot is Some(_): rebind specific token slot + /// * if token_slot is None: rebind first Clevis token slot + fn rebind_clevis(&mut self, token_slot: Option) -> StratisResult; - /// Change the key description and passphrase associated with a pool. - fn rebind_keyring(&mut self, new_key_desc: &KeyDescription) - -> StratisResult>; + /// V1: Unbinds crypt device from the key description token + /// * token_slot is always None + /// + /// V2: Unbinds crypt device from the given key description token specified by token slot. + /// * if token_slot is Some(_): unbind specific token slot + /// * if token_slot is None: unbind first key description token slot + fn unbind_keyring(&mut self, token_slot: Option) -> StratisResult>; - /// Regenerate the Clevis bindings associated with a pool. - fn rebind_clevis(&mut self) -> StratisResult; + /// V1: Unbinds crypt device from the Clevis token + /// * token_slot is always None + /// + /// V2: Unbinds crypt device from the given Clevis token specified by token slot. + /// * if token_slot is Some(_): unbind specific token slot + /// * if token_slot is None: unbind first Clevis token slot + fn unbind_clevis(&mut self, token_slot: Option) -> StratisResult>; /// Ensures that all designated filesystems are gone from pool. /// Returns a list of the filesystems found, and actually destroyed. @@ -300,8 +340,11 @@ pub trait Pool: Debug + Send + Sync { /// Determine if the pool's data is encrypted fn is_encrypted(&self) -> bool; + /// Get all legacy encryption information for this pool. + fn encryption_info_legacy(&self) -> Option; + /// Get all encryption information for this pool. - fn encryption_info(&self) -> Option; + fn encryption_info(&self) -> Option>; /// Get the pool state for the given pool. The state indicates which actions /// will be disabled or enabled. Disabled actions are triggered by failures @@ -381,7 +424,7 @@ pub trait Engine: Debug + Report + Send + Sync { &self, name: &str, blockdev_paths: &[&Path], - encryption_info: Option<&EncryptionInfo>, + encryption_info: Option<&InputEncryptionInfo>, integrity_spec: IntegritySpec, ) -> StratisResult>; @@ -416,7 +459,7 @@ pub trait Engine: Debug + Report + Send + Sync { async fn unlock_pool( &self, uuid: PoolUuid, - unlock_method: UnlockMethod, + token_slot: UnlockMethod, ) -> StratisResult>; /// Find the pool designated by name or UUID. @@ -467,7 +510,7 @@ pub trait Engine: Debug + Report + Send + Sync { async fn start_pool( &self, pool_id: PoolIdentifier, - unlock_method: Option, + token_slot: TokenUnlockMethod, passphrase_fd: Option, ) -> StratisResult>; diff --git a/src/engine/mod.rs b/src/engine/mod.rs index df0a86c2fc..3da84e87ed 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -20,14 +20,15 @@ pub use self::{ structures::{AllLockReadGuard, ExclusiveGuard, SharedGuard, Table}, types::{ ActionAvailability, BlockDevTier, ClevisInfo, CreateAction, DeleteAction, DevUuid, Diff, - EncryptionInfo, EngineAction, FilesystemUuid, GrowAction, IntegritySpec, IntegrityTagSpec, - KeyDescription, Lockable, LockedPoolInfo, LockedPoolsInfo, MappingCreateAction, - MappingDeleteAction, MaybeInconsistent, Name, PoolDiff, PoolEncryptionInfo, PoolIdentifier, - PoolUuid, PropChangeAction, RenameAction, ReportType, SetCreateAction, SetDeleteAction, - SetUnlockAction, StartAction, StopAction, StoppedPoolInfo, StoppedPoolsInfo, - StratBlockDevDiff, StratFilesystemDiff, StratPoolDiff, StratSigblockVersion, StratisUuid, - ThinPoolDiff, ToDisplay, UdevEngineEvent, UnlockMethod, ValidatedIntegritySpec, - DEFAULT_INTEGRITY_JOURNAL_SIZE, DEFAULT_INTEGRITY_TAG_SPEC, + EncryptionInfo, EngineAction, FilesystemUuid, GrowAction, InputEncryptionInfo, + IntegritySpec, IntegrityTagSpec, KeyDescription, Lockable, LockedPoolInfo, LockedPoolsInfo, + MappingCreateAction, MappingDeleteAction, MaybeInconsistent, Name, OptionalTokenSlotInput, + PoolDiff, PoolEncryptionInfo, PoolIdentifier, PoolUuid, PropChangeAction, RenameAction, + ReportType, SetCreateAction, SetDeleteAction, SetUnlockAction, StartAction, StopAction, + StoppedPoolInfo, StoppedPoolsInfo, StratBlockDevDiff, StratFilesystemDiff, StratPoolDiff, + StratSigblockVersion, StratisUuid, ThinPoolDiff, ToDisplay, TokenUnlockMethod, + UdevEngineEvent, UnlockMethod, ValidatedIntegritySpec, DEFAULT_INTEGRITY_JOURNAL_SIZE, + DEFAULT_INTEGRITY_TAG_SPEC, }, }; diff --git a/src/engine/sim_engine/engine.rs b/src/engine/sim_engine/engine.rs index 0aaa178ff8..3f7b8a8f06 100644 --- a/src/engine/sim_engine/engine.rs +++ b/src/engine/sim_engine/engine.rs @@ -25,10 +25,10 @@ use crate::{ }, types::{ CreateAction, DeleteAction, DevUuid, EncryptionInfo, Features, FilesystemUuid, - IntegritySpec, LockedPoolsInfo, Name, PoolDevice, PoolDiff, PoolIdentifier, PoolUuid, - RenameAction, ReportType, SetUnlockAction, StartAction, StopAction, StoppedPoolInfo, - StoppedPoolsInfo, StratFilesystemDiff, UdevEngineEvent, UnlockMethod, - ValidatedIntegritySpec, + InputEncryptionInfo, IntegritySpec, LockedPoolsInfo, Name, PoolDevice, PoolDiff, + PoolIdentifier, PoolUuid, RenameAction, ReportType, SetUnlockAction, StartAction, + StopAction, StoppedPoolInfo, StoppedPoolsInfo, StratFilesystemDiff, TokenUnlockMethod, + UdevEngineEvent, UnlockMechanism, UnlockMethod, ValidatedIntegritySpec, }, StratSigblockVersion, }, @@ -129,7 +129,7 @@ impl Engine for SimEngine { &self, name: &str, blockdev_paths: &[&Path], - encryption_info: Option<&EncryptionInfo>, + encryption_info: Option<&InputEncryptionInfo>, integrity_spec: IntegritySpec, ) -> StratisResult> { validate_name(name)?; @@ -139,14 +139,30 @@ impl Engine for SimEngine { let integrity_spec = ValidatedIntegritySpec::try_from(integrity_spec)?; - if let Some(key_desc) = encryption_info.and_then(|ei| ei.key_description()) { - if !self.key_handler.contains_key(key_desc) { - return Err(StratisError::Msg(format!( - "Key {} was not found in the keyring", - key_desc.as_application_str() - ))); - } - } + 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 guard = self.pools.read(PoolIdentifier::Name(name.clone())).await; match guard.as_ref().map(|g| g.as_tuple()) { @@ -160,7 +176,8 @@ impl Engine for SimEngine { let device_set: HashSet<_, RandomState> = HashSet::from_iter(blockdev_paths); let devices = device_set.into_iter().cloned().collect::>(); - let (pool_uuid, pool) = SimPool::new(&devices, encryption_info, integrity_spec); + let (pool_uuid, pool) = + SimPool::new(&devices, converted_ei.as_ref(), integrity_spec); self.pools.modify_all().await.insert( Name::new(name.to_owned()), @@ -250,7 +267,7 @@ impl Engine for SimEngine { st.stopped.insert( *uuid, StoppedPoolInfo { - info: pool.encryption_info(), + info: pool.encryption_info_legacy(), devices: pool .blockdevs() .into_iter() @@ -300,16 +317,12 @@ impl Engine for SimEngine { async fn start_pool( &self, id: PoolIdentifier, - unlock_method: Option, + token_slot: TokenUnlockMethod, passphrase_fd: Option, ) -> StratisResult> { if let Some(guard) = self.pools.read(id.clone()).await { let (_, pool_uuid, pool) = guard.as_tuple(); - if pool.is_encrypted() && unlock_method.is_none() { - return Err(StratisError::Msg(format!( - "Pool with UUID {pool_uuid} is encrypted but no unlock method was provided" - ))); - } else if !pool.is_encrypted() && unlock_method.is_some() { + if !pool.is_encrypted() && token_slot.is_some() { return Err(StratisError::Msg(format!( "Pool with UUID {pool_uuid} is not encrypted but an unlock method was provided" ))); @@ -345,7 +358,7 @@ impl Engine for SimEngine { }) .map(|(n, p)| (n, u, p))?, }; - if pool.is_encrypted() && unlock_method.is_none() { + if pool.is_encrypted() && token_slot.is_none() { self.stopped_pools .write() .await @@ -353,7 +366,7 @@ impl Engine for SimEngine { return Err(StratisError::Msg(format!( "Pool with UUID {pool_uuid} is encrypted but no unlock method was provided" ))); - } else if !pool.is_encrypted() && unlock_method.is_some() { + } else if !pool.is_encrypted() && token_slot.is_some() { self.stopped_pools .write() .await diff --git a/src/engine/sim_engine/pool.rs b/src/engine/sim_engine/pool.rs index 41ff7860a4..73de8c9dc1 100644 --- a/src/engine/sim_engine/pool.rs +++ b/src/engine/sim_engine/pool.rs @@ -4,11 +4,12 @@ use std::{ collections::{hash_map::RandomState, HashMap, HashSet}, - iter::once, path::Path, vec::Vec, }; +use either::Either; +use itertools::Itertools; use serde_json::{Map, Value}; use devicemapper::{Bytes, Sectors, IEC}; @@ -24,9 +25,10 @@ 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, ValidatedIntegritySpec, + EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, Name, + OptionalTokenSlotInput, PoolDiff, PoolEncryptionInfo, PoolUuid, RegenAction, + RenameAction, SetCreateAction, SetDeleteAction, StratSigblockVersion, UnlockMechanism, + ValidatedIntegritySpec, }, PropChangeAction, }, @@ -95,32 +97,12 @@ impl SimPool { Ok(()) } - fn encryption_info(&self) -> Option { - self.encryption_info - .as_ref() - .map(|p| PoolEncryptionInfo::from(once(p))) + fn encryption_info_legacy(&self) -> Option { + self.encryption_info.as_ref().map(PoolEncryptionInfo::from) } - fn add_clevis_info(&mut self, pin: &str, config: &Value) { - self.encryption_info = self - .encryption_info - .take() - .map(|ei| ei.set_clevis_info((pin.to_owned(), config.to_owned()))); - } - - fn clear_clevis_info(&mut self) { - self.encryption_info = self.encryption_info.take().map(|ei| ei.unset_clevis_info()); - } - - fn add_key_desc(&mut self, key_desc: &KeyDescription) { - self.encryption_info = self - .encryption_info - .take() - .map(|ei| ei.set_key_desc(key_desc.to_owned())); - } - - fn clear_key_desc(&mut self) { - self.encryption_info = self.encryption_info.take().map(|ei| ei.unset_key_desc()); + fn encryption_info(&self) -> Option> { + self.encryption_info.as_ref().cloned().map(Either::Left) } /// Check the limit of filesystems on a pool and return an error if it has been passed. @@ -326,10 +308,11 @@ impl Pool for SimPool { fn bind_clevis( &mut self, + token_slot: OptionalTokenSlotInput, pin: &str, clevis_info: &Value, ) -> StratisResult> { - let encryption_info = match pool_enc_to_enc!(self.encryption_info()) { + let encryption_info = match self.encryption_info.as_mut() { Some(ei) => ei, None => { return Err(StratisError::Msg( @@ -338,24 +321,55 @@ impl Pool for SimPool { } }; - let clevis_info_current = encryption_info.clevis_info(); - if let Some((current_pin, current_info)) = clevis_info_current { - if (current_pin.as_str(), current_info) == (pin, clevis_info) { - Ok(CreateAction::Identity) - } else { - Err(StratisError::Msg(format!( - "This pool is already bound with clevis pin {current_pin} and config {current_info}; - this differs from the requested pin {pin} and config {clevis_info}" - ))) + let token_slot_to_add = match token_slot { + OptionalTokenSlotInput::Some(t) => { + if let Some(info) = encryption_info.get_info(t) { + match info { + UnlockMechanism::KeyDesc(_) => { + return Err(StratisError::Msg(format!( + "Key slot {t} is already in use by a key description" + ))); + } + &UnlockMechanism::ClevisInfo((ref current_pin, ref current_config)) => { + if (current_pin.as_str(), current_config) != (pin, clevis_info) { + return Err(StratisError::Msg(format!( + "Key slot {t} is already in use by Clevis info ({current_pin}, {current_config}); requested ({pin}, {clevis_info})" + ))); + } else { + return Ok(CreateAction::Identity); + } + } + } + } else { + t + } } - } else { - self.add_clevis_info(pin, clevis_info); - Ok(CreateAction::Created(Clevis)) - } + OptionalTokenSlotInput::None => encryption_info.free_token_slot(), + OptionalTokenSlotInput::Legacy => match encryption_info.single_clevis_info() { + Some((_, (current_pin, current_config))) => { + if (current_pin.as_str(), current_config) == (pin, clevis_info) { + return Ok(CreateAction::Identity); + } else { + return Err(StratisError::Msg(format!("Attempted to bind to Clevis with ({pin}, {clevis_info}); already bound with ({current_pin}, {current_config})"))); + } + } + None => encryption_info.free_token_slot(), + }, + }; + + encryption_info.add_info( + token_slot_to_add, + UnlockMechanism::ClevisInfo((pin.to_owned(), clevis_info.to_owned())), + )?; + Ok(CreateAction::Created(Clevis)) } - fn unbind_clevis(&mut self) -> StratisResult> { - let encryption_info = match pool_enc_to_enc!(self.encryption_info()) { + fn bind_keyring( + &mut self, + token_slot: OptionalTokenSlotInput, + key_description: &KeyDescription, + ) -> StratisResult> { + let encryption_info = match self.encryption_info.as_mut() { Some(ei) => ei, None => { return Err(StratisError::Msg( @@ -364,27 +378,67 @@ impl Pool for SimPool { } }; - if encryption_info.key_description().is_none() { - return Err(StratisError::Msg( - "This device is not bound to a keyring passphrase; refusing to remove \ - the only unlocking method" - .to_string(), - )); - } + let token_slot_to_add = match token_slot { + OptionalTokenSlotInput::Some(t) => { + if let Some(info) = encryption_info.get_info(t) { + match info { + UnlockMechanism::KeyDesc(ref key_desc) => { + if key_description != key_desc { + return Err(StratisError::Msg(format!( + "Key slot {t} is already in use by key description {}; requested {}", + key_desc.as_application_str(), + key_description.as_application_str(), + ))); + } else { + return Ok(CreateAction::Identity); + } + } + UnlockMechanism::ClevisInfo(_) => { + return Err(StratisError::Msg(format!( + "Key slot {t} is already in use by a Clevis token" + ))); + } + } + } else { + t + } + } + OptionalTokenSlotInput::None => { + if encryption_info + .all_key_descriptions() + .map(|(_, kd)| kd) + .contains(key_description) + { + return Ok(CreateAction::Identity); + } else { + encryption_info.free_token_slot() + } + } + OptionalTokenSlotInput::Legacy => match encryption_info.single_key_description() { + Some((_, kd)) => { + if kd == key_description { + return Ok(CreateAction::Identity); + } else { + return Err(StratisError::Msg(format!( + "Attempted to bind to key description with {}; already bound with {}", + key_description.as_application_str(), + kd.as_application_str(), + ))); + } + } + None => encryption_info.free_token_slot(), + }, + }; - Ok(if encryption_info.clevis_info().is_some() { - self.clear_clevis_info(); - DeleteAction::Deleted(Clevis) - } else { - DeleteAction::Identity - }) + encryption_info.add_info( + token_slot_to_add, + UnlockMechanism::KeyDesc(key_description.to_owned()), + )?; + Ok(CreateAction::Created(Key)) } - fn bind_keyring( - &mut self, - key_description: &KeyDescription, - ) -> StratisResult> { - let encryption_info = match pool_enc_to_enc!(self.encryption_info()) { + fn unbind_keyring(&mut self, token_slot: Option) -> StratisResult> { + let encryption_info = match self.encryption_info.as_mut() { Some(ei) => ei, None => { return Err(StratisError::Msg( @@ -393,25 +447,36 @@ impl Pool for SimPool { } }; - if let Some(kd) = encryption_info.key_description() { - if key_description == kd { - Ok(CreateAction::Identity) - } else { - Err(StratisError::Msg(format!( - "This pool is already bound with key description {}; - this differs from the requested key description {}", - kd.as_application_str(), - key_description.as_application_str(), - ))) + if encryption_info.all_token_slots().count() < 2 { + return Err(StratisError::Msg( + "There is one remaining unlock method; removing the key description binding \ + would remove the ability to open this device; aborting" + .to_string(), + )); + } + + let t_opt = token_slot.or_else(|| encryption_info.single_key_description().map(|(t, _)| t)); + + let slot = match t_opt { + Some(t) => { + if let Some(UnlockMechanism::ClevisInfo(_)) = encryption_info.get_info(t) { + return Err(StratisError::Msg(format!( + "Cannot unbind token slot {t} from keyring; bound to Clevis", + ))); + }; + encryption_info.remove(t) } + None => false, + }; + Ok(if slot { + DeleteAction::Deleted(Key) } else { - self.add_key_desc(key_description); - Ok(CreateAction::Created(Key)) - } + DeleteAction::Identity + }) } - fn unbind_keyring(&mut self) -> StratisResult> { - let encryption_info = match pool_enc_to_enc!(self.encryption_info()) { + fn unbind_clevis(&mut self, token_slot: Option) -> StratisResult> { + let encryption_info = match self.encryption_info.as_mut() { Some(ei) => ei, None => { return Err(StratisError::Msg( @@ -420,17 +485,29 @@ impl Pool for SimPool { } }; - if encryption_info.clevis_info().is_none() { + if encryption_info.all_token_slots().count() < 2 { return Err(StratisError::Msg( - "This device is not bound to Clevis; refusing to remove the only \ - unlocking method" + "There is one remaining unlock method; removing the Clevis binding \ + would remove the ability to open this device; aborting" .to_string(), )); } - Ok(if encryption_info.key_description().is_some() { - self.clear_key_desc(); - DeleteAction::Deleted(Key) + let t_opt = token_slot.or_else(|| encryption_info.single_clevis_info().map(|(t, _)| t)); + + let slot = match t_opt { + Some(t) => { + if let Some(UnlockMechanism::KeyDesc(_)) = encryption_info.get_info(t) { + return Err(StratisError::Msg(format!( + "Cannot unbind token slot {t} from Clevis; bound to keyring", + ))); + }; + encryption_info.remove(t) + } + None => false, + }; + Ok(if slot { + DeleteAction::Deleted(Clevis) } else { DeleteAction::Identity }) @@ -438,9 +515,10 @@ impl Pool for SimPool { fn rebind_keyring( &mut self, + token_slot: Option, new_key_desc: &KeyDescription, ) -> StratisResult> { - let encryption_info = match pool_enc_to_enc!(self.encryption_info()) { + let encryption_info = match self.encryption_info.as_mut() { Some(ei) => ei, None => { return Err(StratisError::Msg( @@ -449,25 +527,32 @@ impl Pool for SimPool { } }; - if encryption_info.key_description().is_none() { - return Err(StratisError::Msg( - "This device is not bound to a keyring passphrase; cannot change the passphrase" - .to_string(), - )); - } - - Ok(if encryption_info.key_description() != Some(new_key_desc) { - self.add_key_desc(new_key_desc); - RenameAction::Renamed(Key) + if let Some((token_slot, info)) = token_slot + .or_else(|| encryption_info.single_key_description().map(|(t, _)| t)) + .and_then(|t| encryption_info.get_info(t).map(|mech| (t, mech))) + { + match info { + UnlockMechanism::KeyDesc(ref key) => { + if key != new_key_desc { + encryption_info.set_info(token_slot, UnlockMechanism::KeyDesc(new_key_desc.to_owned()))?; + Ok(RenameAction::Renamed(Key)) + } else { + Ok(RenameAction::Identity) + } + } + UnlockMechanism::ClevisInfo(_) => Err(StratisError::Msg(format!( + "Cannot rebind slot {token_slot} with a key description; slot is bound to a Clevis config" + ))), + } } else { - RenameAction::Identity - }) + Err(StratisError::Msg("Cannot rebind empty slot".to_string())) + } } // The sim engine does not store token info so this method will always return // RenameAction::Identity. - fn rebind_clevis(&mut self) -> StratisResult { - let encryption_info = match pool_enc_to_enc!(self.encryption_info()) { + fn rebind_clevis(&mut self, token_slot: Option) -> StratisResult { + let encryption_info = match self.encryption_info.as_ref() { Some(ei) => ei, None => { return Err(StratisError::Msg( @@ -476,12 +561,18 @@ impl Pool for SimPool { } }; - if encryption_info.clevis_info().is_none() { - Err(StratisError::Msg( - "This device is not bound to Clevis; cannot regenerate bindings".to_string(), - )) + if let Some((token_slot, info)) = token_slot + .or_else(|| encryption_info.single_clevis_info().map(|(t, _)| t)) + .and_then(|t| encryption_info.get_info(t).map(|mech| (t, mech))) + { + match info { + UnlockMechanism::KeyDesc(_) => Err(StratisError::Msg(format!( + "Cannot rebind slot {token_slot} with a key description; slot is bound to a key config" + ))), + UnlockMechanism::ClevisInfo(_) => Ok(RegenAction), + } } else { - Ok(RegenAction) + Err(StratisError::Msg("Cannot rebind empty slot".to_string())) } } @@ -713,7 +804,11 @@ impl Pool for SimPool { self.encryption_info().is_some() } - fn encryption_info(&self) -> Option { + fn encryption_info_legacy(&self) -> Option { + self.encryption_info_legacy() + } + + fn encryption_info(&self) -> Option> { self.encryption_info() } diff --git a/src/engine/strat_engine/backstore/backstore/v1.rs b/src/engine/strat_engine/backstore/backstore/v1.rs index a9c5cb5a27..d61c5ae45a 100644 --- a/src/engine/strat_engine/backstore/backstore/v1.rs +++ b/src/engine/strat_engine/backstore/backstore/v1.rs @@ -35,8 +35,8 @@ use crate::{ writing::wipe_sectors, }, types::{ - ActionAvailability, BlockDevTier, DevUuid, EncryptionInfo, KeyDescription, Name, - PoolEncryptionInfo, PoolUuid, + ActionAvailability, BlockDevTier, DevUuid, EncryptionInfo, InputEncryptionInfo, + KeyDescription, Name, PoolEncryptionInfo, PoolUuid, }, }, stratis::{StratisError, StratisResult}, @@ -287,7 +287,7 @@ impl Backstore { pool_uuid: PoolUuid, devices: UnownedDevices, mda_data_size: MDADataSize, - encryption_info: Option<&EncryptionInfo>, + encryption_info: Option<&InputEncryptionInfo>, ) -> StratisResult { let data_tier = DataTier::::new(BlockDevMgr::::initialize( pool_name, @@ -338,6 +338,7 @@ impl Backstore { self.encryption_info() .map(EncryptionInfo::try_from) .transpose()? + .map(InputEncryptionInfo::from) .as_ref(), sector_size, )?; @@ -686,7 +687,9 @@ impl Backstore { } }; - if let Some((ref existing_pin, ref existing_info)) = encryption_info.clevis_info() { + if let Some((_, (ref existing_pin, ref existing_info))) = + encryption_info.single_clevis_info() + { if existing_pin.as_str() == pin && CryptHandle::can_unlock( self.blockdevs() @@ -731,7 +734,7 @@ impl Backstore { } }; - if encryption_info.clevis_info().is_some() { + if encryption_info.single_clevis_info().is_some() { operation_loop( self.blockdevs_mut().into_iter().map(|(_, _, bd)| bd), |blockdev| blockdev.unbind_clevis(), @@ -760,7 +763,7 @@ impl Backstore { } }; - if let Some(kd) = encryption_info.key_description() { + if let Some((_, kd)) = encryption_info.single_key_description() { if kd == key_desc { if CryptHandle::can_unlock( self.blockdevs() @@ -815,7 +818,7 @@ impl Backstore { } }; - if encryption_info.key_description().is_some() { + if encryption_info.single_key_description().is_some() { operation_loop( self.blockdevs_mut().into_iter().map(|(_, _, bd)| bd), |blockdev| blockdev.unbind_keyring(), @@ -845,9 +848,9 @@ impl Backstore { } }; - if encryption_info.key_description() == Some(key_desc) { + if encryption_info.single_key_description().map(|(_, kd)| kd) == Some(key_desc) { Ok(Some(false)) - } else if encryption_info.key_description().is_some() { + } else if encryption_info.single_key_description().is_some() { // Keys are not the same but key description is present operation_loop( self.blockdevs_mut().into_iter().map(|(_, _, bd)| bd), @@ -879,7 +882,7 @@ impl Backstore { } }; - if encryption_info.clevis_info().is_none() { + if encryption_info.single_clevis_info().is_none() { Err(StratisError::Msg( "Requested pool is not already bound to Clevis".to_string(), )) @@ -1310,10 +1313,10 @@ mod tests { pool_uuid, get_devices(paths).unwrap(), MDADataSize::default(), - Some(&EncryptionInfo::ClevisInfo(( + InputEncryptionInfo::new_legacy(None, Some(( "tang".to_string(), json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), - ))), + ))).as_ref() ) .unwrap(); cmd::udev_settle().unwrap(); @@ -1383,13 +1386,13 @@ mod tests { pool_uuid, get_devices(paths).unwrap(), MDADataSize::default(), - Some(&EncryptionInfo::Both( - key_desc.clone(), - ( + InputEncryptionInfo::new_legacy( + Some(key_desc.clone()), + Some(( "tang".to_string(), json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), - ), - )), + )), + ).as_ref(), ).unwrap(); cmd::udev_settle().unwrap(); diff --git a/src/engine/strat_engine/backstore/backstore/v2.rs b/src/engine/strat_engine/backstore/backstore/v2.rs index ed10235457..761a56b675 100644 --- a/src/engine/strat_engine/backstore/backstore/v2.rs +++ b/src/engine/strat_engine/backstore/backstore/v2.rs @@ -33,8 +33,9 @@ use crate::{ writing::wipe_sectors, }, types::{ - ActionAvailability, BlockDevTier, DevUuid, EncryptionInfo, KeyDescription, PoolUuid, - SizedKeyMemory, UnlockMethod, ValidatedIntegritySpec, + ActionAvailability, BlockDevTier, DevUuid, EncryptionInfo, InputEncryptionInfo, + KeyDescription, OptionalTokenSlotInput, PoolUuid, SizedKeyMemory, TokenUnlockMethod, + UnlockMechanism, ValidatedIntegritySpec, }, }, stratis::{StratisError, StratisResult}, @@ -149,7 +150,7 @@ pub struct Backstore { placeholder: Option, /// Either encryption information for a handle to be created at a later time or /// handle for encryption layer in backstore. - enc: Option>, + enc: Option>, /// Data allocations on the cap device, allocs: Vec<(Sectors, Sectors)>, /// Metadata allocations on the cache or placeholder device. @@ -298,7 +299,7 @@ impl Backstore { datadevs: Vec, cachedevs: Vec, last_update_time: DateTime, - unlock_method: Option, + token_slot: TokenUnlockMethod, passphrase: Option, ) -> BDARecordResult { let block_mgr = BlockDevMgr::new(datadevs, Some(last_update_time)); @@ -383,9 +384,13 @@ impl Backstore { .as_str(), )) .collect::(); - let enc = match (metadata_enc_enabled, unlock_method, passphrase.as_ref()) { - (true, Some(unlock_method), pass) => { - match CryptHandle::setup(crypt_physical_path, pool_uuid, unlock_method, pass) { + let has_header = match CryptHandle::load_metadata(crypt_physical_path, pool_uuid) { + Ok(opt) => opt.is_some(), + Err(e) => return Err((e, data_tier.block_mgr.into_bdas())), + }; + let enc = match (metadata_enc_enabled, has_header, passphrase.as_ref()) { + (true, true, pass) => { + match CryptHandle::setup(crypt_physical_path, pool_uuid, token_slot, pass) { Ok(opt) => { if let Some(h) = opt { Some(Either::Right(h)) @@ -396,19 +401,25 @@ impl Backstore { Err(e) => return Err((e, data_tier.block_mgr.into_bdas())), } } - (true, None, _) => { - unreachable!("Checked in liminal device code"); + (true, _, _) => { + return Err(( + StratisError::Msg( + "Metadata reported that encryption is enabled but no header was found" + .to_string(), + ), + data_tier.block_mgr.into_bdas(), + )); } - (false, _, _) => match CryptHandle::load_metadata(crypt_physical_path, pool_uuid) { - Ok(opt) => { - if opt.is_some() { - return Err((StratisError::Msg("Metadata reported that encryption is not enabled but a crypt header was found".to_string()), data_tier.block_mgr.into_bdas())); - } else { - None - } - } - Err(e) => return Err((e, data_tier.block_mgr.into_bdas())), - }, + (false, true, _) => { + return Err(( + StratisError::Msg( + "Metadata reported that encryption is disabled but header was found" + .to_string(), + ), + data_tier.block_mgr.into_bdas(), + )); + } + (false, _, _) => None, }; Ok(Backstore { @@ -436,7 +447,7 @@ impl Backstore { pool_uuid: PoolUuid, devices: UnownedDevices, mda_data_size: MDADataSize, - encryption_info: Option<&EncryptionInfo>, + encryption_info: Option<&InputEncryptionInfo>, integrity_spec: ValidatedIntegritySpec, ) -> StratisResult { let data_tier = DataTier::::new( @@ -910,7 +921,7 @@ impl Backstore { pub fn encryption_info(&self) -> Option<&EncryptionInfo> { self.enc .as_ref() - .map(|either| either.as_ref().either(|e| e, |h| h.encryption_info())) + .and_then(|either| either.as_ref().right().map(|h| h.encryption_info())) } /// Bind device in the given backstore using the given clevis @@ -920,7 +931,12 @@ impl Backstore { /// * Returns Ok(false) if the binding had already been previously performed and /// nothing was changed. /// * Returns Err(_) if binding failed. - pub fn bind_clevis(&mut self, pin: &str, clevis_info: &Value) -> StratisResult { + pub fn bind_clevis( + &mut self, + token_slot: OptionalTokenSlotInput, + pin: &str, + clevis_info: &Value, + ) -> StratisResult { let handle = self .enc .as_mut() @@ -931,29 +947,58 @@ impl Backstore { StratisError::Msg("No space has been allocated from the backstore".to_string()) })?; - if let Some((ref existing_pin, ref existing_info)) = handle.encryption_info().clevis_info() - { - if existing_pin.as_str() == pin { - Ok(false) - } else { - Err(StratisError::Msg(format!( - "Block devices have already been bound with pin {existing_pin} and config {existing_info}; \ - requested pin {pin} and config {clevis_info} can't be applied" - ))) + match token_slot { + OptionalTokenSlotInput::Legacy => { + let ci = handle.encryption_info().single_clevis_info(); + if let Some((_, (existing_pin, existing_info))) = ci { + if existing_pin.as_str() == pin { + Ok(false) + } else { + Err(StratisError::Msg(format!( + "Crypt device has already been bound with pin {existing_pin} and config {existing_info}; \ + requested pin {pin} and config {clevis_info} can't be applied" + ))) + } + } else { + handle.bind_clevis(None, pin, clevis_info)?; + Ok(true) + } + } + OptionalTokenSlotInput::Some(k) => { + // Ignore thumbprint if stratis:tang:trust_url is set in the clevis_info + // config. + let ci = handle.encryption_info().get_info(k); + if let Some(UnlockMechanism::ClevisInfo((existing_pin, existing_info))) = ci { + if existing_pin == pin { + Ok(false) + } else { + Err(StratisError::Msg(format!( + "Crypt device has already been bound with pin {existing_pin} and config {existing_info}; \ + requested pin {pin} and config {clevis_info} can't be applied" + ))) + } + } else { + handle.bind_clevis(Some(k), pin, clevis_info)?; + Ok(true) + } + } + OptionalTokenSlotInput::None => { + // Because idemptotence is checked based on pin, we can't reliably check whether + // the binding has already been applied when no token slot is specified. As a + // result, we default to adding the new config unless a token slot is specified. + handle.bind_clevis(None, pin, clevis_info)?; + Ok(true) } - } else { - handle.clevis_bind(pin, clevis_info)?; - Ok(true) } } - /// Unbind device in the given backstore from clevis. + /// Remove the keyring unlock mechanism specified by the token slot for the backstore. /// /// * Returns Ok(true) if the unbinding was performed. /// * Returns Ok(false) if the unbinding had already been previously performed and /// nothing was changed. /// * Returns Err(_) if unbinding failed. - pub fn unbind_clevis(&mut self) -> StratisResult { + pub fn unbind_keyring(&mut self, token_slot: Option) -> StratisResult { let handle = self .enc .as_mut() @@ -964,22 +1009,37 @@ impl Backstore { StratisError::Msg("No space has been allocated from the backstore".to_string()) })?; - if handle.encryption_info().clevis_info().is_some() { - handle.clevis_unbind()?; - Ok(true) - } else { - Ok(false) + let ei = handle.encryption_info(); + match token_slot { + Some(t) => { + let info = ei.get_info(t); + if let Some(UnlockMechanism::KeyDesc(_)) = info { + handle.unbind_keyring(t)?; + Ok(true) + } else if let Some(UnlockMechanism::ClevisInfo(_)) = info { + Err(StratisError::Msg(format!("Token slot {t} could not be unbound from keyring; it is bound to a Clevis token"))) + } else { + Ok(false) + } + } + None => { + if let Some((t, _)) = ei.single_key_description() { + handle.unbind_keyring(t)?; + Ok(true) + } else { + Ok(false) + } + } } } - /// Bind device in the given backstore to a passphrase using the - /// given key description. + /// Remove the Clevis unlock mechanism specified by the token slot for the backstore. /// - /// * Returns Ok(true) if the binding was performed. - /// * Returns Ok(false) if the binding had already been previously performed and + /// * Returns Ok(true) if the unbinding was performed. + /// * Returns Ok(false) if the unbinding had already been previously performed and /// nothing was changed. - /// * Returns Err(_) if binding failed. - pub fn bind_keyring(&mut self, key_desc: &KeyDescription) -> StratisResult { + /// * Returns Err(_) if unbinding failed. + pub fn unbind_clevis(&mut self, token_slot: Option) -> StratisResult { let handle = self .enc .as_mut() @@ -990,31 +1050,44 @@ impl Backstore { StratisError::Msg("No space has been allocated from the backstore".to_string()) })?; - if let Some(kd) = handle.encryption_info().key_description() { - if kd == key_desc { - Ok(false) - } else { - Err(StratisError::Msg(format!( - "Block devices have already been bound with key description {}; \ - requested key description {} can't be applied", - kd.as_application_str(), - key_desc.as_application_str(), - ))) + let ei = handle.encryption_info(); + match token_slot { + Some(t) => { + let info = ei.get_info(t); + if let Some(UnlockMechanism::ClevisInfo(_)) = info { + handle.unbind_clevis(t)?; + Ok(true) + } else if let Some(UnlockMechanism::KeyDesc(_)) = info { + Err(StratisError::Msg(format!("Token slot {t} could not be unbound from Clevis; it is bound to a key description token"))) + } else { + Ok(false) + } + } + None => { + let opt = ei.single_clevis_info(); + match opt { + Some((t, _)) => { + handle.unbind_clevis(t)?; + Ok(true) + } + None => Ok(false), + } } - } else { - handle.bind_keyring(key_desc)?; - Ok(true) } } - /// Unbind device in the given backstore from the passphrase - /// associated with the key description. + /// Bind device in the given backstore to a passphrase using the + /// given key description. /// - /// * Returns Ok(true) if the unbinding was performed. - /// * Returns Ok(false) if the unbinding had already been previously performed and + /// * Returns Ok(true) if the binding was performed. + /// * Returns Ok(false) if the binding had already been previously performed and /// nothing was changed. - /// * Returns Err(_) if unbinding failed. - pub fn unbind_keyring(&mut self) -> StratisResult { + /// * Returns Err(_) if binding failed. + pub fn bind_keyring( + &mut self, + token_slot: OptionalTokenSlotInput, + key_desc: &KeyDescription, + ) -> StratisResult { let handle = self .enc .as_mut() @@ -1025,12 +1098,59 @@ impl Backstore { StratisError::Msg("No space has been allocated from the backstore".to_string()) })?; - if handle.encryption_info().key_description().is_some() { - handle.unbind_keyring()?; - Ok(true) - } else { - // is encrypted and key description is None - Ok(false) + match token_slot { + OptionalTokenSlotInput::Legacy => { + let info = handle.encryption_info().single_key_description(); + if let Some((_, kd)) = info { + if kd == key_desc { + Ok(false) + } else { + Err(StratisError::Msg(format!( + "Crypt device has already been bound with key description {}; \ + requested key description {} can't be applied", + kd.as_application_str(), + key_desc.as_application_str(), + ))) + } + } else { + handle.bind_keyring(None, key_desc)?; + Ok(true) + } + } + OptionalTokenSlotInput::Some(k) => { + // Ignore thumbprint if stratis:tang:trust_url is set in the clevis_info + // config. + let info = handle.encryption_info().get_info(k); + if let Some(UnlockMechanism::KeyDesc(ref kd)) = info { + if kd == key_desc { + Ok(false) + } else { + Err(StratisError::Msg(format!( + "Crypt device has already been bound with key description {}; \ + requested key description {} can't be applied", + kd.as_application_str(), + key_desc.as_application_str(), + ))) + } + } else { + handle.bind_keyring(Some(k), key_desc)?; + Ok(true) + } + } + OptionalTokenSlotInput::None => { + // Ignore thumbprint if stratis:tang:trust_url is set in the clevis_info + // config. + let existing_config = handle + .encryption_info() + .all_key_descriptions() + .find(|(_, kd)| *kd == key_desc); + if existing_config.is_some() { + Ok(false) + } else { + handle.bind_keyring(None, key_desc)?; + Ok(true) + } + } } } @@ -1041,7 +1161,11 @@ impl Backstore { /// * Ok(Some(true)) if the pool was successfully bound to the new key description. /// * Ok(Some(false)) if the pool is already bound to this key description. /// * Err(_) if an operation fails while changing the passphrase. - pub fn rebind_keyring(&mut self, key_desc: &KeyDescription) -> StratisResult> { + pub fn rebind_keyring( + &mut self, + token_slot: Option, + key_desc: &KeyDescription, + ) -> StratisResult> { let handle = self .enc .as_mut() @@ -1052,14 +1176,32 @@ impl Backstore { StratisError::Msg("No space has been allocated from the backstore".to_string()) })?; - if handle.encryption_info().key_description() == Some(key_desc) { - Ok(Some(false)) - } else if handle.encryption_info().key_description().is_some() { - // Keys are not the same but key description is present - handle.rebind_keyring(key_desc)?; - Ok(Some(true)) - } else { - Ok(None) + let ei = handle.encryption_info(); + match token_slot { + Some(t) => { + let info = ei.get_info(t); + match info { + Some(UnlockMechanism::KeyDesc(ref kd)) => if kd == key_desc { + Ok(Some(false)) + } else { + handle.rebind_keyring(t, key_desc)?; + Ok(Some(true)) + }, + Some(UnlockMechanism::ClevisInfo(_)) => Err(StratisError::Msg(format!("Cannot rebind keyring implementation; token slot {t} is already bound to Clevis"))), + None => Ok(None) + } + } + None => match ei.single_key_description() { + Some((slot, kd)) => { + if kd == key_desc { + Ok(Some(false)) + } else { + handle.rebind_keyring(slot, key_desc)?; + Ok(Some(true)) + } + } + None => Ok(None), + }, } } @@ -1070,7 +1212,7 @@ impl Backstore { /// will always change the metadata when successful. The command is not idempotent /// so this method will either fail to regenerate the bindings or it will /// result in a metadata change. - pub fn rebind_clevis(&mut self) -> StratisResult<()> { + pub fn rebind_clevis(&mut self, token_slot: Option) -> StratisResult<()> { let handle = self .enc .as_mut() @@ -1081,14 +1223,22 @@ impl Backstore { StratisError::Msg("No space has been allocated from the backstore".to_string()) })?; - if handle.encryption_info().clevis_info().is_none() { - Err(StratisError::Msg( - "Requested pool is not already bound to Clevis".to_string(), - )) - } else { - handle.rebind_clevis()?; - - Ok(()) + let ei = handle.encryption_info(); + match token_slot { + Some(t) => { + let info = ei.get_info(t); + match info { + Some(UnlockMechanism::KeyDesc(_)) => Err(StratisError::Msg(format!("Cannot rebind Clevis implementation; token slot {t} is already bound to a key description"))), + Some(UnlockMechanism::ClevisInfo(_)) => handle.rebind_clevis(t), + None => Err(StratisError::Msg(format!("Cannot rebind clevis implementation; token slot {t} is unbound"))), + } + } + None => match ei.single_clevis_info() { + Some((t, _)) => handle.rebind_clevis(t), + None => Err(StratisError::Msg( + "Cannot rebind clevis implementation; no Clevis tokens are present".to_string(), + )), + }, } } @@ -1411,14 +1561,20 @@ mod tests { unshare_mount_namespace().unwrap(); let _memfs = MemoryFilesystem::new().unwrap(); let pool_uuid = PoolUuid::new_v4(); + let ei = InputEncryptionInfo::new( + vec![], + vec![ + (None, ( + "tang".to_string(), + json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), + )) + ] + ).expect("Empty data structure"); let mut backstore = Backstore::initialize( pool_uuid, get_devices(paths).unwrap(), MDADataSize::default(), - Some(&EncryptionInfo::ClevisInfo(( - "tang".to_string(), - json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), - ))), + ei.as_ref(), ValidatedIntegritySpec::default(), ) .unwrap(); @@ -1427,6 +1583,7 @@ mod tests { assert_matches!( backstore.bind_clevis( + OptionalTokenSlotInput::None, "tang", &json!({"url": env::var("TANG_URL").expect("TANG_URL env var required")}) ), @@ -1435,6 +1592,7 @@ mod tests { assert_matches!( backstore.bind_clevis( + OptionalTokenSlotInput::None, "tang", &json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}) ), @@ -1483,19 +1641,21 @@ mod tests { unshare_mount_namespace().unwrap(); let _memfs = MemoryFilesystem::new().unwrap(); let pool_uuid = PoolUuid::new_v4(); + let ei = InputEncryptionInfo::new( + vec![(None, key_desc.to_owned())], + vec![(None, ( + "tang".to_string(), + json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), + ))], + ).expect("Empty data structure"); let mut backstore = Backstore::initialize( pool_uuid, get_devices(paths).unwrap(), MDADataSize::default(), - Some(&EncryptionInfo::Both( - key_desc.clone(), - ( - "tang".to_string(), - json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), - ), - )), + ei.as_ref(), ValidatedIntegritySpec::default(), - ).unwrap(); + ) + .unwrap(); cmd::udev_settle().unwrap(); // Allocate space from the backstore so that the cap device is made. @@ -1504,7 +1664,20 @@ mod tests { .unwrap() .unwrap(); + let ei = backstore.encryption_info().unwrap(); + let (kd_slot, _) = ei + .all_key_descriptions() + .next() + .map(|(slot, kd)| (*slot, kd.clone())) + .expect("Set one key description"); + let (ci_slot, _) = ei + .all_clevis_infos() + .next() + .map(|(slot, ci)| (*slot, ci.clone())) + .expect("Set one Clevis info"); + if backstore.bind_clevis( + OptionalTokenSlotInput::Some(ci_slot), "tang", &json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), ).unwrap() { @@ -1515,31 +1688,42 @@ mod tests { invariant(&backstore); - if backstore.bind_keyring(key_desc).unwrap() { + if backstore + .bind_keyring(OptionalTokenSlotInput::None, key_desc) + .unwrap() + { + panic!("Keyring bind idempotence test failed") + } + + if backstore + .bind_keyring(OptionalTokenSlotInput::Some(kd_slot), key_desc) + .unwrap() + { panic!("Keyring bind idempotence test failed") } invariant(&backstore); - if !backstore.unbind_clevis().unwrap() { + if !backstore.unbind_clevis(Some(ci_slot)).unwrap() { panic!("Clevis unbind test failed"); } invariant(&backstore); - if backstore.unbind_clevis().unwrap() { + if backstore.unbind_clevis(Some(ci_slot)).unwrap() { panic!("Clevis unbind idempotence test failed"); } invariant(&backstore); - if backstore.unbind_keyring().is_ok() { + if backstore.unbind_keyring(Some(kd_slot)).is_ok() { panic!("Keyring unbind check test failed"); } invariant(&backstore); if !backstore.bind_clevis( + OptionalTokenSlotInput::Some(10), "tang", &json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), ).unwrap() { @@ -1550,25 +1734,35 @@ mod tests { invariant(&backstore); - if !backstore.unbind_keyring().unwrap() { + if !backstore.unbind_keyring(Some(kd_slot)).unwrap() { panic!("Keyring unbind test failed"); } invariant(&backstore); - if backstore.unbind_keyring().unwrap() { + if backstore.unbind_keyring(Some(kd_slot)).unwrap() { panic!("Keyring unbind idempotence test failed"); } invariant(&backstore); - if backstore.unbind_clevis().is_ok() { + if backstore.unbind_clevis(Some(10)).is_ok() { panic!("Clevis unbind check test failed"); } invariant(&backstore); - if !backstore.bind_keyring(key_desc).unwrap() { + if !backstore + .bind_keyring(OptionalTokenSlotInput::Some(11), key_desc) + .unwrap() + { + panic!("Keyring bind test failed"); + } + + if !backstore + .bind_keyring(OptionalTokenSlotInput::Some(12), key_desc) + .unwrap() + { panic!("Keyring bind test failed"); } } diff --git a/src/engine/strat_engine/backstore/blockdevmgr.rs b/src/engine/strat_engine/backstore/blockdevmgr.rs index 136371397b..a71e5f926b 100644 --- a/src/engine/strat_engine/backstore/blockdevmgr.rs +++ b/src/engine/strat_engine/backstore/blockdevmgr.rs @@ -30,7 +30,8 @@ use crate::{ shared::bds_to_bdas, }, types::{ - DevUuid, EncryptionInfo, Name, PoolEncryptionInfo, PoolUuid, ValidatedIntegritySpec, + DevUuid, InputEncryptionInfo, Name, PoolEncryptionInfo, PoolUuid, + ValidatedIntegritySpec, }, }, stratis::{StratisError, StratisResult}, @@ -86,7 +87,7 @@ impl BlockDevMgr { pool_uuid: PoolUuid, devices: UnownedDevices, mda_data_size: MDADataSize, - encryption_info: Option<&EncryptionInfo>, + encryption_info: Option<&InputEncryptionInfo>, sector_size: Option, ) -> StratisResult> { Ok(BlockDevMgr::new( @@ -128,8 +129,8 @@ impl BlockDevMgr { .first() .expect("Must have at least one blockdev") .physical_path(), - ei.key_description().is_some(), - ei.clevis_info().is_some(), + ei.single_key_description().is_some(), + ei.single_clevis_info().is_some(), ) { return Err(StratisError::Msg( "Either the key in the kernel keyring, Clevis, or both could not be used to perform encryption operations on the devices in the pool; check that the appropriate key in the keyring is set and that the Clevis key storage method is available depending on your provided unlock methods".to_string(), @@ -146,7 +147,7 @@ impl BlockDevMgr { pool_name, pool_uuid, MDADataSize::default(), - encryption_info.as_ref(), + encryption_info.map(InputEncryptionInfo::from).as_ref(), sector_size, )?; let bdev_uuids = bds.iter().map(|bd| bd.uuid()).collect(); @@ -602,7 +603,7 @@ mod tests { pool_uuid, devices1, MDADataSize::default(), - Some(&EncryptionInfo::KeyDesc(key_desc.clone())), + InputEncryptionInfo::new_legacy(Some(key_desc.clone()), None).as_ref(), None, ) .unwrap(); @@ -649,7 +650,7 @@ mod tests { pool_uuid, devices1, MDADataSize::default(), - Some(&EncryptionInfo::KeyDesc(key_desc.clone())), + InputEncryptionInfo::new_legacy(Some(key_desc.clone()), None).as_ref(), None, ) .unwrap(); diff --git a/src/engine/strat_engine/backstore/devices.rs b/src/engine/strat_engine/backstore/devices.rs index d8ff0a8837..0589d65ed9 100644 --- a/src/engine/strat_engine/backstore/devices.rs +++ b/src/engine/strat_engine/backstore/devices.rs @@ -38,7 +38,7 @@ use crate::{ STRATIS_FS_TYPE, }, }, - types::{DevUuid, DevicePath, EncryptionInfo, Name, PoolUuid, StratSigblockVersion}, + types::{DevUuid, DevicePath, InputEncryptionInfo, Name, PoolUuid, StratSigblockVersion}, }, stratis::{StratisError, StratisResult}, }; @@ -506,7 +506,7 @@ pub fn initialize_devices_legacy( pool_name: Name, pool_uuid: PoolUuid, mda_data_size: MDADataSize, - encryption_info: Option<&EncryptionInfo>, + encryption_info: Option<&InputEncryptionInfo>, sector_size: Option, ) -> StratisResult> { /// Initialize an encrypted device on the given physical device @@ -521,7 +521,7 @@ pub fn initialize_devices_legacy( pool_name: Name, pool_uuid: PoolUuid, dev_uuid: DevUuid, - encryption_info: &EncryptionInfo, + encryption_info: &InputEncryptionInfo, sector_size: Option, ) -> StratisResult<(CryptHandle, Device, Sectors)> { let handle = CryptHandle::initialize( @@ -653,7 +653,7 @@ pub fn initialize_devices_legacy( pool_name: Name, pool_uuid: PoolUuid, mda_data_size: MDADataSize, - encryption_info: Option<&EncryptionInfo>, + encryption_info: Option<&InputEncryptionInfo>, sector_size: Option, ) -> StratisResult { let dev_uuid = DevUuid::new_v4(); @@ -729,7 +729,7 @@ pub fn initialize_devices_legacy( pool_name: Name, pool_uuid: PoolUuid, mda_data_size: MDADataSize, - encryption_info: Option<&EncryptionInfo>, + encryption_info: Option<&InputEncryptionInfo>, sector_size: Option, ) -> StratisResult> { let mut initialized_blockdevs: Vec = Vec::new(); @@ -1077,7 +1077,7 @@ mod tests { pool_uuid, MDADataSize::default(), key_description - .map(|kd| EncryptionInfo::KeyDesc(kd.clone())) + .and_then(|kd| InputEncryptionInfo::new_legacy(Some(kd.clone()), None)) .as_ref(), None, ) @@ -1292,7 +1292,7 @@ mod tests { pool_uuid, MDADataSize::default(), key_desc - .map(|kd| EncryptionInfo::KeyDesc(kd.clone())) + .and_then(|kd| InputEncryptionInfo::new_legacy(Some(kd.clone()), None)) .as_ref(), None, ) diff --git a/src/engine/strat_engine/cmd.rs b/src/engine/strat_engine/cmd.rs index 64e9780fe2..d0dd2343c3 100644 --- a/src/engine/strat_engine/cmd.rs +++ b/src/engine/strat_engine/cmd.rs @@ -367,8 +367,8 @@ pub fn udev_settle() -> StratisResult<()> { /// Bind a LUKS device using clevis. pub fn clevis_luks_bind( dev_path: &Path, - existing_auth: Either, - slot: c_uint, + existing_auth: &Either, + slot: Option, pin: &str, json: &Value, yes: bool, @@ -387,10 +387,10 @@ pub fn clevis_luks_bind( cmd.arg("-e").arg(token_slot.to_string()); } - cmd.arg("-t") - .arg(slot.to_string()) - .arg(pin) - .arg(json.to_string()); + if let Some(t) = slot { + cmd.arg("-t").arg(t.to_string()); + } + cmd.arg(pin).arg(json.to_string()); match existing_auth { Either::Left(_) => { diff --git a/src/engine/strat_engine/crypt/consts.rs b/src/engine/strat_engine/crypt/consts.rs index d4f953fa1e..343ee66532 100644 --- a/src/engine/strat_engine/crypt/consts.rs +++ b/src/engine/strat_engine/crypt/consts.rs @@ -20,6 +20,7 @@ pub const CLEVIS_LUKS_TOKEN_ID: c_uint = 2; pub const LUKS2_TOKEN_TYPE: &str = "luks2-keyring"; pub const STRATIS_TOKEN_TYPE: &str = "stratis"; +pub const CLEVIS_TOKEN_TYPE: &str = "clevis"; /// The size of the media encryption key generated by cryptsetup for /// each block device. diff --git a/src/engine/strat_engine/crypt/handle/v1.rs b/src/engine/strat_engine/crypt/handle/v1.rs index 7e2625fd21..43af0cf138 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, activate_by_token, 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, + check_luks2_token, clevis_decrypt, device_from_physical_path, + encryption_info_from_metadata, ensure_inactive, ensure_wiped, + get_keyslot_number, interpret_clevis_config, luks2_token_type_is_valid, + read_key, wipe_fallback, }, }, dm::DEVICEMAPPER_PATH, @@ -52,10 +53,9 @@ use crate::{ names::format_crypt_name, }, types::{ - DevUuid, DevicePath, EncryptionInfo, KeyDescription, Name, PoolUuid, SizedKeyMemory, - UnlockMethod, + DevUuid, DevicePath, EncryptionInfo, InputEncryptionInfo, KeyDescription, Name, + PoolUuid, SizedKeyMemory, TokenUnlockMethod, UnlockMechanism, }, - ClevisInfo, }, stratis::{StratisError, StratisResult}, }; @@ -282,38 +282,8 @@ pub fn load_crypt_metadata( let identifiers = identifiers_from_metadata(device)?; let activation_name = activation_name_from_metadata(device)?; let pool_name = pool_name_from_metadata(device)?; - let key_description = key_desc_from_metadata(device); + let encryption_info = encryption_info_from_metadata(device)?; let devno = get_devno_from_path(physical_path)?; - let key_description = match key_description - .as_ref() - .map(|kd| KeyDescription::from_system_key_desc(kd)) - { - Some(Some(Ok(description))) => Some(description), - Some(Some(Err(e))) => { - return Err(StratisError::Msg(format!( - "key description {} found on devnode {} is not a valid Stratis key description: {}", - key_description.expect("key_desc_from_metadata determined to be Some(_) above"), - physical_path.display(), - e, - ))); - } - Some(None) => { - warn!("Key description stored on device {} does not appear to be a Stratis key description; ignoring", physical_path.display()); - None - } - None => None, - }; - let clevis_info = clevis_info_from_metadata(device)?; - - let encryption_info = - if let Some(info) = EncryptionInfo::from_options((key_description, clevis_info)) { - info - } else { - return Err(StratisError::Msg(format!( - "No valid encryption method that can be used to unlock device {} found", - physical_path.display() - ))); - }; let path = vec![DEVICEMAPPER_PATH, &activation_name.to_string()] .into_iter() @@ -412,7 +382,7 @@ pub fn setup_crypt_device(physical_path: &Path) -> StratisResult, + unlock_method: TokenUnlockMethod, passphrase: Option<&SizedKeyMemory>, ) -> StratisResult> { let metadata = match load_crypt_metadata(device, physical_path)? { @@ -425,11 +395,11 @@ fn setup_crypt_handle( .collect::() .exists() { - if let Some(unlock) = unlock_method { + if let Ok(opt) = unlock_method.get_token_slot() { activate( device, - metadata.encryption_info.key_description(), - unlock, + &metadata.encryption_info, + opt, passphrase, &metadata.activation_name, )? @@ -512,7 +482,8 @@ impl CryptHandle { let mut device = acquire_crypt_device(physical_path)?; if try_unlock_keyring { - let key_description = key_desc_from_metadata(&mut device); + let encryption_info = encryption_info_from_metadata(&mut device)?; + let key_description = encryption_info.single_key_description(); if key_description.is_some() { check_luks2_token(&mut device)?; @@ -550,7 +521,7 @@ impl CryptHandle { pool_uuid: PoolUuid, dev_uuid: DevUuid, pool_name: Name, - encryption_info: &EncryptionInfo, + encryption_info: &InputEncryptionInfo, sector_size: Option, ) -> StratisResult { let activation_name = format_crypt_name(&dev_uuid); @@ -577,18 +548,8 @@ impl CryptHandle { KeyslotsSize::try_from(convert_int!(*DEFAULT_CRYPT_KEYSLOTS_SIZE, u128, u64)?)?, )?; Self::initialize_with_err(&mut device, physical_path, pool_uuid, dev_uuid, &pool_name, encryption_info, luks2_params.as_ref()) - .and_then(|path| clevis_info_from_metadata(&mut device).map(|ci| (path, ci))) - .and_then(|(_, clevis_info)| { - let encryption_info = - if let Some(info) = EncryptionInfo::from_options((encryption_info.key_description().cloned(), clevis_info)) { - info - } else { - return Err(StratisError::Msg(format!( - "No valid encryption method that can be used to unlock device {} found after initialization", - physical_path.display() - ))); - }; - + .and_then(|_| encryption_info_from_metadata(&mut device)) + .and_then(|encryption_info| { let device_path = DevicePath::new(physical_path)?; let devno = get_devno_from_path(physical_path)?; Ok(CryptHandle::new( @@ -618,7 +579,7 @@ impl CryptHandle { device: &mut CryptDevice, key_description: &KeyDescription, ) -> StratisResult<()> { - add_keyring_keyslot(device, key_description, None)?; + add_keyring_keyslot(device, Some(LUKS2_TOKEN_ID), key_description, None)?; Ok(()) } @@ -650,8 +611,8 @@ impl CryptHandle { clevis_luks_bind( physical_path, - Either::Right(key), - CLEVIS_LUKS_TOKEN_ID, + &Either::Right(key), + Some(CLEVIS_LUKS_TOKEN_ID), pin, json, yes, @@ -682,8 +643,8 @@ impl CryptHandle { clevis_luks_bind( physical_path, - Either::Left(LUKS2_TOKEN_ID), - CLEVIS_LUKS_TOKEN_ID, + &Either::Left(LUKS2_TOKEN_ID), + Some(CLEVIS_LUKS_TOKEN_ID), pin, json, yes, @@ -707,7 +668,7 @@ impl CryptHandle { pool_uuid: PoolUuid, dev_uuid: DevUuid, pool_name: &Name, - encryption_info: &EncryptionInfo, + encryption_info: &InputEncryptionInfo, luks2_params: Option<&CryptParamsLuks2>, ) -> StratisResult<()> { let mut luks2_params_ref: Option> = @@ -725,19 +686,24 @@ impl CryptHandle { physical_path.display() ); - match encryption_info { - EncryptionInfo::Both(kd, (pin, config)) => { + match encryption_info.into_parts_legacy()? { + (Some(kd), Some((pin, config))) => { let mut parsed_config = config.clone(); let y = interpret_clevis_config(pin, &mut parsed_config)?; Self::initialize_with_both(device, physical_path, kd, (pin, &parsed_config, y))? } - EncryptionInfo::KeyDesc(kd) => Self::initialize_with_keyring(device, kd)?, - EncryptionInfo::ClevisInfo((pin, config)) => { + (Some(kd), _) => Self::initialize_with_keyring(device, kd)?, + (_, Some((pin, config))) => { let mut parsed_config = config.clone(); let y = interpret_clevis_config(pin, &mut parsed_config)?; Self::initialize_with_clevis(device, physical_path, (pin, &parsed_config, y))? } - }; + (_, _) => { + return Err(StratisError::Msg( + "Found no available unlock methods for encrypted device".to_string(), + )) + } + } let activation_name = format_crypt_name(&dev_uuid); // Initialize stratis token @@ -756,13 +722,9 @@ impl CryptHandle { "Failed to create the Stratis token" ); - activate( - device, - encryption_info.key_description(), - UnlockMethod::Any, - None, - &activation_name, - ) + let encryption_info = encryption_info_from_metadata(device)?; + + activate(device, &encryption_info, None, None, &activation_name) } pub fn rollback( @@ -795,7 +757,7 @@ impl CryptHandle { /// * has a token of the proper type for LUKS2 keyring unlocking pub fn setup( physical_path: &Path, - unlock_method: Option, + unlock_method: TokenUnlockMethod, passphrase: Option<&SizedKeyMemory>, ) -> StratisResult> { match setup_crypt_device(physical_path)? { @@ -866,11 +828,6 @@ impl CryptHandle { get_keyslot_number(&mut self.acquire_crypt_device()?, token_id) } - /// Get info for the clevis binding. - pub fn clevis_info(&self) -> StratisResult> { - clevis_info_from_metadata(&mut self.acquire_crypt_device()?) - } - /// Bind the given device using clevis. pub fn clevis_bind(&mut self, pin: &str, json: &Value) -> StratisResult<()> { let mut json_owned = json.clone(); @@ -878,28 +835,27 @@ impl CryptHandle { clevis_luks_bind( self.luks2_device_path(), - Either::Left(LUKS2_TOKEN_ID), - CLEVIS_LUKS_TOKEN_ID, + &Either::Left(LUKS2_TOKEN_ID), + Some(CLEVIS_LUKS_TOKEN_ID), pin, &json_owned, yes, )?; - self.metadata.encryption_info = - self.metadata - .encryption_info - .clone() - .set_clevis_info(self.clevis_info()?.ok_or_else(|| { - StratisError::Msg( - "Clevis reported successfully binding to device but no metadata was found" - .to_string(), - ) - })?); + self.metadata.encryption_info.add_info( + CLEVIS_LUKS_TOKEN_ID, + UnlockMechanism::ClevisInfo((pin.to_owned(), json.to_owned())), + )?; Ok(()) } /// Unbind the given device using clevis. pub fn clevis_unbind(&mut self) -> StratisResult<()> { - if self.metadata.encryption_info.key_description().is_none() { + if self + .metadata + .encryption_info + .single_key_description() + .is_none() + { return Err(StratisError::Msg( "No kernel keyring binding found; removing the Clevis binding \ would remove the ability to open this device; aborting" @@ -917,7 +873,7 @@ impl CryptHandle { "Failed to unbind device {} from Clevis", self.luks2_device_path().display() ); - self.metadata.encryption_info = self.metadata.encryption_info.clone().unset_clevis_info(); + self.metadata.encryption_info.remove(CLEVIS_LUKS_TOKEN_ID); Ok(()) } @@ -927,7 +883,7 @@ impl CryptHandle { /// the config may change specifically in the case where a new thumbprint /// is provided if Tang keys are rotated. pub fn rebind_clevis(&mut self) -> StratisResult<()> { - if self.metadata.encryption_info.clevis_info().is_none() { + if self.metadata.encryption_info.single_clevis_info().is_none() { return Err(StratisError::Msg( "No Clevis binding found; cannot regenerate the Clevis binding if the device does not already have a Clevis binding".to_string(), )); @@ -950,24 +906,14 @@ impl CryptHandle { )); } - let (pin, config) = clevis_info_from_metadata(&mut device)?.ok_or_else(|| { - StratisError::Msg(format!( - "Did not find Clevis metadata on device {}", - self.luks2_device_path().display() - )) - })?; - self.metadata.encryption_info = self - .metadata - .encryption_info - .clone() - .set_clevis_info((pin, config)); + self.metadata.encryption_info = encryption_info_from_metadata(&mut device)?; Ok(()) } /// 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, CLEVIS_LUKS_TOKEN_ID)?.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 \ @@ -976,19 +922,23 @@ impl CryptHandle { ) })?; - add_keyring_keyslot(&mut device, key_desc, Some(Either::Left(key)))?; + add_keyring_keyslot( + &mut device, + Some(LUKS2_TOKEN_ID), + key_desc, + Some(Either::Left(key)), + )?; - self.metadata.encryption_info = self - .metadata - .encryption_info - .clone() - .set_key_desc(key_desc.clone()); + self.metadata.encryption_info.add_info( + LUKS2_TOKEN_ID, + UnlockMechanism::KeyDesc(key_desc.to_owned()), + )?; Ok(()) } /// Add a keyring binding to the underlying LUKS2 volume. pub fn unbind_keyring(&mut self) -> StratisResult<()> { - if self.metadata.encryption_info.clevis_info().is_none() { + if self.metadata.encryption_info.single_clevis_info().is_none() { return Err(StratisError::Msg( "No Clevis binding was found; removing the keyring binding would \ remove the ability to open this device; aborting" @@ -1004,7 +954,7 @@ impl CryptHandle { .token_handle() .json_set(TokenInput::RemoveToken(LUKS2_TOKEN_ID))?; - self.metadata.encryption_info = self.metadata.encryption_info.clone().unset_key_desc(); + self.metadata.encryption_info.remove(LUKS2_TOKEN_ID); Ok(()) } @@ -1013,21 +963,21 @@ impl CryptHandle { pub fn rebind_keyring(&mut self, new_key_desc: &KeyDescription) -> StratisResult<()> { let mut device = self.acquire_crypt_device()?; - let old_key_description = self.metadata.encryption_info - .key_description() + let (_, old_key_description) = self.metadata.encryption_info + .single_key_description() .ok_or_else(|| { StratisError::Msg("Cannot change passphrase because this device is not bound to a passphrase in the kernel keyring".to_string()) })?; add_keyring_keyslot( &mut device, + Some(LUKS2_TOKEN_ID), new_key_desc, Some(Either::Right(old_key_description)), )?; - self.metadata.encryption_info = self - .metadata - .encryption_info - .clone() - .set_key_desc(new_key_desc.clone()); + self.metadata.encryption_info.set_info( + LUKS2_TOKEN_ID, + UnlockMechanism::KeyDesc(new_key_desc.clone()), + )?; Ok(()) } @@ -1037,24 +987,6 @@ impl CryptHandle { replace_pool_name(&mut device, pool_name) } - /// Decrypt a Clevis passphrase and return it securely. - fn clevis_decrypt(device: &mut CryptDevice) -> StratisResult> { - let mut token = match device.token_handle().json_get(CLEVIS_LUKS_TOKEN_ID).ok() { - Some(t) => t, - None => return Ok(None), - }; - let jwe = token - .as_object_mut() - .and_then(|map| map.remove("jwe")) - .ok_or_else(|| { - StratisError::Msg(format!( - "Token slot {CLEVIS_LUKS_TOKEN_ID} is occupied but does not appear to be a Clevis \ - token; aborting" - )) - })?; - clevis_decrypt(&jwe).map(Some) - } - /// Deactivate the device referenced by the current device handle. pub fn deactivate(&self) -> StratisResult<()> { ensure_inactive(&mut self.acquire_crypt_device()?, self.activation_name()) @@ -1099,12 +1031,12 @@ impl CryptHandle { None => 0, }; let mut crypt = self.acquire_crypt_device()?; - let passphrase = if let Some(kd) = self.encryption_info().key_description() { + let passphrase = if let Some((_, kd)) = self.encryption_info().single_key_description() { read_key(kd)?.ok_or_else(|| { 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") + } else if self.encryption_info().single_clevis_info().is_some() { + clevis_decrypt(&mut crypt, CLEVIS_LUKS_TOKEN_ID)?.expect("Already checked token exists") } else { unreachable!("Must be encrypted") }; @@ -1151,7 +1083,7 @@ mod tests { ns::{unshare_mount_namespace, MemoryFilesystem}, tests::{crypt, loopbacked, real}, }, - types::{DevUuid, EncryptionInfo, KeyDescription, Name, PoolUuid, UnlockMethod}, + types::{DevUuid, KeyDescription, Name, PoolUuid, UnlockMethod}, }; use super::*; @@ -1175,7 +1107,9 @@ mod tests { pool_uuid, dev_uuid, pool_name, - &EncryptionInfo::KeyDesc(key_description), + InputEncryptionInfo::new_legacy(Some(key_description), None) + .as_ref() + .expect("Passed in key description"), None, ); @@ -1219,7 +1153,9 @@ mod tests { pool_uuid, dev_uuid, pool_name.clone(), - &EncryptionInfo::KeyDesc(key_desc.clone()), + InputEncryptionInfo::new_legacy(Some(key_desc.to_owned()), None) + .as_ref() + .expect("Passed in key description"), None, ) .unwrap(); @@ -1300,7 +1236,9 @@ mod tests { pool_uuid, dev_uuid, pool_name, - &EncryptionInfo::KeyDesc(key_desc.clone()), + InputEncryptionInfo::new_legacy(Some(key_desc.to_owned()), None) + .as_ref() + .expect("Passed in key description"), None, ) .unwrap(); @@ -1382,14 +1320,18 @@ mod tests { handle.deactivate().unwrap(); - let handle = CryptHandle::setup(path, Some(UnlockMethod::Keyring), None) - .unwrap() - .unwrap_or_else(|| { - panic!( - "Device {} no longer appears to be a LUKS2 device", - path.display(), - ) - }); + let handle = CryptHandle::setup( + path, + TokenUnlockMethod::from(Some(UnlockMethod::Keyring)), + None, + ) + .unwrap() + .unwrap_or_else(|| { + panic!( + "Device {} no longer appears to be a LUKS2 device", + path.display(), + ) + }); handle.wipe().unwrap(); } @@ -1444,7 +1386,9 @@ mod tests { pool_uuid, dev_uuid, pool_name, - &EncryptionInfo::KeyDesc(key_description.clone()), + InputEncryptionInfo::new_legacy(Some(key_description.clone()), None) + .as_ref() + .expect("Passed in key description"), Some(4096u32), ) .unwrap(); @@ -1467,13 +1411,13 @@ mod tests { PoolUuid::new_v4(), DevUuid::new_v4(), pool_name, - &EncryptionInfo::Both( - key_desc.clone(), - ( + InputEncryptionInfo::new_legacy( + Some(key_desc.clone()), + Some(( "tang".to_string(), json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), - ), - ), + )), + ).as_ref().expect("Passed in key description and Clevis info"), None, ).unwrap(); @@ -1488,9 +1432,13 @@ mod tests { fn unlock_clevis(paths: &[&Path]) { let path = paths.first().copied().expect("Expected exactly one path"); - CryptHandle::setup(path, Some(UnlockMethod::Clevis), None) - .unwrap() - .unwrap(); + CryptHandle::setup( + path, + TokenUnlockMethod::from(Some(UnlockMethod::Clevis)), + None, + ) + .unwrap() + .unwrap(); } crypt::insert_and_remove_key(paths, both_initialize, |paths, _| unlock_clevis(paths)); @@ -1541,10 +1489,10 @@ mod tests { PoolUuid::new_v4(), DevUuid::new_v4(), pool_name, - &EncryptionInfo::ClevisInfo(( + InputEncryptionInfo::new_legacy(None, Some(( "tang".to_string(), json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), - )), + ))).as_ref().expect("Passed in Clevis config"), None, ) .unwrap(); @@ -1597,10 +1545,15 @@ mod tests { PoolUuid::new_v4(), DevUuid::new_v4(), pool_name.clone(), - &EncryptionInfo::ClevisInfo(( - "tang".to_string(), - json!({"url": env::var("TANG_URL").expect("TANG_URL env var required")}), - )), + InputEncryptionInfo::new_legacy( + None, + Some(( + "tang".to_string(), + json!({"url": env::var("TANG_URL").expect("TANG_URL env var required")}), + )) + ) + .as_ref() + .expect("Passed in Clevis config"), None, ) .is_err()); @@ -1609,13 +1562,15 @@ mod tests { PoolUuid::new_v4(), DevUuid::new_v4(), pool_name, - &EncryptionInfo::ClevisInfo(( - "tang".to_string(), - json!({ - "stratis:tang:trust_url": true, - "url": env::var("TANG_URL").expect("TANG_URL env var required"), - }), - )), + InputEncryptionInfo::new_legacy( + None, + Some(( + "tang".to_string(), + json!({"url": env::var("TANG_URL").expect("TANG_URL env var required")}), + )), + ) + .as_ref() + .expect("Passed in Clevis config"), None, ) .unwrap(); @@ -1646,10 +1601,15 @@ mod tests { PoolUuid::new_v4(), DevUuid::new_v4(), pool_name.clone(), - &EncryptionInfo::ClevisInfo(( - "sss".to_string(), - json!({"t": 1, "pins": {"tang": {"url": env::var("TANG_URL").expect("TANG_URL env var required")}, "tpm2": {}}}), - )), + InputEncryptionInfo::new_legacy( + None, + Some(( + "sss".to_string(), + json!({"t": 1, "pins": {"tang": {"url": env::var("TANG_URL").expect("TANG_URL env var required")}, "tpm2": {}}}), + )), + ) + .as_ref() + .expect("Passed in Clevis config"), None, ) .is_err()); @@ -1658,17 +1618,22 @@ mod tests { PoolUuid::new_v4(), DevUuid::new_v4(), pool_name, - &EncryptionInfo::ClevisInfo(( - "sss".to_string(), - json!({ - "t": 1, - "stratis:tang:trust_url": true, - "pins": { - "tang": {"url": env::var("TANG_URL").expect("TANG_URL env var required")}, - "tpm2": {} - } - }), - )), + InputEncryptionInfo::new_legacy( + None, + Some(( + "sss".to_string(), + json!({ + "t": 1, + "stratis:tang:trust_url": true, + "pins": { + "tang": {"url": env::var("TANG_URL").expect("TANG_URL env var required")}, + "tpm2": {} + } + }), + )), + ) + .as_ref() + .expect("Passed in Clevis config"), None, ) .unwrap(); @@ -1699,7 +1664,9 @@ mod tests { PoolUuid::new_v4(), DevUuid::new_v4(), Name::new("pool_name".to_string()), - &EncryptionInfo::KeyDesc(key_desc.clone()), + InputEncryptionInfo::new_legacy(Some(key_desc.clone()), None) + .as_ref() + .expect("Passed in key description"), None, ) .unwrap(); @@ -1709,9 +1676,13 @@ mod tests { fn unlock(paths: &[&Path], key: &SizedKeyMemory) { let path = paths[0]; - CryptHandle::setup(path, Some(UnlockMethod::Any), Some(key)) - .unwrap() - .unwrap(); + CryptHandle::setup( + path, + TokenUnlockMethod::from(Some(UnlockMethod::Any)), + Some(key), + ) + .unwrap() + .unwrap(); } crypt::insert_and_remove_key(paths, init, unlock); diff --git a/src/engine/strat_engine/crypt/handle/v2.rs b/src/engine/strat_engine/crypt/handle/v2.rs index d514de406e..93af52cecd 100644 --- a/src/engine/strat_engine/crypt/handle/v2.rs +++ b/src/engine/strat_engine/crypt/handle/v2.rs @@ -30,17 +30,16 @@ 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, - LUKS2_TOKEN_ID, STRATIS_MEK_SIZE, + DEFAULT_CRYPT_KEYSLOTS_SIZE, DEFAULT_CRYPT_METADATA_SIZE, STRATIS_MEK_SIZE, }, shared::{ acquire_crypt_device, activate, activate_by_token, add_keyring_keyslot, - clevis_info_from_metadata, device_from_physical_path, ensure_wiped, - get_keyslot_number, interpret_clevis_config, key_desc_from_metadata, - luks2_token_type_is_valid, wipe_fallback, + clevis_decrypt, clevis_info_from_json, device_from_physical_path, + encryption_info_from_metadata, ensure_wiped, get_keyslot_number, + interpret_clevis_config, read_key, wipe_fallback, }, }, device::blkdev_size, @@ -48,9 +47,9 @@ use crate::{ names::format_crypt_backstore_name, }, types::{ - DevicePath, EncryptionInfo, KeyDescription, PoolUuid, SizedKeyMemory, UnlockMethod, + DevicePath, EncryptionInfo, InputEncryptionInfo, KeyDescription, PoolUuid, + SizedKeyMemory, TokenUnlockMethod, UnlockMechanism, }, - ClevisInfo, }, stratis::{StratisError, StratisResult}, }; @@ -64,37 +63,7 @@ pub fn load_crypt_metadata( let physical = DevicePath::new(physical_path)?; let activation_name = format_crypt_backstore_name(&pool_uuid); - let key_description = key_desc_from_metadata(device); - let key_description = match key_description - .as_ref() - .map(|kd| KeyDescription::from_system_key_desc(kd)) - { - Some(Some(Ok(description))) => Some(description), - Some(Some(Err(e))) => { - return Err(StratisError::Msg(format!( - "key description {} found on devnode {} is not a valid Stratis key description: {}", - key_description.expect("key_desc_from_metadata determined to be Some(_) above"), - physical_path.display(), - e, - ))); - } - Some(None) => { - warn!("Key description stored on device {} does not appear to be a Stratis key description; ignoring", physical_path.display()); - None - } - None => None, - }; - let clevis_info = clevis_info_from_metadata(device)?; - - let encryption_info = - if let Some(info) = EncryptionInfo::from_options((key_description, clevis_info)) { - info - } else { - return Err(StratisError::Msg(format!( - "No valid encryption method that can be used to unlock device {} found", - physical_path.display() - ))); - }; + let encryption_info = encryption_info_from_metadata(device)?; let path = vec![DEVICEMAPPER_PATH, &activation_name.to_string()] .into_iter() @@ -127,19 +96,13 @@ pub struct CryptMetadata { /// with encryption by Stratis. This requires that the device is a LUKS2 encrypted device. fn is_encrypted_stratis_device(device: &mut CryptDevice) -> bool { fn device_operations(device: &mut CryptDevice) -> StratisResult<()> { - let luks_token = device.token_handle().json_get(LUKS2_TOKEN_ID).ok(); - let clevis_token = device.token_handle().json_get(CLEVIS_LUKS_TOKEN_ID).ok(); - if luks_token.is_none() && clevis_token.is_none() { + let all_tokens = encryption_info_from_metadata(device)?.all_infos().count(); + if all_tokens < 1 { return Err(StratisError::Msg( "Device appears to be missing some of the required Stratis LUKS2 tokens" .to_string(), )); } - if let Some(ref lt) = luks_token { - if !luks2_token_type_is_valid(lt) { - return Err(StratisError::Msg("LUKS2 token is invalid".to_string())); - } - } Ok(()) } @@ -178,7 +141,7 @@ fn setup_crypt_handle( device: &mut CryptDevice, physical_path: &Path, pool_uuid: PoolUuid, - unlock_method: UnlockMethod, + token_slot: TokenUnlockMethod, passphrase: Option<&SizedKeyMemory>, ) -> StratisResult> { let metadata = match load_crypt_metadata(device, physical_path, pool_uuid)? { @@ -193,8 +156,8 @@ fn setup_crypt_handle( { activate( device, - metadata.encryption_info.key_description(), - unlock_method, + &metadata.encryption_info, + token_slot.get_token_slot()?, passphrase, &metadata.activation_name, )? @@ -212,6 +175,61 @@ fn setup_crypt_handle( ))) } +/// Get one of the passphrases for the encrypted device. +fn get_passphrase( + device: &mut CryptDevice, + encryption_info: &EncryptionInfo, +) -> StratisResult<(c_uint, SizedKeyMemory)> { + for (ts, mech) in encryption_info.all_infos() { + match mech { + UnlockMechanism::KeyDesc(kd) => match read_key(kd) { + Ok(Some(pass)) => { + let keyslot = match get_keyslot_number(device, *ts) { + Ok(Some(ks)) => ks, + Ok(None) => { + warn!("Failed to find keyslot associated with the given token"); + continue; + } + Err(e) => { + warn!("Failure while finding keyslot associated with the given token: {e}"); + continue; + } + }; + return Ok((keyslot, pass)); + } + Ok(None) => { + info!("Key description was not in keyring; trying next unlock mechanism") + } + Err(e) => info!("Error searching keyring: {e}"), + }, + UnlockMechanism::ClevisInfo(_) => match clevis_decrypt(device, *ts) { + Ok(Some(pass)) => { + let keyslot = match get_keyslot_number(device, *ts) { + Ok(Some(ks)) => ks, + Ok(None) => { + warn!("Failed to find keyslot associated with the given token"); + continue; + } + Err(e) => { + warn!("Failure while finding keyslot associated with the given token: {e}"); + continue; + } + }; + return Ok((keyslot, pass)); + } + Ok(None) => { + info!("Failed to find the given token; trying next unlock method"); + } + Err(e) => info!("Error searching keyring: {e}"), + }, + } + } + + Err(StratisError::Msg( + "Unable to get passphrase for any available token slots".to_string(), + )) +} + /// Handle for performing all operations on an encrypted device. /// /// `Clone` is derived for this data structure because `CryptHandle` acquires @@ -253,7 +271,7 @@ impl CryptHandle { pub fn initialize( physical_path: &Path, pool_uuid: PoolUuid, - encryption_info: &EncryptionInfo, + encryption_info: &InputEncryptionInfo, sector_size: Option, ) -> StratisResult { let activation_name = format_crypt_backstore_name(&pool_uuid); @@ -280,21 +298,13 @@ impl CryptHandle { KeyslotsSize::try_from(convert_int!(*DEFAULT_CRYPT_KEYSLOTS_SIZE, u128, u64)?)?, )?; Self::initialize_with_err(&mut device, physical_path, pool_uuid, encryption_info, luks2_params.as_ref()) - .and_then(|path| clevis_info_from_metadata(&mut device).map(|ci| (path, ci))) - .and_then(|(_, clevis_info)| { - let encryption_info = - if let Some(info) = EncryptionInfo::from_options((encryption_info.key_description().cloned(), clevis_info)) { - info - } else { - return Err(StratisError::Msg(format!( - "No valid encryption method that can be used to unlock device {} found after initialization", - physical_path.display() - ))); - }; + .and_then(|_| { + let encryption_info = encryption_info_from_metadata(&mut device)?; let device_path = DevicePath::new(physical_path)?; - let devno = get_devno_from_path(&once(DEVICEMAPPER_PATH).chain(once(activation_name.to_string().as_str())).collect::())?; - let size = blkdev_size(&File::open(["/dev", "mapper", &activation_name.to_string()].iter().collect::())?)?.sectors(); + let activated_path = once(DEVICEMAPPER_PATH).chain(once(activation_name.to_string().as_str())).collect::(); + let devno = get_devno_from_path(&activated_path)?; + let size = blkdev_size(&File::open(&activated_path)?)?.sectors(); Ok(CryptHandle::new( device_path, pool_uuid, @@ -304,6 +314,10 @@ impl CryptHandle { )) }) .map_err(|e| { + if let Err(e) = device.context_handle().load::<()>(Some(EncryptionFormat::Luks2), None) { + warn!("Failed to reload state of encrypted device: {e}"); + return StratisError::from(e); + } if let Err(err) = Self::rollback(&mut device, physical_path, &activation_name) { @@ -316,99 +330,11 @@ impl CryptHandle { }) } - /// Initialize with a passphrase in the kernel keyring only. - fn initialize_with_keyring( - device: &mut CryptDevice, - key_description: &KeyDescription, - ) -> StratisResult<()> { - add_keyring_keyslot(device, key_description, None)?; - - Ok(()) - } - - /// Initialize with Clevis only. - fn initialize_with_clevis( - device: &mut CryptDevice, - physical_path: &Path, - (pin, json, yes): (&str, &Value, bool), - ) -> StratisResult<()> { - let (_, key_data) = thread_rng() - .sample_iter(Alphanumeric) - .take(MAX_STRATIS_PASS_SIZE) - .fold( - (0, SafeMemHandle::alloc(MAX_STRATIS_PASS_SIZE)?), - |(idx, mut mem), ch| { - mem.as_mut()[idx] = ch; - (idx + 1, mem) - }, - ); - - let key = SizedKeyMemory::new(key_data, MAX_STRATIS_PASS_SIZE); - let keyslot = log_on_failure!( - device - .keyslot_handle() - .add_by_key(None, None, key.as_ref(), CryptVolumeKey::empty(),), - "Failed to initialize keyslot with provided key in keyring" - ); - - clevis_luks_bind( - physical_path, - Either::Right(key), - CLEVIS_LUKS_TOKEN_ID, - pin, - json, - yes, - )?; - - // Need to reload device here to refresh the state of the device - // after being modified by Clevis. - if let Err(e) = device - .context_handle() - .load::<()>(Some(EncryptionFormat::Luks2), None) - { - return Err(wipe_fallback(physical_path, StratisError::from(e))); - } - - device.keyslot_handle().destroy(keyslot)?; - - Ok(()) - } - - /// Initialize with both a passphrase in the kernel keyring and Clevis. - fn initialize_with_both( - device: &mut CryptDevice, - physical_path: &Path, - key_description: &KeyDescription, - (pin, json, yes): (&str, &Value, bool), - ) -> StratisResult<()> { - Self::initialize_with_keyring(device, key_description)?; - - clevis_luks_bind( - physical_path, - Either::Left(LUKS2_TOKEN_ID), - CLEVIS_LUKS_TOKEN_ID, - pin, - json, - yes, - )?; - - // Need to reload device here to refresh the state of the device - // after being modified by Clevis. - if let Err(e) = device - .context_handle() - .load::<()>(Some(EncryptionFormat::Luks2), None) - { - return Err(wipe_fallback(physical_path, StratisError::from(e))); - } - - Ok(()) - } - fn initialize_with_err( device: &mut CryptDevice, physical_path: &Path, pool_uuid: PoolUuid, - encryption_info: &EncryptionInfo, + encryption_info: &InputEncryptionInfo, luks2_params: Option<&CryptParamsLuks2>, ) -> StratisResult<()> { let mut luks2_params_ref: Option> = @@ -426,28 +352,106 @@ impl CryptHandle { physical_path.display() ); - match encryption_info { - EncryptionInfo::Both(kd, (pin, config)) => { - let mut parsed_config = config.clone(); - let y = interpret_clevis_config(pin, &mut parsed_config)?; - Self::initialize_with_both(device, physical_path, kd, (pin, &parsed_config, y))? + let (key_descs, clevis_infos, key_descs_with_token_id, clevis_infos_with_token_id) = + encryption_info.into_parts()?; + + let (to_delete, clevis_bind_input) = match ( + key_descs.iter().next(), + key_descs_with_token_id.keys().next(), + ) { + (_, Some(t)) => (None, Either::Left(*t)), + (Some(kd), _) => { + let key = read_key(kd)?.ok_or_else(|| { + StratisError::Msg(format!( + "Passphrase for key description {} not found in keyring", + kd.as_application_str() + )) + })?; + (None, Either::Right(key)) } - EncryptionInfo::KeyDesc(kd) => Self::initialize_with_keyring(device, kd)?, - EncryptionInfo::ClevisInfo((pin, config)) => { - let mut parsed_config = config.clone(); - let y = interpret_clevis_config(pin, &mut parsed_config)?; - Self::initialize_with_clevis(device, physical_path, (pin, &parsed_config, y))? + (None, None) => { + let (_, key_data) = thread_rng() + .sample_iter(Alphanumeric) + .take(MAX_STRATIS_PASS_SIZE) + .fold( + (0, SafeMemHandle::alloc(MAX_STRATIS_PASS_SIZE)?), + |(idx, mut mem), ch| { + mem.as_mut()[idx] = ch; + (idx + 1, mem) + }, + ); + + let key = SizedKeyMemory::new(key_data, MAX_STRATIS_PASS_SIZE); + let keyslot_to_delete = log_on_failure!( + device.keyslot_handle().add_by_key( + None, + None, + key.as_ref(), + CryptVolumeKey::empty(), + ), + "Failed to initialize keyslot with provided key in keyring" + ); + + (Some(keyslot_to_delete), Either::Right(key)) } }; + for (token_slot, key_desc) in key_descs_with_token_id { + add_keyring_keyslot(device, Some(token_slot), key_desc, None)?; + } + let has_clevis_with_id = !clevis_infos_with_token_id.is_empty(); + for (token_slot, (pin, config)) in clevis_infos_with_token_id { + let mut config_to_use = config.to_owned(); + let yes = interpret_clevis_config(pin, &mut config_to_use)?; + clevis_luks_bind( + physical_path, + &clevis_bind_input, + Some(token_slot), + pin, + &config_to_use, + yes, + )?; + } + if has_clevis_with_id { + // Need to reload device here to refresh the state of the device + // after being modified by Clevis. + device + .context_handle() + .load::<()>(Some(EncryptionFormat::Luks2), None)?; + } + + for key_desc in key_descs { + add_keyring_keyslot(device, None, key_desc, None)?; + } + let has_clevis = !clevis_infos.is_empty(); + for (pin, config) in clevis_infos { + let mut config_to_use = config.to_owned(); + let yes = interpret_clevis_config(pin, &mut config_to_use)?; + clevis_luks_bind( + physical_path, + &clevis_bind_input, + None, + pin, + &config_to_use, + yes, + )?; + } + if has_clevis { + // Need to reload device here to refresh the state of the device + // after being modified by Clevis. + device + .context_handle() + .load::<()>(Some(EncryptionFormat::Luks2), None)?; + } + + if let Some(t) = to_delete { + device.keyslot_handle().destroy(t)?; + } + + let encryption_info = encryption_info_from_metadata(device)?; + let activation_name = format_crypt_backstore_name(&pool_uuid); - activate( - device, - encryption_info.key_description(), - UnlockMethod::Any, - None, - &activation_name, - ) + activate(device, &encryption_info, None, None, &activation_name) } pub fn rollback( @@ -455,7 +459,12 @@ impl CryptHandle { physical_path: &Path, name: &DmName, ) -> StratisResult<()> { - ensure_wiped(device, physical_path, name) + if let Err(e) = ensure_wiped(device, physical_path, name) { + warn!("Wipe failed: {e}; falling back on manual wipe"); + Err(wipe_fallback(physical_path, e)) + } else { + Ok(()) + } } /// Acquire the crypt device handle for the physical path in this `CryptHandle`. @@ -478,12 +487,12 @@ impl CryptHandle { pub fn setup( physical_path: &Path, pool_uuid: PoolUuid, - unlock_method: UnlockMethod, + token_slot: TokenUnlockMethod, passphrase: Option<&SizedKeyMemory>, ) -> StratisResult> { match setup_crypt_device(physical_path)? { Some(ref mut device) => { - setup_crypt_handle(device, physical_path, pool_uuid, unlock_method, passphrase) + setup_crypt_handle(device, physical_path, pool_uuid, token_slot, passphrase) } None => Ok(None), } @@ -538,50 +547,58 @@ impl CryptHandle { get_keyslot_number(&mut self.acquire_crypt_device()?, token_id) } - /// Get info for the clevis binding. - pub fn clevis_info(&self) -> StratisResult> { - clevis_info_from_metadata(&mut self.acquire_crypt_device()?) - } - /// Bind the given device using clevis. - pub fn clevis_bind(&mut self, pin: &str, json: &Value) -> StratisResult<()> { + pub fn bind_clevis( + &mut self, + token_slot: Option, + pin: &str, + json: &Value, + ) -> StratisResult<()> { let mut json_owned = json.clone(); let yes = interpret_clevis_config(pin, &mut json_owned)?; + let t = match token_slot { + Some(t) => t, + None => self.metadata.encryption_info.free_token_slot(), + }; + + let (_, key) = get_passphrase( + &mut acquire_crypt_device(self.luks2_device_path())?, + self.encryption_info(), + )?; + clevis_luks_bind( self.luks2_device_path(), - Either::Left(LUKS2_TOKEN_ID), - CLEVIS_LUKS_TOKEN_ID, + &Either::Right(key), + Some(t), pin, &json_owned, yes, )?; - self.metadata.encryption_info = - self.metadata - .encryption_info - .clone() - .set_clevis_info(self.clevis_info()?.ok_or_else(|| { - StratisError::Msg( - "Clevis reported successfully binding to device but no metadata was found" - .to_string(), - ) - })?); + self.metadata.encryption_info.add_info( + t, + UnlockMechanism::ClevisInfo((pin.to_owned(), json.to_owned())), + )?; + Ok(()) } /// Unbind the given device using clevis. - pub fn clevis_unbind(&mut self) -> StratisResult<()> { - if self.metadata.encryption_info.key_description().is_none() { + /// + /// Precondition: The caller should check that the slot is not empty and that a Clevis token + /// has been passed as the token slot input. + pub fn unbind_clevis(&mut self, token_slot: u32) -> StratisResult<()> { + if self.metadata.encryption_info.all_token_slots().count() < 2 { return Err(StratisError::Msg( - "No kernel keyring binding found; removing the Clevis binding \ + "There is one remaining unlock method; removing the Clevis binding \ would remove the ability to open this device; aborting" .to_string(), )); } - let keyslot = self.keyslot(CLEVIS_LUKS_TOKEN_ID)?.ok_or_else(|| { + let keyslot = self.keyslot(token_slot)?.ok_or_else(|| { StratisError::Msg(format!( - "Token slot {CLEVIS_LUKS_TOKEN_ID} appears to be empty; could not determine keyslots" + "Token slot {token_slot} appears to be empty; could not determine keyslots" )) })?; log_on_failure!( @@ -589,7 +606,7 @@ impl CryptHandle { "Failed to unbind device {} from Clevis", self.luks2_device_path().display() ); - self.metadata.encryption_info = self.metadata.encryption_info.clone().unset_clevis_info(); + self.metadata.encryption_info.remove(token_slot); Ok(()) } @@ -598,15 +615,9 @@ impl CryptHandle { /// This method needs to re-read the cached Clevis information because /// the config may change specifically in the case where a new thumbprint /// is provided if Tang keys are rotated. - pub fn rebind_clevis(&mut self) -> StratisResult<()> { - if self.metadata.encryption_info.clevis_info().is_none() { - return Err(StratisError::Msg( - "No Clevis binding found; cannot regenerate the Clevis binding if the device does not already have a Clevis binding".to_string(), - )); - } - + pub fn rebind_clevis(&mut self, token_slot: u32) -> StratisResult<()> { let mut device = self.acquire_crypt_device()?; - let keyslot = get_keyslot_number(&mut device, CLEVIS_LUKS_TOKEN_ID)?.ok_or_else(|| { + let keyslot = get_keyslot_number(&mut device, token_slot)?.ok_or_else(|| { StratisError::Msg("Clevis binding found but no keyslot was associated".to_string()) })?; @@ -622,109 +633,91 @@ impl CryptHandle { )); } - let (pin, config) = clevis_info_from_metadata(&mut device)?.ok_or_else(|| { - StratisError::Msg(format!( - "Did not find Clevis metadata on device {}", - self.luks2_device_path().display() - )) - })?; - self.metadata.encryption_info = self - .metadata + let token = device.token_handle().json_get(token_slot)?; + let (pin, config) = clevis_info_from_json(&token)?; + self.metadata .encryption_info - .clone() - .set_clevis_info((pin, config)); + .set_info(token_slot, UnlockMechanism::ClevisInfo((pin, config)))?; Ok(()) } /// Add a keyring binding to the underlying LUKS2 volume. - pub fn bind_keyring(&mut self, key_desc: &KeyDescription) -> StratisResult<()> { + pub fn bind_keyring( + &mut self, + token_slot: Option, + key_desc: &KeyDescription, + ) -> StratisResult<()> { let mut device = self.acquire_crypt_device()?; - let key = Self::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 \ - passphrase to unlock the device" - .to_string(), - ) - })?; + let (_, key) = get_passphrase(&mut device, self.encryption_info())?; + + let t = match token_slot { + Some(t) => t, + None => self.metadata.encryption_info.free_token_slot(), + }; - add_keyring_keyslot(&mut device, key_desc, Some(Either::Left(key)))?; + add_keyring_keyslot(&mut device, Some(t), key_desc, Some(Either::Left(key)))?; - self.metadata.encryption_info = self - .metadata + self.metadata .encryption_info - .clone() - .set_key_desc(key_desc.clone()); + .add_info(t, UnlockMechanism::KeyDesc(key_desc.to_owned()))?; Ok(()) } - /// Add a keyring binding to the underlying LUKS2 volume. - pub fn unbind_keyring(&mut self) -> StratisResult<()> { - if self.metadata.encryption_info.clevis_info().is_none() { + /// Remove keyring binding from the underlying LUKS2 volume. + /// + /// Precondition: The caller should check that the slot is not empty and that a key description token + /// has been passed as the token slot input. + pub fn unbind_keyring(&mut self, token_slot: u32) -> StratisResult<()> { + if self.metadata.encryption_info.all_token_slots().count() < 2 { return Err(StratisError::Msg( - "No Clevis binding was found; removing the keyring binding would \ - remove the ability to open this device; aborting" + "There is one remaining unlock method; removing the key description binding \ + would remove the ability to open this device; aborting" .to_string(), )); } let mut device = self.acquire_crypt_device()?; - let keyslot = get_keyslot_number(&mut device, LUKS2_TOKEN_ID)? + let keyslot = get_keyslot_number(&mut device, token_slot)? .ok_or_else(|| StratisError::Msg("No LUKS2 keyring token was found".to_string()))?; - log_on_failure!( - device.keyslot_handle().destroy(keyslot), - "Failed partway through the kernel keyring unbinding operation \ - which cannot be rolled back; manual intervention may be required" - ); + device.keyslot_handle().destroy(keyslot)?; device .token_handle() - .json_set(TokenInput::RemoveToken(LUKS2_TOKEN_ID))?; - - self.metadata.encryption_info = self.metadata.encryption_info.clone().unset_key_desc(); + .json_set(TokenInput::RemoveToken(token_slot))?; + self.metadata.encryption_info.remove(token_slot); Ok(()) } /// Change the key description and passphrase that a device is bound to - pub fn rebind_keyring(&mut self, new_key_desc: &KeyDescription) -> StratisResult<()> { + pub fn rebind_keyring( + &mut self, + token_slot: u32, + new_key_desc: &KeyDescription, + ) -> StratisResult<()> { let mut device = self.acquire_crypt_device()?; let old_key_description = self.metadata.encryption_info - .key_description() + .get_info(token_slot) .ok_or_else(|| { StratisError::Msg("Cannot change passphrase because this device is not bound to a passphrase in the kernel keyring".to_string()) + })? + .key_desc() + .ok_or_else(|| { + StratisError::Msg(format!("Cannot rebind to new key description; token slot {token_slot} is already bound to a Clevis token")) })?; add_keyring_keyslot( &mut device, + Some(token_slot), new_key_desc, Some(Either::Right(old_key_description)), )?; - self.metadata.encryption_info = self - .metadata - .encryption_info - .clone() - .set_key_desc(new_key_desc.clone()); + self.metadata.encryption_info.set_info( + token_slot, + UnlockMechanism::KeyDesc(new_key_desc.to_owned()), + )?; 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), - }; - 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. #[cfg(test)] pub fn deactivate(&self) -> StratisResult<()> { @@ -789,15 +782,14 @@ mod tests { strat_engine::{ crypt::{ consts::{ - CLEVIS_LUKS_TOKEN_ID, DEFAULT_CRYPT_KEYSLOTS_SIZE, DEFAULT_CRYPT_METADATA_SIZE, - LUKS2_TOKEN_ID, STRATIS_MEK_SIZE, + DEFAULT_CRYPT_KEYSLOTS_SIZE, DEFAULT_CRYPT_METADATA_SIZE, STRATIS_MEK_SIZE, }, shared::acquire_crypt_device, }, ns::{unshare_mount_namespace, MemoryFilesystem}, tests::{crypt, loopbacked, real}, }, - types::{EncryptionInfo, KeyDescription, PoolUuid, UnlockMethod}, + types::{KeyDescription, PoolUuid}, }; use super::*; @@ -814,12 +806,10 @@ mod tests { let pool_uuid = PoolUuid::new_v4(); - let result = CryptHandle::initialize( - path, - pool_uuid, - &EncryptionInfo::KeyDesc(key_description), - None, - ); + let ei = InputEncryptionInfo::new(vec![(None, key_description)], vec![]) + .expect("Empty data structure") + .expect("Passed in key description"); + let result = CryptHandle::initialize(path, pool_uuid, &ei, None); // Initialization cannot occur with a non-existent key assert!(result.is_err()); @@ -868,13 +858,10 @@ mod tests { let pool_uuid = PoolUuid::new_v4(); - let handle = CryptHandle::initialize( - path, - pool_uuid, - &EncryptionInfo::KeyDesc(key_desc.clone()), - None, - ) - .unwrap(); + let ei = InputEncryptionInfo::new(vec![(Some(0), key_desc.clone())], vec![]) + .expect("Empty data structure") + .expect("Passed in key description"); + let handle = CryptHandle::initialize(path, pool_uuid, &ei, None).unwrap(); let logical_path = handle.activated_device_path(); const WINDOW_SIZE: usize = 1024 * 1024; @@ -953,7 +940,7 @@ mod tests { handle.deactivate().unwrap(); - let handle = CryptHandle::setup(path, pool_uuid, UnlockMethod::Keyring, None) + let handle = CryptHandle::setup(path, pool_uuid, TokenUnlockMethod::Token(0), None) .unwrap() .unwrap_or_else(|| { panic!( @@ -1008,13 +995,10 @@ mod tests { fn test_set_sector_size(paths: &[&Path], key_description: &KeyDescription) { let pool_uuid = PoolUuid::new_v4(); - CryptHandle::initialize( - paths[0], - pool_uuid, - &EncryptionInfo::KeyDesc(key_description.clone()), - Some(4096u32), - ) - .unwrap(); + let ei = InputEncryptionInfo::new(vec![(Some(0), key_description.clone())], vec![]) + .expect("Empty data structure") + .expect("Passed in key description"); + CryptHandle::initialize(paths[0], pool_uuid, &ei, Some(4096u32)).unwrap(); } crypt::insert_and_cleanup_key(paths, test_set_sector_size); @@ -1026,31 +1010,28 @@ mod tests { fn test_both_initialize(paths: &[&Path]) { fn both_initialize(paths: &[&Path], key_desc: &KeyDescription, pool_uuid: PoolUuid) { let path = paths.first().copied().expect("Expected exactly one path"); - let handle = CryptHandle::initialize( - path, - pool_uuid, - &EncryptionInfo::Both( - key_desc.clone(), + let ei = InputEncryptionInfo::new(vec![(Some(0), key_desc.clone())], vec![ + ( + Some(1), ( "tang".to_string(), json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), ), - ), - None, - ).unwrap(); + ) + ]) + .expect("Empty data structure") + .expect("Passed in key description and Clevis info"); + let handle = CryptHandle::initialize(path, pool_uuid, &ei, None).unwrap(); let mut device = acquire_crypt_device(handle.luks2_device_path()).unwrap(); - device.token_handle().json_get(LUKS2_TOKEN_ID).unwrap(); - device - .token_handle() - .json_get(CLEVIS_LUKS_TOKEN_ID) - .unwrap(); + device.token_handle().json_get(0).unwrap(); + device.token_handle().json_get(1).unwrap(); handle.deactivate().unwrap(); } fn unlock_clevis(paths: &[&Path], pool_uuid: PoolUuid) { let path = paths.first().copied().expect("Expected exactly one path"); - CryptHandle::setup(path, pool_uuid, UnlockMethod::Clevis, None) + CryptHandle::setup(path, pool_uuid, TokenUnlockMethod::Token(1), None) .unwrap() .unwrap(); } @@ -1102,20 +1083,22 @@ mod tests { let _memfs = MemoryFilesystem::new().unwrap(); let path = paths[0]; - let handle = CryptHandle::initialize( - path, - PoolUuid::new_v4(), - &EncryptionInfo::ClevisInfo(( - "tang".to_string(), - json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), - )), - None, + let ei = InputEncryptionInfo::new( + vec![], + vec![ + (Some(0), ( + "tang".to_string(), + json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), + )) + ] ) - .unwrap(); + .expect("Empty token slot").expect("Input Clevis info"); + + let handle = CryptHandle::initialize(path, PoolUuid::new_v4(), &ei, None).unwrap(); let mut device = acquire_crypt_device(handle.luks2_device_path()).unwrap(); - assert!(device.token_handle().json_get(CLEVIS_LUKS_TOKEN_ID).is_ok()); - assert!(device.token_handle().json_get(LUKS2_TOKEN_ID).is_err()); + assert!(device.token_handle().json_get(0).is_ok()); + assert!(device.token_handle().json_get(1).is_err()); } #[test] @@ -1155,29 +1138,38 @@ mod tests { fn test_clevis_tang_configs(paths: &[&Path]) { let path = paths[0]; - assert!(CryptHandle::initialize( - path, - PoolUuid::new_v4(), - &EncryptionInfo::ClevisInfo(( - "tang".to_string(), - json!({"url": env::var("TANG_URL").expect("TANG_URL env var required")}), - )), - None, + let ei = InputEncryptionInfo::new( + vec![], + vec![( + Some(0), + ( + "tang".to_string(), + json!({"url": env::var("TANG_URL").expect("TANG_URL env var required")}), + ), + )], ) - .is_err()); - CryptHandle::initialize( - path, - PoolUuid::new_v4(), - &EncryptionInfo::ClevisInfo(( - "tang".to_string(), - json!({ - "stratis:tang:trust_url": true, - "url": env::var("TANG_URL").expect("TANG_URL env var required"), - }), - )), - None, + .expect("Empty token slot") + .expect("Passed in Clevis info"); + + assert!(CryptHandle::initialize(path, PoolUuid::new_v4(), &ei, None,).is_err()); + + let ei = InputEncryptionInfo::new( + vec![], + vec![( + Some(0), + ( + "tang".to_string(), + json!({ + "stratis:tang:trust_url": true, + "url": env::var("TANG_URL").expect("TANG_URL env var required"), + }), + ), + )], ) - .unwrap(); + .expect("Empty token slot") + .expect("Passed Clevis info"); + + CryptHandle::initialize(path, PoolUuid::new_v4(), &ei, None).unwrap(); } #[test] @@ -1199,33 +1191,44 @@ mod tests { fn test_clevis_sss_configs(paths: &[&Path]) { let path = paths[0]; - assert!(CryptHandle::initialize( - path, - PoolUuid::new_v4(), - &EncryptionInfo::ClevisInfo(( - "sss".to_string(), - json!({"t": 1, "pins": {"tang": {"url": env::var("TANG_URL").expect("TANG_URL env var required")}, "tpm2": {}}}), - )), - None, + let ei = InputEncryptionInfo::new( + vec![], + vec![ + ( + Some(0), + ( + "sss".to_string(), + json!({"t": 1, "pins": {"tang": {"url": env::var("TANG_URL").expect("TANG_URL env var required")}, "tpm2": {}}}), + ), + ) + ] ) - .is_err()); - CryptHandle::initialize( - path, - PoolUuid::new_v4(), - &EncryptionInfo::ClevisInfo(( - "sss".to_string(), - json!({ - "t": 1, - "stratis:tang:trust_url": true, - "pins": { - "tang": {"url": env::var("TANG_URL").expect("TANG_URL env var required")}, - "tpm2": {} - } - }), - )), - None, + .expect("Empty token slot").expect("Passed in Clevis info"); + + assert!(CryptHandle::initialize(path, PoolUuid::new_v4(), &ei, None,).is_err()); + + let ei = InputEncryptionInfo::new( + vec![], + vec![ + ( + Some(0), + ( + "sss".to_string(), + json!({ + "t": 1, + "stratis:tang:trust_url": true, + "pins": { + "tang": {"url": env::var("TANG_URL").expect("TANG_URL env var required")}, + "tpm2": {} + } + }), + ), + ) + ] ) - .unwrap(); + .expect("Empty token slot").expect("Input Clevis info"); + + CryptHandle::initialize(path, PoolUuid::new_v4(), &ei, None).unwrap(); } #[test] @@ -1248,20 +1251,18 @@ mod tests { fn init(paths: &[&Path], pool_uuid: PoolUuid, key_desc: &KeyDescription) { let path = paths[0]; - let handle = CryptHandle::initialize( - path, - pool_uuid, - &EncryptionInfo::KeyDesc(key_desc.clone()), - None, - ) - .unwrap(); + let ei = InputEncryptionInfo::new(vec![(Some(0), key_desc.clone())], vec![]) + .expect("Empty data structure") + .expect("Input key description"); + + let handle = CryptHandle::initialize(path, pool_uuid, &ei, None).unwrap(); handle.deactivate().unwrap(); } fn unlock(paths: &[&Path], pool_uuid: PoolUuid, key: &SizedKeyMemory) { let path = paths[0]; - CryptHandle::setup(path, pool_uuid, UnlockMethod::Any, Some(key)) + CryptHandle::setup(path, pool_uuid, TokenUnlockMethod::Any, Some(key)) .unwrap() .unwrap(); } diff --git a/src/engine/strat_engine/crypt/mod.rs b/src/engine/strat_engine/crypt/mod.rs index cf08bb5752..9f8937b0b3 100644 --- a/src/engine/strat_engine/crypt/mod.rs +++ b/src/engine/strat_engine/crypt/mod.rs @@ -10,7 +10,7 @@ pub mod handle; mod shared; pub use self::{ - consts::CLEVIS_TANG_TRUST_URL, + consts::{CLEVIS_LUKS_TOKEN_ID, CLEVIS_TANG_TRUST_URL, LUKS2_TOKEN_ID}, shared::{ back_up_luks_header, crypt_metadata_size, register_clevis_token, restore_luks_header, set_up_crypt_logging, diff --git a/src/engine/strat_engine/crypt/shared.rs b/src/engine/strat_engine/crypt/shared.rs index ef65070f2f..d3d8e803ec 100644 --- a/src/engine/strat_engine/crypt/shared.rs +++ b/src/engine/strat_engine/crypt/shared.rs @@ -32,17 +32,18 @@ use libcryptsetup_rs::{ use crate::{ engine::{ strat_engine::{ - cmd::clevis_decrypt, + cmd, crypt::consts::{ - CLEVIS_LUKS_TOKEN_ID, CLEVIS_RECURSION_LIMIT, CLEVIS_TANG_TRUST_URL, - CLEVIS_TOKEN_NAME, DEFAULT_CRYPT_KEYSLOTS_SIZE, DEFAULT_CRYPT_METADATA_SIZE, + CLEVIS_RECURSION_LIMIT, CLEVIS_TANG_TRUST_URL, CLEVIS_TOKEN_NAME, + CLEVIS_TOKEN_TYPE, DEFAULT_CRYPT_KEYSLOTS_SIZE, DEFAULT_CRYPT_METADATA_SIZE, LUKS2_SECTOR_SIZE, LUKS2_TOKEN_ID, LUKS2_TOKEN_TYPE, TOKEN_KEYSLOTS_KEY, TOKEN_TYPE_KEY, }, dm::get_dm, keys, }, - types::{KeyDescription, SizedKeyMemory, UnlockMethod}, + types::{KeyDescription, SizedKeyMemory, UnlockMechanism}, + EncryptionInfo, }, stratis::{StratisError, StratisResult}, }; @@ -100,6 +101,7 @@ fn key_desc_to_passphrase(key_description: &KeyDescription) -> StratisResult, key_description: &KeyDescription, pass: Option>, ) -> StratisResult<()> { @@ -142,16 +144,16 @@ pub fn add_keyring_keyslot( } }; - log_on_failure!( + let new_token_slot = log_on_failure!( device .token_handle() - .luks2_keyring_set(Some(LUKS2_TOKEN_ID), &key_description.to_system_string()), + .luks2_keyring_set(token_slot, &key_description.to_system_string()), "Failed to initialize the LUKS2 token for driving keyring activation operations" ); log_on_failure!( device .token_handle() - .assign_keyslot(LUKS2_TOKEN_ID, Some(keyslot)), + .assign_keyslot(new_token_slot, Some(keyslot)), "Failed to assign the LUKS2 keyring token to the Stratis keyslot" ); @@ -178,33 +180,35 @@ pub fn device_from_physical_path(physical_path: &Path) -> StratisResult StratisResult> { - let json = match device.token_handle().json_get(CLEVIS_LUKS_TOKEN_ID).ok() { - Some(j) => j, - None => return Ok(None), - }; - let json_b64 = match json +/// * Ok(_) if a key description token was detected +/// * Err(_) if an error occurred or the token was marked as a key description token but is malformed in some way +fn key_description_from_json(json: &Value) -> StratisResult> { + json.get("key_description") + .and_then(|val| val.as_str()) + .ok_or_else(|| StratisError::Msg("Found malformed key description token".to_string())) + .and_then(|s| KeyDescription::from_system_key_desc(s).transpose()) +} + +/// Get the Clevis binding information from a JSON token value +/// +/// This method returns: +/// * Ok(_) if a Clevis token was detected +/// * Err(_) if an error occurred or the token was marked as a Clevis token but is malformed in some way +pub fn clevis_info_from_json(json: &Value) -> StratisResult<(String, Value)> { + let json_b64 = json .get("jwe") .and_then(|map| map.get("protected")) .and_then(|string| string.as_str()) - { - Some(s) => s.to_owned(), - None => return Ok(None), - }; + .map(|s| s.to_owned()) + .ok_or_else(|| StratisError::Msg("Found malformed Clevis token".to_string()))?; let json_bytes = BASE64URL_NOPAD.decode(json_b64.as_bytes())?; let subjson: Value = serde_json::from_slice(json_bytes.as_slice())?; - pin_dispatch(&subjson, CLEVIS_RECURSION_LIMIT).map(Some) + pin_dispatch(&subjson, CLEVIS_RECURSION_LIMIT) } /// Returns true if the Tang config has a thumbprint or an advertisement @@ -470,28 +474,21 @@ fn device_is_active(device: Option<&mut CryptDevice>, device_name: &DmName) -> S } } -/// Activate encrypted Stratis device using the name stored in the -/// Stratis token. +/// Activate encrypted Stratis device. pub fn activate( device: &mut CryptDevice, - key_desc: Option<&KeyDescription>, - unlock_method: UnlockMethod, + ei: &EncryptionInfo, + unlock_method: Option, passphrase: Option<&SizedKeyMemory>, name: &DmName, ) -> StratisResult<()> { if let Some(p) = passphrase { let key_slot = - if unlock_method == UnlockMethod::Keyring { - Some(get_keyslot_number(device, LUKS2_TOKEN_ID)?.ok_or_else(|| { - StratisError::Msg("LUKS keyring keyslot not found".to_string()) - })?) - } else if unlock_method == UnlockMethod::Clevis { - Some( - get_keyslot_number(device, CLEVIS_LUKS_TOKEN_ID)? - .ok_or_else(|| StratisError::Msg("Clevis keyslot not found".to_string()))?, - ) - } else { - None + match unlock_method { + Some(t) => Some(get_keyslot_number(device, t)?.ok_or_else(|| { + StratisError::Msg("Keyslot for token {t} not found".to_string()) + })?), + None => None, }; log_on_failure!( device.activate_handle().activate_by_passphrase( @@ -504,38 +501,41 @@ pub fn activate( name ); } else { - if let (Some(kd), UnlockMethod::Keyring | UnlockMethod::Any) = (key_desc, unlock_method) { - let key_description_missing = keys::search_key_persistent(kd) - .map_err(|_| { - StratisError::Msg(format!( - "Searching the persistent keyring for the key description {} failed.", - kd.as_application_str(), - )) - })? - .is_none(); - if key_description_missing { - warn!( - "Key description {} was not found in the keyring", - kd.as_application_str() - ); + match unlock_method { + Some(t) => { + if let Some(kd) = ei + .get_info(t) + .ok_or_else(|| StratisError::Msg(format!("Token slot {t} empty")))? + .key_desc() + { + let key_description_missing = keys::search_key_persistent(kd) + .map_err(|_| { + StratisError::Msg(format!( + "Searching the persistent keyring for the key description {} failed.", + kd.as_application_str(), + )) + })? + .is_none(); + if key_description_missing { + warn!( + "Key description {} was not found in the keyring", + kd.as_application_str() + ); + return Err(StratisError::Msg(format!( + "The key description \"{}\" is not currently set.", + kd.as_application_str(), + ))); + } + } } - if key_description_missing && unlock_method == UnlockMethod::Keyring { - return Err(StratisError::Msg(format!( - "The key description \"{}\" is not currently set.", - kd.as_application_str(), - ))); + None => { + keys::get_persistent_keyring()?; } - } + }; activate_by_token( device, Some(&name.to_string()), - if unlock_method == UnlockMethod::Keyring { - Some(LUKS2_TOKEN_ID) - } else if unlock_method == UnlockMethod::Clevis { - Some(CLEVIS_LUKS_TOKEN_ID) - } else { - None - }, + unlock_method, CryptActivate::empty(), )?; } @@ -764,10 +764,65 @@ pub fn read_key(key_description: &KeyDescription) -> StratisResult Option { - device.token_handle().luks2_keyring_get(LUKS2_TOKEN_ID).ok() +fn token_dispatch(json: &Value) -> StratisResult> { + match json.get("type").and_then(|val| val.as_str()) { + Some(CLEVIS_TOKEN_TYPE) => Ok(Some(UnlockMechanism::ClevisInfo(clevis_info_from_json( + json, + )?))), + Some(LUKS2_TOKEN_TYPE) => { + Ok(key_description_from_json(json)?.map(UnlockMechanism::KeyDesc)) + } + Some(ty) => { + info!("Token type {ty} found for the given token; ignoring",); + Ok(None) + } + None => { + warn!( + "No token type found for the given token: {}; this token may be malformed", + json.to_string() + ); + Ok(None) + } + } +} + +pub fn encryption_info_from_metadata(device: &mut CryptDevice) -> StratisResult { + let json = device.status_handle().dump_json()?; + let tokens = if let Value::Object(mut obj) = json { + obj.remove("tokens").ok_or_else(|| { + StratisError::Msg("Did not find a record of tokens in the LUKS2 metadata".to_string()) + })? + } else { + return Err(StratisError::Msg(format!( + "Found malformed JSON record of metadata: {json}" + ))); + }; + + if let Value::Object(obj) = tokens { + let encryption_info = obj.into_iter().try_fold( + EncryptionInfo::new(), + |mut enc_info, (token_slot, token)| { + let token_slot = token_slot + .parse::() + .map_err(|e| StratisError::Msg(e.to_string()))?; + if let Some(res) = token_dispatch(&token)? { + enc_info.add_info(token_slot, res)?; + } + Result::<_, StratisError>::Ok(enc_info) + }, + )?; + if encryption_info.is_empty() { + Err(StratisError::Msg( + "No valid unlock mechanism found for the encrypted device".to_string(), + )) + } else { + Ok(encryption_info) + } + } else { + Err(StratisError::Msg(format!( + "Found malformed JSON record of token metadata: {tokens}" + ))) + } } // Bytes occupied by crypt metadata @@ -799,7 +854,7 @@ fn open_safe(device: &mut CryptDevice, token: libc::c_int) -> StratisResult StratisResult> { + let mut token = match device.token_handle().json_get(token_slot).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 {token_slot} is occupied but does not appear to be a Clevis \ + token; aborting" + )) + })?; + cmd::clevis_decrypt(&jwe).map(Some) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/engine/strat_engine/engine.rs b/src/engine/strat_engine/engine.rs index 1de9e4184f..20dc3bfc0f 100644 --- a/src/engine/strat_engine/engine.rs +++ b/src/engine/strat_engine/engine.rs @@ -37,10 +37,10 @@ use crate::{ SomeLockWriteGuard, Table, }, types::{ - CreateAction, DeleteAction, DevUuid, EncryptionInfo, FilesystemUuid, IntegritySpec, - LockedPoolsInfo, PoolDiff, PoolIdentifier, RenameAction, ReportType, SetUnlockAction, - StartAction, StopAction, StoppedPoolsInfo, StratFilesystemDiff, UdevEngineEvent, - UnlockMethod, ValidatedIntegritySpec, + CreateAction, DeleteAction, DevUuid, FilesystemUuid, InputEncryptionInfo, + IntegritySpec, LockedPoolsInfo, PoolDiff, PoolIdentifier, RenameAction, ReportType, + SetUnlockAction, StartAction, StopAction, StoppedPoolsInfo, StratFilesystemDiff, + TokenUnlockMethod, UdevEngineEvent, UnlockMethod, ValidatedIntegritySpec, }, Engine, Name, Pool, PoolUuid, Report, }, @@ -104,7 +104,7 @@ impl StratEngine { &self, name: &str, blockdev_paths: &[&Path], - encryption_info: Option<&EncryptionInfo>, + encryption_info: Option<&InputEncryptionInfo>, ) -> StratisResult> { validate_name(name)?; let name = Name::new(name.to_owned()); @@ -494,7 +494,7 @@ impl Engine for StratEngine { &self, name: &str, blockdev_paths: &[&Path], - encryption_info: Option<&EncryptionInfo>, + encryption_info: Option<&InputEncryptionInfo>, integrity_spec: IntegritySpec, ) -> StratisResult> { validate_name(name)?; @@ -683,7 +683,7 @@ impl Engine for StratEngine { let (name, pool_uuid, pool, unlocked_uuids) = liminal.start_pool( &pools, PoolIdentifier::Uuid(pool_uuid), - Some(unlock_method), + TokenUnlockMethod::from(Some(unlock_method)), None, )?; pools.insert(name, pool_uuid, pool); @@ -788,16 +788,12 @@ impl Engine for StratEngine { async fn start_pool( &self, id: PoolIdentifier, - unlock_method: Option, + token_slot: TokenUnlockMethod, passphrase_fd: Option, ) -> StratisResult> { if let Some(lock) = self.pools.read(id.clone()).await { let (_, pool_uuid, pool) = lock.as_tuple(); - if pool.is_encrypted() && unlock_method.is_none() { - return Err(StratisError::Msg(format!( - "Pool with UUID {pool_uuid} is encrypted but no unlock method was provided" - ))); - } else if !pool.is_encrypted() && unlock_method.is_some() { + if !pool.is_encrypted() && token_slot.is_some() { return Err(StratisError::Msg(format!( "Pool with UUID {pool_uuid} is not encrypted but an unlock method was provided" ))); @@ -813,7 +809,7 @@ impl Engine for StratEngine { let mut liminal = self.liminal_devices.write().await; let pool_uuid = spawn_blocking!({ let (name, pool_uuid, pool, _) = - liminal.start_pool(&pools, id, unlock_method, passphrase_fd)?; + liminal.start_pool(&pools, id, token_slot, passphrase_fd)?; pools.insert(name, pool_uuid, pool); StratisResult::Ok(pool_uuid) })??; @@ -904,7 +900,7 @@ mod test { ns::unshare_mount_namespace, tests::{crypt, loopbacked, real, FailDevice}, }, - types::{ActionAvailability, EngineAction, KeyDescription}, + types::{ActionAvailability, EngineAction, KeyDescription, OptionalTokenSlotInput}, }; use super::*; @@ -1087,9 +1083,9 @@ mod test { data_paths: &[&Path], cache_paths: Option<&[&Path]>, fail_device: &FailDevice, - encryption_info: &EncryptionInfo, + encryption_info: &InputEncryptionInfo, operation: F, - unlock_method: UnlockMethod, + unlock_method: TokenUnlockMethod, ) where F: FnOnce(&mut AnyPool) + UnwindSafe, { @@ -1123,8 +1119,7 @@ mod test { test_async!(engine.stop_pool(PoolIdentifier::Uuid(uuid), true)).unwrap(); - test_async!(engine.start_pool(PoolIdentifier::Uuid(uuid), Some(unlock_method), None)) - .unwrap(); + test_async!(engine.start_pool(PoolIdentifier::Uuid(uuid), unlock_method, None)).unwrap(); test_async!(engine.destroy_pool(uuid)).unwrap(); cmd::udev_settle().unwrap(); engine.teardown().unwrap(); @@ -1151,17 +1146,23 @@ mod test { paths_with_fail_device.as_slice(), None, &fail_device, - &EncryptionInfo::ClevisInfo(( - "tang".to_string(), - json!({ - "url": tang_url, - "stratis:tang:trust_url": true - }), - )), + InputEncryptionInfo::new_legacy( + None, + Some(( + "tang".to_string(), + json!({ + "url": tang_url, + "stratis:tang:trust_url": true + }), + )), + ) + .as_ref() + .expect("Passed Clevis info"), |pool| { - pool.bind_keyring(key_desc).unwrap(); + pool.bind_keyring(OptionalTokenSlotInput::Legacy, key_desc) + .unwrap(); }, - UnlockMethod::Clevis, + TokenUnlockMethod::from(Some(UnlockMethod::Clevis)), ); } @@ -1204,14 +1205,16 @@ mod test { paths_with_fail_device.as_slice(), None, &fail_device, - &EncryptionInfo::KeyDesc(key_desc1.to_owned()), + InputEncryptionInfo::new_legacy(Some(key_desc1.to_owned()), None) + .as_ref() + .expect("Passed key description"), |pool| { - pool.rebind_keyring(key_desc2).unwrap(); + pool.rebind_keyring(None, key_desc2).unwrap(); // Change the key to ensure that the second key description // is not the one that causes it to unlock successfully. crypt::change_key(key_desc2); }, - UnlockMethod::Keyring, + TokenUnlockMethod::from(Some(UnlockMethod::Keyring)), ); } @@ -1255,20 +1258,22 @@ mod test { paths_with_fail_device.as_slice(), None, &fail_device, - &EncryptionInfo::Both( - key_desc.to_owned(), - ( + InputEncryptionInfo::new_legacy( + Some(key_desc.to_owned()), + Some(( "tang".to_string(), json!({ "url": tang_url, "stratis:tang:trust_url": true }), - ), - ), + )), + ) + .as_ref() + .expect("Passed both key description and CLevis Info"), |pool| { - pool.unbind_keyring().unwrap(); + pool.unbind_keyring(None).unwrap(); }, - UnlockMethod::Keyring, + TokenUnlockMethod::from(Some(UnlockMethod::Keyring)), ); } @@ -1312,9 +1317,12 @@ mod test { paths_with_fail_device.as_slice(), None, &fail_device, - &EncryptionInfo::KeyDesc(key_desc.to_owned()), + InputEncryptionInfo::new_legacy(Some(key_desc.to_owned()), None) + .as_ref() + .expect("Passed key description"), |pool| { pool.bind_clevis( + OptionalTokenSlotInput::Legacy, "tang", &json!({ "url": tang_url, @@ -1323,7 +1331,7 @@ mod test { ) .unwrap(); }, - UnlockMethod::Keyring, + TokenUnlockMethod::from(Some(UnlockMethod::Keyring)), ); } @@ -1363,17 +1371,22 @@ mod test { paths_with_fail_device.as_slice(), None, &fail_device, - &EncryptionInfo::ClevisInfo(( - "tang".to_string(), - json!({ - "url": tang_url, - "stratis:tang:trust_url": true - }), - )), + InputEncryptionInfo::new_legacy( + None, + Some(( + "tang".to_string(), + json!({ + "url": tang_url, + "stratis:tang:trust_url": true + }), + )), + ) + .as_ref() + .expect("Passed Clevis info"), |pool| { - pool.rebind_clevis().unwrap(); + pool.rebind_clevis(None).unwrap(); }, - UnlockMethod::Clevis, + TokenUnlockMethod::from(Some(UnlockMethod::Clevis)), ); } @@ -1414,20 +1427,22 @@ mod test { paths_with_fail_device.as_slice(), None, &fail_device, - &EncryptionInfo::Both( - key_desc.to_owned(), - ( + InputEncryptionInfo::new_legacy( + Some(key_desc.to_owned()), + Some(( "tang".to_string(), json!({ "url": tang_url, "stratis:tang:trust_url": true }), - ), - ), + )), + ) + .as_ref() + .expect("Passed key description and Clevis info"), |pool| { - pool.unbind_clevis().unwrap(); + pool.unbind_clevis(None).unwrap(); }, - UnlockMethod::Clevis, + TokenUnlockMethod::from(Some(UnlockMethod::Clevis)), ); } @@ -1469,14 +1484,16 @@ mod test { data, Some(cache), &fail_device, - &EncryptionInfo::KeyDesc(key_desc1.to_owned()), + InputEncryptionInfo::new_legacy(Some(key_desc1.to_owned()), None) + .as_ref() + .expect("Passed key description"), |pool| { - pool.rebind_keyring(key_desc2).unwrap(); + pool.rebind_keyring(None, key_desc2).unwrap(); // Change the key to ensure that the second key description // is not the one that causes it to unlock successfully. crypt::change_key(key_desc2); }, - UnlockMethod::Keyring, + TokenUnlockMethod::from(Some(UnlockMethod::Keyring)), ); } @@ -1522,11 +1539,13 @@ mod test { assert_eq!(test_async!(engine.stopped_pools()).stopped.len(), 1); assert_eq!(test_async!(engine.pools()).len(), 0); - assert!( - test_async!(engine.start_pool(PoolIdentifier::Uuid(uuid), None, None)) - .unwrap() - .is_changed() - ); + assert!(test_async!(engine.start_pool( + PoolIdentifier::Uuid(uuid), + TokenUnlockMethod::None, + None + )) + .unwrap() + .is_changed()); assert_eq!(test_async!(engine.stopped_pools()).stopped.len(), 0); assert_eq!(test_async!(engine.pools()).len(), 1); } diff --git a/src/engine/strat_engine/liminal/device_info.rs b/src/engine/strat_engine/liminal/device_info.rs index aeec10ac24..6da64a1031 100644 --- a/src/engine/strat_engine/liminal/device_info.rs +++ b/src/engine/strat_engine/liminal/device_info.rs @@ -103,28 +103,31 @@ impl Into for &LLuksInfo { /// Convert a reference to a HashMap to an owned HashMap with borrowed values. pub fn stratis_infos_ref( - infos: &HashMap, + infos: &HashMap>, ) -> HashMap { infos .iter() - .map(|(dev_uuid, info)| (*dev_uuid, info)) + .map(|(dev_uuid, info)| (*dev_uuid, info.as_ref())) .collect::>() } /// Split a `Vec` into a `Vec` of BDAs and a `Vec` of the remaining /// information contained by `Vec`. pub fn split_stratis_infos( - infos: HashMap, -) -> (HashMap, HashMap) { + infos: HashMap>, +) -> ( + HashMap>, + HashMap, +) { infos.into_iter().fold( (HashMap::new(), HashMap::new()), |(mut new_infos, mut bdas), (dev_uuid, info)| { new_infos.insert( dev_uuid, - LStratisDevInfo { + Box::new(LStratisDevInfo { dev_info: info.dev_info, luks: info.luks, - }, + }), ); bdas.insert(dev_uuid, info.bda); (new_infos, bdas) @@ -136,7 +139,7 @@ pub fn split_stratis_infos( /// /// Precondition: infos.keys() == bdas.keys() pub fn reconstruct_stratis_infos( - mut infos: HashMap, + mut infos: HashMap>, mut bdas: HashMap, ) -> DeviceSet { let uuids = infos.keys().copied().collect::>(); @@ -149,11 +152,11 @@ pub fn reconstruct_stratis_infos( let bda = bdas.remove(&uuid).expect("infos.keys() == bdas.keys()"); ( uuid, - LInfo::Stratis(LStratisInfo { + LInfo::Stratis(Box::new(LStratisInfo { dev_info: info.dev_info, luks: info.luks, bda, - }), + })), ) }) .collect::() @@ -287,7 +290,7 @@ impl LStratisInfo { #[derive(Debug, Eq, Hash, PartialEq)] pub enum LInfo { /// A Stratis device, which may be an encrypted device - Stratis(LStratisInfo), + Stratis(Box), /// A LUKS device Luks(LLuksInfo), } @@ -305,7 +308,7 @@ impl From for LInfo { fn from(info: DeviceInfo) -> LInfo { match info { DeviceInfo::Luks(info) => LInfo::Luks(info.into()), - DeviceInfo::Stratis(info) => LInfo::Stratis(info.into()), + DeviceInfo::Stratis(info) => LInfo::Stratis(Box::new(info.into())), } } } @@ -315,7 +318,7 @@ impl Into for &LInfo { // Precondition: (&LLuksInfo).into() pattern matches Value::Object() fn into(self) -> Value { match self { - LInfo::Stratis(info) => info.into(), + LInfo::Stratis(info) => info.as_ref().into(), LInfo::Luks(info) => info.into(), } } @@ -430,11 +433,11 @@ impl LInfo { match (info_1, info_2) { (LInfo::Luks(luks_info), DeviceInfo::Stratis(strat_info)) => { - Either::Left(LInfo::Stratis(LStratisInfo { + Either::Left(LInfo::Stratis(Box::new(LStratisInfo { dev_info: strat_info.dev_info, bda: strat_info.bda, luks: Some(luks_info), - })) + }))) } (LInfo::Stratis(strat_info), DeviceInfo::Luks(luks_info)) => { if let Some(luks) = strat_info.luks.as_ref() { @@ -442,11 +445,11 @@ impl LInfo { return Either::Right(LInfo::Stratis(strat_info)); } } - Either::Left(LInfo::Stratis(LStratisInfo { + Either::Left(LInfo::Stratis(Box::new(LStratisInfo { dev_info: strat_info.dev_info, bda: strat_info.bda, luks: Some(LLuksInfo::from(luks_info)), - })) + }))) } (LInfo::Luks(luks_info_1), DeviceInfo::Luks(luks_info_2)) => { if !luks_luks_compatible(&luks_info_1, &luks_info_2) { @@ -459,11 +462,11 @@ impl LInfo { if !stratis_stratis_compatible(&strat_info_1, &strat_info_2) { Either::Right(LInfo::Stratis(strat_info_1)) } else { - Either::Left(LInfo::Stratis(LStratisInfo { + Either::Left(LInfo::Stratis(Box::new(LStratisInfo { dev_info: strat_info_2.dev_info, bda: strat_info_2.bda, luks: strat_info_1.luks, - })) + }))) } } } @@ -571,7 +574,7 @@ impl DeviceSet { .iter() .map(|(dev_uuid, info)| match info { LInfo::Luks(_) => unreachable!("!self.some_closed() is satisfied"), - LInfo::Stratis(info) => (*dev_uuid, info), + LInfo::Stratis(info) => (*dev_uuid, info.as_ref()), }) .collect(), ) @@ -580,7 +583,7 @@ impl DeviceSet { /// Return a view of the DeviceSet as a set of wholly opened devices. /// Return Either::Right(_) if some of the devices are unopened. - pub fn into_opened_set(self) -> Either, Self> { + pub fn into_opened_set(self) -> Either>, Self> { if self.some_closed() { Either::Right(self) } else { diff --git a/src/engine/strat_engine/liminal/identify.rs b/src/engine/strat_engine/liminal/identify.rs index 864a959953..9981daee99 100644 --- a/src/engine/strat_engine/liminal/identify.rs +++ b/src/engine/strat_engine/liminal/identify.rs @@ -513,7 +513,7 @@ mod tests { tests::{crypt, loopbacked, real}, udev::block_device_apply, }, - types::{DevicePath, EncryptionInfo, KeyDescription}, + types::{DevicePath, KeyDescription}, }, stratis::StratisResult, }; @@ -591,6 +591,8 @@ mod tests { } mod v1 { + use crate::engine::InputEncryptionInfo; + use super::*; /// Test that an encrypted device initialized by stratisd is properly @@ -612,7 +614,7 @@ mod tests { pool_name, pool_uuid, MDADataSize::default(), - Some(&EncryptionInfo::KeyDesc(key_description.clone())), + InputEncryptionInfo::new_legacy(Some(key_description.clone()), None).as_ref(), None, ) .unwrap(); @@ -641,10 +643,17 @@ mod tests { ); } - if info.encryption_info.key_description() != Some(key_description) { + if info + .encryption_info + .single_key_description() + .map(|(_, kd)| kd) + != Some(key_description) + { panic!( "Discovered key description {:?} != expected key description {:?}", - info.encryption_info.key_description(), + info.encryption_info + .single_key_description() + .map(|(_, kd)| kd), Some(key_description.as_application_str()) ); } diff --git a/src/engine/strat_engine/liminal/liminal.rs b/src/engine/strat_engine/liminal/liminal.rs index 6adf878d8d..18cb9d35d7 100644 --- a/src/engine/strat_engine/liminal/liminal.rs +++ b/src/engine/strat_engine/liminal/liminal.rs @@ -49,7 +49,7 @@ use crate::{ types::{ DevUuid, LockedPoolsInfo, MaybeInconsistent, Name, PoolEncryptionInfo, PoolIdentifier, PoolUuid, SizedKeyMemory, StoppedPoolsInfo, StratBlockDevDiff, StratSigblockVersion, - UdevEngineEvent, UnlockMethod, UuidOrConflict, + TokenUnlockMethod, UdevEngineEvent, UuidOrConflict, }, BlockDevTier, }, @@ -97,17 +97,15 @@ impl LiminalDevices { &mut self, pools: &Table, pool_uuid: PoolUuid, - unlock_method: UnlockMethod, + token_slot: TokenUnlockMethod, passphrase: Option<&SizedKeyMemory>, ) -> StratisResult> { fn handle_luks( luks_info: &LLuksInfo, - unlock_method: UnlockMethod, + token_slot: TokenUnlockMethod, passphrase: Option<&SizedKeyMemory>, ) -> StratisResult<()> { - if CryptHandle::setup(&luks_info.dev_info.devnode, Some(unlock_method), passphrase)? - .is_some() - { + if CryptHandle::setup(&luks_info.dev_info.devnode, token_slot, passphrase)?.is_some() { Ok(()) } else { Err(StratisError::Msg(format!( @@ -145,7 +143,7 @@ impl LiminalDevices { match info { LInfo::Stratis(_) => (), LInfo::Luks(ref luks_info) => { - match handle_luks(luks_info, unlock_method, passphrase) { + match handle_luks(luks_info, token_slot, passphrase) { Ok(()) => unlocked.push(*dev_uuid), Err(e) => return Err(e), } @@ -183,14 +181,14 @@ impl LiminalDevices { &mut self, pools: &Table, pool_uuid: PoolUuid, - unlock_method: Option, + token_slot: TokenUnlockMethod, passphrase_fd: Option, ) -> StratisResult<(Name, PoolUuid, AnyPool, Vec)> { fn start_pool_failure( pools: &Table, pool_uuid: PoolUuid, luks_info: StratisResult>, - infos: &HashMap, + infos: &HashMap>, bdas: HashMap, meta_res: StratisResult<(DateTime, PoolSave)>, ) -> BDARecordResult<(Name, AnyPool)> { @@ -214,19 +212,19 @@ impl LiminalDevices { // below requires the pool being unlocked to still have its entry in stopped_pools. // Removing it here would cause an error. let encryption_info = pool.encryption_info(); - let unlocked_devices = match (encryption_info, unlock_method, passphrase_fd) { - (Ok(None), None, None) => Vec::new(), + let unlocked_devices = match (encryption_info, token_slot, passphrase_fd) { + (Ok(None), TokenUnlockMethod::None, None) => Vec::new(), (Ok(None), _, _) => { return Err(StratisError::Msg(format!( "Pool with UUID {pool_uuid} is not encrypted but an unlock method or passphrase was provided" ))); } - (Ok(Some(_)), None, _) => { + (Ok(Some(_)), TokenUnlockMethod::None, _) => { return Err(StratisError::Msg(format!( "Pool with UUID {pool_uuid} is encrypted but no unlock method was provided" ))); } - (Ok(Some(_)), Some(method), passphrase_fd) => { + (Ok(Some(_)), method, passphrase_fd) => { let passphrase = if let Some(fd) = passphrase_fd { let mut memory = SafeMemHandle::alloc(MAX_STRATIS_PASS_SIZE)?; let len = read_key_shared(fd, memory.as_mut())?; @@ -333,16 +331,16 @@ impl LiminalDevices { &mut self, pools: &Table, pool_uuid: PoolUuid, - unlock_method: Option, + token_slot: TokenUnlockMethod, passphrase_fd: Option, ) -> StratisResult<(Name, PoolUuid, AnyPool, Vec)> { fn start_pool_failure( pools: &Table, pool_uuid: PoolUuid, - infos: &HashMap, + infos: &HashMap>, bdas: HashMap, meta_res: StratisResult<(DateTime, PoolSave)>, - unlock_method: Option, + token_slot: TokenUnlockMethod, passphrase_fd: Option, ) -> BDARecordResult<(Name, AnyPool)> { let (timestamp, metadata) = match meta_res { @@ -352,10 +350,10 @@ impl LiminalDevices { let passphrase = match ( metadata.features.contains(&PoolFeatures::Encryption), - unlock_method, + token_slot, passphrase_fd, ) { - (false, None, None) | (true, Some(_), None) => None, + (_, _, None) => None, (false, _, _) => { return Err(( StratisError::Msg(format!( @@ -364,14 +362,14 @@ impl LiminalDevices { bdas, )); } - (true, None, _) => return Err(( + (true, TokenUnlockMethod::None, _) => return Err(( StratisError::Msg( "Metadata reported that encryption enabled but no unlock method was provided" .to_string() ), bdas, )), - (true, Some(_), Some(fd)) => { + (true, _, Some(fd)) => { let mut memory = match SafeMemHandle::alloc(MAX_STRATIS_PASS_SIZE) { Ok(m) => m, Err(e) => return Err((StratisError::from(e), bdas)), @@ -385,14 +383,7 @@ impl LiminalDevices { }; setup_pool( - pools, - pool_uuid, - infos, - bdas, - timestamp, - metadata, - unlock_method, - passphrase, + pools, pool_uuid, infos, bdas, timestamp, metadata, token_slot, passphrase, ) } @@ -418,7 +409,7 @@ impl LiminalDevices { &infos, bdas, res, - unlock_method, + token_slot, passphrase_fd, ) { Ok((name, pool)) => { @@ -459,7 +450,7 @@ impl LiminalDevices { &mut self, pools: &Table, id: PoolIdentifier, - unlock_method: Option, + token_slot: TokenUnlockMethod, passphrase_fd: Option, ) -> StratisResult<(Name, PoolUuid, AnyPool, Vec)> { let pool_uuid = match id { @@ -483,10 +474,10 @@ impl LiminalDevices { match metadata_version { StratSigblockVersion::V1 => { - self.start_pool_legacy(pools, pool_uuid, unlock_method, passphrase_fd) + self.start_pool_legacy(pools, pool_uuid, token_slot, passphrase_fd) } StratSigblockVersion::V2 => { - self.start_pool_new(pools, pool_uuid, unlock_method, passphrase_fd) + self.start_pool_new(pools, pool_uuid, token_slot, passphrase_fd) } } } @@ -846,7 +837,7 @@ impl LiminalDevices { pools: &Table, pool_uuid: PoolUuid, luks_info: StratisResult>, - infos: &HashMap, + infos: &HashMap>, bdas: HashMap, metadata_version: StratisResult, meta_res: StratisResult<(DateTime, PoolSave)>, @@ -865,24 +856,17 @@ impl LiminalDevices { pools, pool_uuid, luks_info, infos, bdas, timestamp, metadata, ) .map(Either::Left), - StratSigblockVersion::V2 => { - let is_encrypted = metadata.features.contains(&PoolFeatures::Encryption); - setup_pool( - pools, - pool_uuid, - infos, - bdas, - timestamp, - metadata, - if is_encrypted { - Some(UnlockMethod::Any) - } else { - None - }, - None, - ) - .map(Either::Left) - } + StratSigblockVersion::V2 => setup_pool( + pools, + pool_uuid, + infos, + bdas, + timestamp, + metadata, + TokenUnlockMethod::None, + None, + ) + .map(Either::Left), } } else { Ok(Either::Right(bdas)) @@ -1282,7 +1266,7 @@ fn setup_pool_legacy( pools: &Table, pool_uuid: PoolUuid, luks_info: StratisResult>, - infos: &HashMap, + infos: &HashMap>, bdas: HashMap, timestamp: DateTime, metadata: PoolSave, @@ -1359,11 +1343,11 @@ fn setup_pool_legacy( fn setup_pool( pools: &Table, pool_uuid: PoolUuid, - infos: &HashMap, + infos: &HashMap>, bdas: HashMap, timestamp: DateTime, metadata: PoolSave, - unlock_method: Option, + token_slot: TokenUnlockMethod, passphrase: Option, ) -> BDARecordResult<(Name, AnyPool)> { if let Some((uuid, _)) = pools.get_by_name(&metadata.name) { @@ -1399,7 +1383,7 @@ fn setup_pool( )); } - v2::StratPool::setup(pool_uuid, datadevs, cachedevs, timestamp, &metadata, unlock_method, passphrase) + v2::StratPool::setup(pool_uuid, datadevs, cachedevs, timestamp, &metadata, token_slot, passphrase) .map(|(name, pool)| { (name, AnyPool::V2(pool)) }) diff --git a/src/engine/strat_engine/liminal/setup.rs b/src/engine/strat_engine/liminal/setup.rs index ac743912ea..7ffeeb08e1 100644 --- a/src/engine/strat_engine/liminal/setup.rs +++ b/src/engine/strat_engine/liminal/setup.rs @@ -30,7 +30,7 @@ use crate::{ shared::{bds_to_bdas, tiers_to_bdas}, types::{BDARecordResult, BDAResult}, }, - types::{BlockDevTier, DevUuid, DevicePath, Name}, + types::{BlockDevTier, DevUuid, DevicePath, Name, TokenUnlockMethod}, }, stratis::{StratisError, StratisResult}, }; @@ -189,7 +189,7 @@ pub fn get_feature_set( /// infos and bdas are identical. pub fn get_blockdevs_legacy( backstore_save: &BackstoreSave, - infos: &HashMap, + infos: &HashMap>, mut bdas: HashMap, ) -> BDARecordResult<(Vec, Vec)> { let recorded_data_map: HashMap = backstore_save @@ -290,7 +290,7 @@ pub fn get_blockdevs_legacy( /// infos and bdas are identical. pub fn get_blockdevs( backstore_save: &BackstoreSave, - infos: &HashMap, + infos: &HashMap>, mut bdas: HashMap, ) -> BDARecordResult<(Vec, Vec)> { let recorded_data_map: HashMap = backstore_save @@ -451,7 +451,7 @@ fn get_blockdev_legacy( Some(luks) => &luks.dev_info.devnode, None => &info.dev_info.devnode, }; - let handle = match CryptHandle::setup(physical_path, None, None) { + let handle = match CryptHandle::setup(physical_path, TokenUnlockMethod::None, None) { Ok(h) => h, Err(e) => return Err((e, bda)), }; diff --git a/src/engine/strat_engine/mod.rs b/src/engine/strat_engine/mod.rs index 37fd872abf..22ce4a7680 100644 --- a/src/engine/strat_engine/mod.rs +++ b/src/engine/strat_engine/mod.rs @@ -31,7 +31,8 @@ pub use self::pool::inspection as pool_inspection; pub use self::{ backstore::integrity_meta_space, crypt::{ - crypt_metadata_size, register_clevis_token, set_up_crypt_logging, CLEVIS_TANG_TRUST_URL, + crypt_metadata_size, register_clevis_token, set_up_crypt_logging, CLEVIS_LUKS_TOKEN_ID, + CLEVIS_TANG_TRUST_URL, LUKS2_TOKEN_ID, }, dm::{get_dm, get_dm_init}, engine::StratEngine, diff --git a/src/engine/strat_engine/pool/dispatch.rs b/src/engine/strat_engine/pool/dispatch.rs index f9c8e84254..89e5d659ad 100644 --- a/src/engine/strat_engine/pool/dispatch.rs +++ b/src/engine/strat_engine/pool/dispatch.rs @@ -4,6 +4,7 @@ use std::{collections::HashSet, path::Path}; +use either::Either; use serde_json::Value; use devicemapper::{Bytes, Sectors}; @@ -14,9 +15,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, + EncryptionInfo, FilesystemUuid, GrowAction, Key, KeyDescription, Name, + OptionalTokenSlotInput, PoolDiff, PoolEncryptionInfo, PoolUuid, PropChangeAction, + RegenAction, RenameAction, SetCreateAction, SetDeleteAction, StratSigblockVersion, }, }, stratis::StratisResult, @@ -44,53 +45,56 @@ impl Pool for AnyPool { fn bind_clevis( &mut self, + token_slot: OptionalTokenSlotInput, pin: &str, clevis_info: &Value, ) -> StratisResult> { match self { - AnyPool::V1(p) => p.bind_clevis(pin, clevis_info), - AnyPool::V2(p) => p.bind_clevis(pin, clevis_info), + AnyPool::V1(p) => p.bind_clevis(token_slot, pin, clevis_info), + AnyPool::V2(p) => p.bind_clevis(token_slot, pin, clevis_info), } } - fn unbind_clevis(&mut self) -> StratisResult> { + fn bind_keyring( + &mut self, + token_slot: OptionalTokenSlotInput, + key_description: &KeyDescription, + ) -> StratisResult> { match self { - AnyPool::V1(p) => p.unbind_clevis(), - AnyPool::V2(p) => p.unbind_clevis(), + AnyPool::V1(p) => p.bind_keyring(token_slot, key_description), + AnyPool::V2(p) => p.bind_keyring(token_slot, key_description), } } - fn bind_keyring( + fn rebind_keyring( &mut self, - key_description: &KeyDescription, - ) -> StratisResult> { + token_slot: Option, + new_key_desc: &KeyDescription, + ) -> StratisResult> { match self { - AnyPool::V1(p) => p.bind_keyring(key_description), - AnyPool::V2(p) => p.bind_keyring(key_description), + AnyPool::V1(p) => p.rebind_keyring(token_slot, new_key_desc), + AnyPool::V2(p) => p.rebind_keyring(token_slot, new_key_desc), } } - fn unbind_keyring(&mut self) -> StratisResult> { + fn rebind_clevis(&mut self, token_slot: Option) -> StratisResult { match self { - AnyPool::V1(p) => p.unbind_keyring(), - AnyPool::V2(p) => p.unbind_keyring(), + AnyPool::V1(p) => p.rebind_clevis(token_slot), + AnyPool::V2(p) => p.rebind_clevis(token_slot), } } - fn rebind_keyring( - &mut self, - new_key_desc: &KeyDescription, - ) -> StratisResult> { + fn unbind_keyring(&mut self, token_slot: Option) -> StratisResult> { match self { - AnyPool::V1(p) => p.rebind_keyring(new_key_desc), - AnyPool::V2(p) => p.rebind_keyring(new_key_desc), + AnyPool::V1(p) => p.unbind_keyring(token_slot), + AnyPool::V2(p) => p.unbind_keyring(token_slot), } } - fn rebind_clevis(&mut self) -> StratisResult { + fn unbind_clevis(&mut self, token_slot: Option) -> StratisResult> { match self { - AnyPool::V1(p) => p.rebind_clevis(), - AnyPool::V2(p) => p.rebind_clevis(), + AnyPool::V1(p) => p.unbind_clevis(token_slot), + AnyPool::V2(p) => p.unbind_clevis(token_slot), } } @@ -252,7 +256,14 @@ impl Pool for AnyPool { } } - fn encryption_info(&self) -> Option { + fn encryption_info_legacy(&self) -> Option { + match self { + AnyPool::V1(p) => p.encryption_info_legacy(), + AnyPool::V2(p) => p.encryption_info_legacy(), + } + } + + fn encryption_info(&self) -> Option> { match self { AnyPool::V1(p) => p.encryption_info(), AnyPool::V2(p) => p.encryption_info(), diff --git a/src/engine/strat_engine/pool/v1.rs b/src/engine/strat_engine/pool/v1.rs index 44fdb79473..95c63780b0 100644 --- a/src/engine/strat_engine/pool/v1.rs +++ b/src/engine/strat_engine/pool/v1.rs @@ -10,6 +10,7 @@ use std::{ }; use chrono::{DateTime, Utc}; +use either::Either; use serde_json::{Map, Value}; use devicemapper::{Bytes, DmNameBuf, Sectors}; @@ -22,7 +23,7 @@ use crate::engine::{ metadata::MDADataSize, thinpool::{ThinPoolSizeParams, DATA_BLOCK_SIZE}, }, - types::EncryptionInfo, + types::InputEncryptionInfo, }; use crate::{ engine::{ @@ -46,11 +47,11 @@ use crate::{ }, types::{ ActionAvailability, BlockDevTier, Clevis, Compare, CreateAction, DeleteAction, DevUuid, - Diff, FilesystemUuid, GrowAction, Key, KeyDescription, Name, PoolDiff, - PoolEncryptionInfo, PoolUuid, RegenAction, RenameAction, SetCreateAction, + Diff, FilesystemUuid, GrowAction, Key, KeyDescription, Name, OptionalTokenSlotInput, + PoolDiff, PoolEncryptionInfo, PoolUuid, RegenAction, RenameAction, SetCreateAction, SetDeleteAction, StratFilesystemDiff, StratPoolDiff, StratSigblockVersion, }, - PropChangeAction, + EncryptionInfo, PropChangeAction, }, stratis::{StratisError, StratisResult}, }; @@ -187,7 +188,7 @@ impl StratPool { pub fn initialize( name: &str, devices: UnownedDevices, - encryption_info: Option<&EncryptionInfo>, + encryption_info: Option<&InputEncryptionInfo>, ) -> StratisResult<(PoolUuid, StratPool)> { let pool_uuid = PoolUuid::new_v4(); @@ -704,9 +705,14 @@ impl Pool for StratPool { #[pool_rollback] fn bind_clevis( &mut self, + token_slot: OptionalTokenSlotInput, pin: &str, clevis_info: &Value, ) -> StratisResult> { + if token_slot != OptionalTokenSlotInput::Legacy { + return Err(StratisError::Msg("Specifying the token slot for binding is not supported in V1 pools; please migrate to V2 pools to use this feature".to_string())); + } + let changed = self.backstore.bind_clevis(pin, clevis_info)?; if changed { Ok(CreateAction::Created(Clevis)) @@ -715,23 +721,17 @@ impl Pool for StratPool { } } - #[pool_mutating_action("NoRequests")] - #[pool_rollback] - fn unbind_clevis(&mut self) -> StratisResult> { - let changed = self.backstore.unbind_clevis()?; - if changed { - Ok(DeleteAction::Deleted(Clevis)) - } else { - Ok(DeleteAction::Identity) - } - } - #[pool_mutating_action("NoRequests")] #[pool_rollback] fn bind_keyring( &mut self, + token_slot: OptionalTokenSlotInput, key_description: &KeyDescription, ) -> StratisResult> { + if token_slot != OptionalTokenSlotInput::Legacy { + return Err(StratisError::Msg("Specifying the token slot for binding is not supported in V1 pools; please migrate to V2 pools to use this feature".to_string())); + } + let changed = self.backstore.bind_keyring(key_description)?; if changed { Ok(CreateAction::Created(Key)) @@ -740,23 +740,17 @@ impl Pool for StratPool { } } - #[pool_mutating_action("NoRequests")] - #[pool_rollback] - fn unbind_keyring(&mut self) -> StratisResult> { - let changed = self.backstore.unbind_keyring()?; - if changed { - Ok(DeleteAction::Deleted(Key)) - } else { - Ok(DeleteAction::Identity) - } - } - #[pool_mutating_action("NoRequests")] #[pool_rollback] fn rebind_keyring( &mut self, + token_slot: Option, new_key_desc: &KeyDescription, ) -> StratisResult> { + if token_slot.is_some() { + return Err(StratisError::Msg("Specifying the token slot for rebinding is not supported in V1 pools; please migrate to V2 pools to use this feature".to_string())); + } + match self.backstore.rebind_keyring(new_key_desc)? { Some(true) => Ok(RenameAction::Renamed(Key)), Some(false) => Ok(RenameAction::Identity), @@ -766,10 +760,48 @@ impl Pool for StratPool { #[pool_mutating_action("NoRequests")] #[pool_rollback] - fn rebind_clevis(&mut self) -> StratisResult { + fn rebind_clevis(&mut self, token_slot: Option) -> StratisResult { + if token_slot.is_some() { + return Err(StratisError::Msg("Specifying the token slot for rebinding is not supported in V1 pools; please migrate to V2 pools to use this feature".to_string())); + } + self.backstore.rebind_clevis().map(|_| RegenAction) } + #[pool_mutating_action("NoRequests")] + #[pool_rollback] + fn unbind_keyring(&mut self, token_slot: Option) -> StratisResult> { + if token_slot.is_some() { + return Err(StratisError::Msg( + "Token slot can only be provided for V2 pools".to_string(), + )); + } + + let changed = self.backstore.unbind_keyring()?; + if changed { + Ok(DeleteAction::Deleted(Key)) + } else { + Ok(DeleteAction::Identity) + } + } + + #[pool_mutating_action("NoRequests")] + #[pool_rollback] + fn unbind_clevis(&mut self, token_slot: Option) -> StratisResult> { + if token_slot.is_some() { + return Err(StratisError::Msg( + "Token slot can only be provided for V2 pools".to_string(), + )); + } + + let changed = self.backstore.unbind_clevis()?; + if changed { + Ok(DeleteAction::Deleted(Clevis)) + } else { + Ok(DeleteAction::Identity) + } + } + #[pool_mutating_action("NoRequests")] fn create_filesystems<'a>( &mut self, @@ -1168,10 +1200,14 @@ impl Pool for StratPool { self.backstore.is_encrypted() } - fn encryption_info(&self) -> Option { + fn encryption_info_legacy(&self) -> Option { self.backstore.encryption_info() } + fn encryption_info(&self) -> Option> { + self.backstore.encryption_info().map(Either::Right) + } + fn avail_actions(&self) -> ActionAvailability { self.action_avail.clone() } @@ -1370,13 +1406,13 @@ mod tests { assert!( pool.encryption_info().is_some() && pool - .encryption_info() + .encryption_info_legacy() .map(|ei| { ei.is_inconsistent() }) .unwrap_or(false) ); } else if pool.avail_actions() == ActionAvailability::Full { assert!(!pool - .encryption_info() + .encryption_info_legacy() .map(|ei| ei.is_inconsistent()) .unwrap_or(false)); } diff --git a/src/engine/strat_engine/pool/v2.rs b/src/engine/strat_engine/pool/v2.rs index 1d8cda4bd7..71263769b7 100644 --- a/src/engine/strat_engine/pool/v2.rs +++ b/src/engine/strat_engine/pool/v2.rs @@ -9,6 +9,7 @@ use std::{ }; use chrono::{DateTime, Utc}; +use either::Either; use serde_json::{Map, Value}; use devicemapper::{Bytes, DmNameBuf, Sectors}; @@ -36,10 +37,11 @@ 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, ValidatedIntegritySpec, + Diff, EncryptionInfo, FilesystemUuid, GrowAction, InputEncryptionInfo, Key, + KeyDescription, Name, OptionalTokenSlotInput, PoolDiff, PoolEncryptionInfo, PoolUuid, + PropChangeAction, RegenAction, RenameAction, SetCreateAction, SetDeleteAction, + SizedKeyMemory, StratFilesystemDiff, StratPoolDiff, StratSigblockVersion, + TokenUnlockMethod, ValidatedIntegritySpec, }, }, stratis::{StratisError, StratisResult}, @@ -152,7 +154,7 @@ impl StratPool { pub fn initialize( name: &str, devices: UnownedDevices, - encryption_info: Option<&EncryptionInfo>, + encryption_info: Option<&InputEncryptionInfo>, integrity_spec: ValidatedIntegritySpec, ) -> StratisResult<(PoolUuid, StratPool)> { let pool_uuid = PoolUuid::new_v4(); @@ -231,7 +233,7 @@ impl StratPool { cachedevs: Vec, timestamp: DateTime, metadata: &PoolSave, - unlock_method: Option, + token_slot: TokenUnlockMethod, passphrase: Option, ) -> BDARecordResult<(Name, StratPool)> { if let Err(e) = check_metadata(metadata) { @@ -239,13 +241,7 @@ impl StratPool { } let backstore = Backstore::setup( - uuid, - metadata, - datadevs, - cachedevs, - timestamp, - unlock_method, - passphrase, + uuid, metadata, datadevs, cachedevs, timestamp, token_slot, passphrase, )?; let action_avail = backstore.action_availability(); @@ -650,10 +646,11 @@ impl Pool for StratPool { #[pool_mutating_action("NoRequests")] fn bind_clevis( &mut self, + token_slot: OptionalTokenSlotInput, pin: &str, clevis_info: &Value, ) -> StratisResult> { - let changed = self.backstore.bind_clevis(pin, clevis_info)?; + let changed = self.backstore.bind_clevis(token_slot, pin, clevis_info)?; if changed { Ok(CreateAction::Created(Clevis)) } else { @@ -661,22 +658,13 @@ impl Pool for StratPool { } } - #[pool_mutating_action("NoRequests")] - fn unbind_clevis(&mut self) -> StratisResult> { - let changed = self.backstore.unbind_clevis()?; - if changed { - Ok(DeleteAction::Deleted(Clevis)) - } else { - Ok(DeleteAction::Identity) - } - } - #[pool_mutating_action("NoRequests")] fn bind_keyring( &mut self, + token_slot: OptionalTokenSlotInput, key_description: &KeyDescription, ) -> StratisResult> { - let changed = self.backstore.bind_keyring(key_description)?; + let changed = self.backstore.bind_keyring(token_slot, key_description)?; if changed { Ok(CreateAction::Created(Key)) } else { @@ -684,22 +672,13 @@ impl Pool for StratPool { } } - #[pool_mutating_action("NoRequests")] - fn unbind_keyring(&mut self) -> StratisResult> { - let changed = self.backstore.unbind_keyring()?; - if changed { - Ok(DeleteAction::Deleted(Key)) - } else { - Ok(DeleteAction::Identity) - } - } - #[pool_mutating_action("NoRequests")] fn rebind_keyring( &mut self, + token_slot: Option, new_key_desc: &KeyDescription, ) -> StratisResult> { - match self.backstore.rebind_keyring(new_key_desc)? { + match self.backstore.rebind_keyring(token_slot, new_key_desc)? { Some(true) => Ok(RenameAction::Renamed(Key)), Some(false) => Ok(RenameAction::Identity), None => Ok(RenameAction::NoSource), @@ -707,8 +686,30 @@ impl Pool for StratPool { } #[pool_mutating_action("NoRequests")] - fn rebind_clevis(&mut self) -> StratisResult { - self.backstore.rebind_clevis().map(|_| RegenAction) + fn rebind_clevis(&mut self, token_slot: Option) -> StratisResult { + self.backstore + .rebind_clevis(token_slot) + .map(|_| RegenAction) + } + + #[pool_mutating_action("NoRequests")] + fn unbind_keyring(&mut self, token_slot: Option) -> StratisResult> { + let changed = self.backstore.unbind_keyring(token_slot)?; + if changed { + Ok(DeleteAction::Deleted(Key)) + } else { + Ok(DeleteAction::Identity) + } + } + + #[pool_mutating_action("NoRequests")] + fn unbind_clevis(&mut self, token_slot: Option) -> StratisResult> { + let changed = self.backstore.unbind_clevis(token_slot)?; + if changed { + Ok(DeleteAction::Deleted(Clevis)) + } else { + Ok(DeleteAction::Identity) + } } #[pool_mutating_action("NoRequests")] @@ -1072,12 +1073,16 @@ impl Pool for StratPool { self.backstore.is_encrypted() } - fn encryption_info(&self) -> Option { + fn encryption_info_legacy(&self) -> Option { self.backstore .encryption_info() .map(PoolEncryptionInfo::from) } + fn encryption_info(&self) -> Option> { + self.backstore.encryption_info().cloned().map(Either::Left) + } + fn avail_actions(&self) -> ActionAvailability { self.action_avail.clone() } @@ -1272,20 +1277,6 @@ mod tests { fn invariant(pool: &StratPool, pool_name: &str) { check_metadata(&pool.record(&Name::new(pool_name.into()))).unwrap(); assert!(!(pool.is_encrypted() && pool.backstore.has_cache())); - if pool.avail_actions() == ActionAvailability::NoRequests { - assert!( - pool.encryption_info().is_some() - && pool - .encryption_info() - .map(|ei| { ei.is_inconsistent() }) - .unwrap_or(false) - ); - } else if pool.avail_actions() == ActionAvailability::Full { - assert!(!pool - .encryption_info() - .map(|ei| ei.is_inconsistent()) - .unwrap_or(false)); - } assert!(pool .backstore .blockdevs() diff --git a/src/engine/types/keys.rs b/src/engine/types/keys.rs index f147f28f42..4421e2bbe9 100644 --- a/src/engine/types/keys.rs +++ b/src/engine/types/keys.rs @@ -3,19 +3,36 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::{ + collections::{ + hash_map::{self, Entry}, + HashMap, HashSet, + }, fmt::{self, Debug}, hash::{Hash, Hasher}, + slice, vec, }; +use itertools::Itertools; use serde_json::{Map, Value}; +use strum_macros::{self, EnumString, VariantNames}; use libcryptsetup_rs::SafeMemHandle; use crate::{ - engine::types::{ClevisInfo, MaybeInconsistent}, + engine::{ + strat_engine::{CLEVIS_LUKS_TOKEN_ID, LUKS2_TOKEN_ID}, + types::{ClevisInfo, MaybeInconsistent}, + }, stratis::{StratisError, StratisResult}, }; +type IntoPartsTuple<'a> = ( + HashSet<&'a KeyDescription>, + Vec<&'a ClevisInfo>, + HashMap, + HashMap, +); + /// A handle for memory designed to safely handle Stratis passphrases. It can /// be coerced to a slice reference for use in read-only operations. pub struct SizedKeyMemory { @@ -42,98 +59,366 @@ impl AsRef<[u8]> for SizedKeyMemory { } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub enum EncryptionInfo { +pub enum UnlockMechanism { KeyDesc(KeyDescription), ClevisInfo(ClevisInfo), - Both(KeyDescription, ClevisInfo), } -impl EncryptionInfo { - /// Convert two optional encryption parameters into an EncryptionInfo enum. - pub fn from_options(options: (Option, Option)) -> Option { - match options { - (Some(kd), Some(ci)) => Some(EncryptionInfo::Both(kd, ci)), - (Some(kd), _) => Some(EncryptionInfo::KeyDesc(kd)), - (_, Some(ci)) => Some(EncryptionInfo::ClevisInfo(ci)), - (None, None) => None, +impl UnlockMechanism { + pub fn is_key_desc(&self) -> bool { + match self { + UnlockMechanism::KeyDesc(_) => true, + UnlockMechanism::ClevisInfo(_) => false, } } - /// Set the key description. - pub fn set_key_desc(self, key_desc: KeyDescription) -> Self { + pub fn is_clevis_info(&self) -> bool { match self { - EncryptionInfo::KeyDesc(_) => EncryptionInfo::KeyDesc(key_desc), - EncryptionInfo::ClevisInfo(ci) => EncryptionInfo::Both(key_desc, ci), - EncryptionInfo::Both(_, ci) => EncryptionInfo::Both(key_desc, ci), + UnlockMechanism::KeyDesc(_) => false, + UnlockMechanism::ClevisInfo(_) => true, } } - /// Unset the key description. If only Clevis info is set, no action is taken. - /// Precondition: matches!(self, EncryptionInfo::Both(_, _) | EncryptonInfo::ClevisInfo(_)) - pub fn unset_key_desc(self) -> Self { + pub fn key_desc(&self) -> Option<&KeyDescription> { match self { - EncryptionInfo::KeyDesc(_) => { - panic!("Cannot convert from encrypted to unencrypted device"); - } - EncryptionInfo::Both(_, ci) => EncryptionInfo::ClevisInfo(ci), - unchanged => unchanged, + UnlockMechanism::KeyDesc(kd) => Some(kd), + UnlockMechanism::ClevisInfo(_) => None, } } - /// Set Clevis information. - pub fn set_clevis_info(self, clevis_info: ClevisInfo) -> Self { + pub fn clevis_info(&self) -> Option<&ClevisInfo> { match self { - EncryptionInfo::ClevisInfo(_) => EncryptionInfo::ClevisInfo(clevis_info), - EncryptionInfo::KeyDesc(kd) => EncryptionInfo::Both(kd, clevis_info), - EncryptionInfo::Both(kd, _) => EncryptionInfo::Both(kd, clevis_info), + UnlockMechanism::ClevisInfo(ci) => Some(ci), + UnlockMechanism::KeyDesc(_) => None, } } +} - /// Unset the Clevis info. If only the key description is set, no action is taken. - /// Precondition: matches!(self, EncryptionInfo::Both(_, _) | EncryptonInfo::KeyDesc(_)) - pub fn unset_clevis_info(self) -> Self { - match self { - EncryptionInfo::ClevisInfo(_) => { - panic!("Cannot convert from encrypted to unencrypted device"); +#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct InputEncryptionInfo { + encryption_infos: Vec<(Option, UnlockMechanism)>, +} + +impl InputEncryptionInfo { + pub fn new_legacy( + key_desc: Option, + clevis_info: Option, + ) -> Option { + let mut ei = InputEncryptionInfo { + encryption_infos: Vec::new(), + }; + if key_desc.is_none() && clevis_info.is_none() { + return None; + } + if let Some(kd) = key_desc { + ei.add_info(Some(LUKS2_TOKEN_ID), UnlockMechanism::KeyDesc(kd)) + .expect("Initialized new data structure"); + } + if let Some(ci) = clevis_info { + ei.add_info(Some(CLEVIS_LUKS_TOKEN_ID), UnlockMechanism::ClevisInfo(ci)) + .expect("Initialized new data structure"); + } + Some(ei) + } + + pub fn new( + kds: Vec<(Option, KeyDescription)>, + cis: Vec<(Option, ClevisInfo)>, + ) -> StratisResult> { + if kds.is_empty() && cis.is_empty() { + Ok(None) + } else { + let mut iei = InputEncryptionInfo { + encryption_infos: Vec::default(), + }; + for (slot, kd) in kds { + iei.add_info(slot, UnlockMechanism::KeyDesc(kd))?; } - EncryptionInfo::Both(kd, _) => EncryptionInfo::KeyDesc(kd), - unchanged => unchanged, + for (slot, ci) in cis { + iei.add_info(slot, UnlockMechanism::ClevisInfo(ci))?; + } + Ok(Some(iei)) } } - /// Get an optional key description from the encryption information. - pub fn key_description(&self) -> Option<&KeyDescription> { - match self { - EncryptionInfo::Both(kd, _) | EncryptionInfo::KeyDesc(kd) => Some(kd), - _ => None, + pub fn is_empty(&self) -> bool { + self.encryption_infos.is_empty() + } + + pub fn add_info( + &mut self, + token_slot: Option, + mech: UnlockMechanism, + ) -> StratisResult<()> { + if let Some(t) = token_slot { + if self.iter().map(|(slot, _)| slot).contains(&Some(t)) { + return Err(StratisError::Msg(format!("Token slot {t} already taken"))); + } } + self.encryption_infos.push((token_slot, mech)); + Ok(()) } - /// Get optional Clevis information from the encryption information. - pub fn clevis_info(&self) -> Option<&ClevisInfo> { - match self { - EncryptionInfo::Both(_, ci) | EncryptionInfo::ClevisInfo(ci) => Some(ci), - _ => None, + pub fn iter(&self) -> slice::Iter<'_, (Option, UnlockMechanism)> { + self.encryption_infos.iter() + } + + pub fn into_parts_legacy( + &self, + ) -> StratisResult<(Option<&KeyDescription>, Option<&ClevisInfo>)> { + let mut kd = None; + let mut ci = None; + for (token_slot, unlock_mech) in self.encryption_infos.iter() { + match token_slot { + None => { + return Err(StratisError::Msg( + "Static token slots are required for V1 of the metadata".to_string(), + )); + } + Some(LUKS2_TOKEN_ID) => match unlock_mech { + UnlockMechanism::KeyDesc(k) => kd = Some(k), + UnlockMechanism::ClevisInfo(_) => { + return Err(StratisError::Msg(format!( + "Token slot {LUKS2_TOKEN_ID} is required to refer to a key description in V1 of the metadata" + ))); + } + }, + Some(CLEVIS_LUKS_TOKEN_ID) => match unlock_mech { + UnlockMechanism::ClevisInfo(c) => ci = Some(c), + UnlockMechanism::KeyDesc(_) => { + return Err(StratisError::Msg(format!( + "Token slot {CLEVIS_LUKS_TOKEN_ID} is required to refer to Clevis info in V1 of the metadata" + ))); + } + }, + Some(t) => { + return Err(StratisError::Msg(format!( + "Invalid token slot {t} for V1 of the metadata" + ))); + } + } } + + Ok((kd, ci)) + } + + pub fn into_parts(&self) -> StratisResult> { + let mut key_descs = HashSet::default(); + let mut clevis_infos = Vec::default(); + let mut key_descs_with_token_id = HashMap::default(); + let mut clevis_infos_with_token_id = HashMap::default(); + for (token_slot, unlock_mechanism) in self.iter() { + match token_slot { + Some(t) => match unlock_mechanism { + UnlockMechanism::KeyDesc(kd) => { + if key_descs_with_token_id.insert(*t, kd).is_some() { + return Err(StratisError::Msg(format!( + "Token slot {t} is requested for two unlock mechanisms" + ))); + } + } + UnlockMechanism::ClevisInfo(ci) => { + if clevis_infos_with_token_id.insert(*t, ci).is_some() { + return Err(StratisError::Msg(format!( + "Token slot {t} is requested for two unlock mechanisms" + ))); + } + } + }, + None => match unlock_mechanism { + UnlockMechanism::KeyDesc(kd) => { + key_descs.insert(kd); + } + UnlockMechanism::ClevisInfo(ci) => { + clevis_infos.push(ci); + } + }, + } + } + + Ok(( + key_descs, + clevis_infos, + key_descs_with_token_id, + clevis_infos_with_token_id, + )) } } -impl fmt::Display for EncryptionInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let key_desc_str = if let Some(kd) = self.key_description() { - format!("key description: \"{}\"", kd.as_application_str()) - } else { - "no key description".to_string() - }; - if let Some((pin, config)) = self.clevis_info() { - write!( - f, - "{key_desc_str}, clevis pin: \"{pin}\", clevis configuration: \"{config}\"" - ) +impl From for InputEncryptionInfo { + fn from(ei: EncryptionInfo) -> Self { + InputEncryptionInfo { + encryption_infos: ei + .into_iter() + .map(|(slot, um)| (Some(slot), um)) + .collect::>(), + } + } +} + +impl IntoIterator for InputEncryptionInfo { + type Item = (Option, UnlockMechanism); + type IntoIter = vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.encryption_infos.into_iter() + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct EncryptionInfo { + encryption_infos: HashMap, +} + +impl EncryptionInfo { + pub fn new() -> Self { + EncryptionInfo { + encryption_infos: HashMap::default(), + } + } + + /// Note: This is a legacy method for operations that only operate on one token per type of + /// unlock mechanism. + pub fn from_options(kd: Option, ci: Option) -> Option { + match (kd, ci) { + (Some(k), Some(c)) => { + let mut ei = EncryptionInfo::new(); + let _ = ei.add_info(LUKS2_TOKEN_ID, UnlockMechanism::KeyDesc(k)); + let _ = ei.add_info(CLEVIS_LUKS_TOKEN_ID, UnlockMechanism::ClevisInfo(c)); + Some(ei) + } + (Some(k), _) => { + let mut ei = EncryptionInfo::new(); + let _ = ei.add_info(LUKS2_TOKEN_ID, UnlockMechanism::KeyDesc(k)); + Some(ei) + } + (_, Some(c)) => { + let mut ei = EncryptionInfo::new(); + let _ = ei.add_info(CLEVIS_LUKS_TOKEN_ID, UnlockMechanism::ClevisInfo(c)); + Some(ei) + } + (_, _) => None, + } + } + + pub fn single_key_description(&self) -> Option<(u32, &KeyDescription)> { + self.encryption_infos + .iter() + .filter_map(|(i, mech)| mech.key_desc().map(|kd| (i, kd))) + .min_by(|(i, _), (j, _)| i.cmp(j)) + .map(|(i, kd)| (*i, kd)) + } + + pub fn single_clevis_info(&self) -> Option<(u32, &ClevisInfo)> { + self.encryption_infos + .iter() + .filter_map(|(i, mech)| mech.clevis_info().map(|ci| (i, ci))) + .min_by(|(i, _), (j, _)| i.cmp(j)) + .map(|(i, clevis)| (*i, clevis)) + } + + pub fn all_token_slots(&self) -> impl Iterator + '_ { + self.encryption_infos.keys().cloned() + } + + pub fn free_token_slot(&self) -> u32 { + // Find the maximum value that's occupied and add 1 to it for all in case there is no unused token + // slot smaller than or equal to the max. + let occupied = self.all_token_slots().collect::>(); + let max = occupied.iter().max().cloned().unwrap_or(0); + let all = (0..=max + 1).collect::>(); + *all.difference(&occupied) + .next() + .expect("Difference should always contain one element") + } + + pub fn all_key_descriptions(&self) -> impl Iterator + '_ { + self.encryption_infos + .iter() + .filter_map(|(i, mech)| mech.key_desc().map(|kd| (i, kd))) + } + + pub fn all_clevis_infos(&self) -> impl Iterator + '_ { + self.encryption_infos + .iter() + .filter_map(|(i, mech)| mech.clevis_info().map(|clevis| (i, clevis))) + } + + pub fn all_infos(&self) -> impl Iterator + '_ { + self.encryption_infos.iter() + } + + pub fn add_info(&mut self, token_slot: u32, mech: UnlockMechanism) -> StratisResult<()> { + if let Entry::Vacant(v) = self.encryption_infos.entry(token_slot) { + v.insert(mech); + Ok(()) } else { - write!(f, "{key_desc_str}, no Clevis information") + Err(StratisError::Msg(format!( + "Token slot {token_slot} already occupied" + ))) } } + + pub fn get_info(&self, token_slot: u32) -> Option<&UnlockMechanism> { + self.encryption_infos.get(&token_slot) + } + + pub fn set_info(&mut self, token_slot: u32, mech: UnlockMechanism) -> StratisResult<()> { + let entry = self.encryption_infos.entry(token_slot); + match entry { + Entry::Occupied(mut entry) => match entry.get_mut() { + UnlockMechanism::KeyDesc(ref mut kd) => match mech { + UnlockMechanism::KeyDesc(kd_set) => *kd = kd_set, + UnlockMechanism::ClevisInfo(_) => return Err(StratisError::Msg("Binding is a key description but provided unlock mechanism is a Clevis binding".to_string())), + }, + UnlockMechanism::ClevisInfo(ref mut clevis) => match mech { + UnlockMechanism::KeyDesc(_) => return Err(StratisError::Msg("Binding is a Clevis binding but provided unlock mechanism is a key description".to_string())), + UnlockMechanism::ClevisInfo(clevis_set) => *clevis = clevis_set, + }, + } + Entry::Vacant(entry) => { + entry.insert(mech); + } + } + Ok(()) + } + + pub fn remove(&mut self, token_slot: u32) -> bool { + self.encryption_infos.remove(&token_slot).is_some() + } + + pub fn is_empty(&self) -> bool { + self.encryption_infos.is_empty() + } +} + +impl IntoIterator for EncryptionInfo { + type Item = (u32, UnlockMechanism); + type IntoIter = hash_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.encryption_infos.into_iter() + } +} + +impl fmt::Display for EncryptionInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + self.encryption_infos + .iter() + .map(|(i, mech)| match mech { + UnlockMechanism::KeyDesc(kd) => { + format!("{i}: key description {}", kd.as_application_str()) + } + UnlockMechanism::ClevisInfo((pin, json)) => { + format!("{i}: clevis info {pin}, {json}") + } + }) + .join(", ") + ) + } } // Implement Hash explicitly because Value does not implement Hash. @@ -144,24 +429,33 @@ impl fmt::Display for EncryptionInfo { // if x_1 == x_2, then hash(x_1) == hash(x_2) obviously holds. impl Hash for EncryptionInfo { fn hash(&self, state: &mut H) { - self.key_description().hash(state); - self.clevis_info().map(|(pin, _)| pin).hash(state); + for (t, mech) in self.encryption_infos.iter() { + t.hash(state); + match mech { + UnlockMechanism::KeyDesc(kd) => kd.hash(state), + UnlockMechanism::ClevisInfo((pin, _)) => pin.hash(state), + } + } } } impl Into for &EncryptionInfo { fn into(self) -> Value { - let mut json = Map::new(); - if let Some(kd) = self.key_description() { - json.insert( - "key_description".to_string(), - Value::from(kd.as_application_str()), - ); - } - if let Some(info) = self.clevis_info() { - json.insert("clevis_pin".to_string(), Value::from(info.0.to_owned())); - json.insert("clevis_config".to_string(), info.1.clone()); - } + let json = self + .encryption_infos + .iter() + .map(|(token_slot, mech)| { + ( + token_slot.to_string(), + match mech { + UnlockMechanism::KeyDesc(kd) => Value::from(kd.as_application_str()), + UnlockMechanism::ClevisInfo((pin, config)) => { + Value::from(vec![Value::from(pin.to_owned()), config.to_owned()]) + } + }, + ) + }) + .collect::>(); Value::from(json) } } @@ -172,13 +466,20 @@ impl TryFrom for EncryptionInfo { fn try_from(pei: PoolEncryptionInfo) -> StratisResult { match pei { PoolEncryptionInfo::KeyDesc(MaybeInconsistent::No(kd)) => { - Ok(EncryptionInfo::KeyDesc(kd)) + let mut ei = EncryptionInfo::new(); + ei.add_info(LUKS2_TOKEN_ID, UnlockMechanism::KeyDesc(kd))?; + Ok(ei) }, PoolEncryptionInfo::ClevisInfo(MaybeInconsistent::No(ci)) => { - Ok(EncryptionInfo::ClevisInfo(ci)) + let mut ei = EncryptionInfo::new(); + ei.add_info(CLEVIS_LUKS_TOKEN_ID, UnlockMechanism::ClevisInfo(ci))?; + Ok(ei) }, PoolEncryptionInfo::Both(MaybeInconsistent::No(kd), MaybeInconsistent::No(ci)) => { - Ok(EncryptionInfo::Both(kd, ci)) + let mut ei = EncryptionInfo::new(); + ei.add_info(LUKS2_TOKEN_ID, UnlockMechanism::KeyDesc(kd))?; + ei.add_info(CLEVIS_LUKS_TOKEN_ID, UnlockMechanism::ClevisInfo(ci))?; + Ok(ei) }, _ => { Err(StratisError::Msg("Cannot determine encryption information from metadata that is inconsistent across devices in a pool".to_string())) @@ -309,17 +610,20 @@ impl PoolEncryptionInfo { impl From<&EncryptionInfo> for PoolEncryptionInfo { fn from(enc_info: &EncryptionInfo) -> Self { - match enc_info { - EncryptionInfo::KeyDesc(ref kd) => { + let key_description = enc_info.single_key_description(); + let clevis_info = enc_info.single_clevis_info(); + match (key_description, clevis_info) { + (Some((_, kd)), None) => { PoolEncryptionInfo::KeyDesc(MaybeInconsistent::No(kd.to_owned())) } - EncryptionInfo::ClevisInfo(ref ci) => { + (None, Some((_, ci))) => { PoolEncryptionInfo::ClevisInfo(MaybeInconsistent::No(ci.to_owned())) } - EncryptionInfo::Both(ref kd, ref ci) => PoolEncryptionInfo::Both( + (Some((_, kd)), Some((_, ci))) => PoolEncryptionInfo::Both( MaybeInconsistent::No(kd.to_owned()), MaybeInconsistent::No(ci.to_owned()), ), + (_, _) => unreachable!("Cannot have no unlock mechanism"), } } } @@ -376,3 +680,75 @@ impl TryFrom<&String> for KeyDescription { KeyDescription::try_from(s.to_owned()) } } + +/// Use Clevis or keyring to unlock LUKS volume. +#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, Debug, EnumString, VariantNames)] +#[strum(serialize_all = "snake_case")] +pub enum UnlockMethod { + Clevis, + Keyring, + Any, +} + +/// Represents the token slot input for an encryption operation. +#[derive(PartialEq, Serialize, Deserialize)] +pub enum OptionalTokenSlotInput { + /// Indicates that legacy behavior of assuming one token slot for a key description and one for + /// Clevis info should be used. + Legacy, + /// No token slot is provided. Typically means that one should be automatically selected. + None, + /// A specific token slot to use for the operation. + Some(u32), +} + +/// Represents the token slot input for an unlock operation. +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] +pub enum TokenUnlockMethod { + None, + Any, + Token(u32), +} + +impl TokenUnlockMethod { + pub fn from_options(input: Option>) -> Self { + match input { + None => TokenUnlockMethod::None, + Some(None) => TokenUnlockMethod::Any, + Some(Some(t)) => TokenUnlockMethod::Token(t), + } + } + + pub fn is_none(&self) -> bool { + matches!(self, TokenUnlockMethod::None) + } + + pub fn is_some(&self) -> bool { + !matches!(self, TokenUnlockMethod::None) + } + + pub fn is_any(&self) -> bool { + matches!(self, TokenUnlockMethod::Any) + } + + pub fn get_token_slot(&self) -> StratisResult> { + match self { + TokenUnlockMethod::None => Err(StratisError::Msg( + "No unlock mechanism was provided".to_string(), + )), + TokenUnlockMethod::Any => Ok(None), + TokenUnlockMethod::Token(t) => Ok(Some(*t)), + } + } +} + +impl From> for TokenUnlockMethod { + fn from(method: Option) -> Self { + match method { + Some(UnlockMethod::Any) => TokenUnlockMethod::Any, + Some(UnlockMethod::Clevis) => TokenUnlockMethod::Token(CLEVIS_LUKS_TOKEN_ID), + Some(UnlockMethod::Keyring) => TokenUnlockMethod::Token(LUKS2_TOKEN_ID), + None => TokenUnlockMethod::None, + } + } +} diff --git a/src/engine/types/mod.rs b/src/engine/types/mod.rs index 80e1f93e54..40fc1a8e57 100644 --- a/src/engine/types/mod.rs +++ b/src/engine/types/mod.rs @@ -21,23 +21,30 @@ use uuid::Uuid; use devicemapper::{Bytes, Sectors, IEC}; -pub use crate::engine::{ - engine::StateDiff, - structures::Lockable, - types::{ - actions::{ - Clevis, CreateAction, DeleteAction, EngineAction, GrowAction, Key, MappingCreateAction, - MappingDeleteAction, PropChangeAction, RegenAction, RenameAction, SetCreateAction, - SetDeleteAction, SetUnlockAction, StartAction, StopAction, ToDisplay, +pub use crate::{ + engine::{ + engine::StateDiff, + structures::Lockable, + types::{ + actions::{ + Clevis, CreateAction, DeleteAction, EngineAction, GrowAction, Key, + MappingCreateAction, MappingDeleteAction, PropChangeAction, RegenAction, + RenameAction, SetCreateAction, SetDeleteAction, SetUnlockAction, StartAction, + StopAction, ToDisplay, + }, + diff::{ + Compare, Diff, PoolDiff, StratBlockDevDiff, StratFilesystemDiff, StratPoolDiff, + ThinPoolDiff, + }, + keys::{ + EncryptionInfo, InputEncryptionInfo, KeyDescription, OptionalTokenSlotInput, + PoolEncryptionInfo, SizedKeyMemory, TokenUnlockMethod, UnlockMechanism, + UnlockMethod, + }, }, - diff::{ - Compare, Diff, PoolDiff, StratBlockDevDiff, StratFilesystemDiff, StratPoolDiff, - ThinPoolDiff, - }, - keys::{EncryptionInfo, KeyDescription, PoolEncryptionInfo, SizedKeyMemory}, }, + stratis::{StratisError, StratisResult}, }; -use crate::stratis::{StratisError, StratisResult}; pub const DEFAULT_INTEGRITY_JOURNAL_SIZE: Bytes = Bytes(128 * IEC::Mi as u128); pub const DEFAULT_INTEGRITY_BLOCK_SIZE: Bytes = Bytes(4 * IEC::Ki as u128); @@ -136,15 +143,6 @@ impl Display for StratisUuid { } } -/// Use Clevis or keyring to unlock LUKS volume. -#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, Debug, EnumString, VariantNames)] -#[strum(serialize_all = "snake_case")] -pub enum UnlockMethod { - Clevis, - Keyring, - Any, -} - /// Blockdev tier. Used to distinguish between blockdevs used for /// data and blockdevs used for a cache. #[derive(Clone, Copy, Debug, Eq, PartialEq)] diff --git a/src/jsonrpc/client/pool.rs b/src/jsonrpc/client/pool.rs index f253272bde..18d3f91a20 100644 --- a/src/jsonrpc/client/pool.rs +++ b/src/jsonrpc/client/pool.rs @@ -9,7 +9,10 @@ use nix::unistd::{pipe, write}; use serde_json::Value; use crate::{ - engine::{EncryptionInfo, KeyDescription, PoolIdentifier, PoolUuid, UnlockMethod}, + engine::{ + InputEncryptionInfo, KeyDescription, OptionalTokenSlotInput, PoolIdentifier, PoolUuid, + TokenUnlockMethod, + }, jsonrpc::client::utils::{prompt_password, to_suffix_repr}, print_table, stratis::{StratisError, StratisResult}, @@ -19,7 +22,7 @@ use crate::{ pub fn pool_create( name: String, blockdevs: Vec, - enc_info: Option, + enc_info: Option, ) -> StratisResult<()> { do_request_standard!(PoolCreate, name, blockdevs, enc_info) } @@ -27,7 +30,7 @@ pub fn pool_create( // stratis-min pool start pub fn pool_start( id: PoolIdentifier, - unlock_method: Option, + unlock_method: TokenUnlockMethod, prompt: bool, ) -> StratisResult<()> { if prompt { @@ -161,46 +164,48 @@ pub fn pool_has_passphrase(id: PoolIdentifier) -> StratisResult } } -// stratis-min pool clevis-pin -pub fn pool_clevis_pin(id: PoolIdentifier) -> StratisResult { - let (clevis_pin, rc, rs) = do_request!(PoolClevisPin, id); - if rc != 0 { - Err(StratisError::Msg(rs)) - } else { - Ok(clevis_pin.unwrap_or_else(|| "None".to_string())) - } -} - pub fn pool_bind_keyring( id: PoolIdentifier, + token_slot: OptionalTokenSlotInput, key_desc: KeyDescription, ) -> StratisResult<()> { - do_request_standard!(PoolBindKeyring, id, key_desc) + do_request_standard!(PoolBindKeyring, id, token_slot, key_desc) } pub fn pool_bind_clevis( id: PoolIdentifier, + token_slot: OptionalTokenSlotInput, pin: String, clevis_info: Value, ) -> StratisResult<()> { - do_request_standard!(PoolBindClevis, id, pin, clevis_info) + do_request_standard!(PoolBindClevis, id, token_slot, pin, clevis_info) } -pub fn pool_unbind_keyring(id: PoolIdentifier) -> StratisResult<()> { - do_request_standard!(PoolUnbindKeyring, id) +pub fn pool_unbind_keyring( + id: PoolIdentifier, + token_slot: Option, +) -> StratisResult<()> { + do_request_standard!(PoolUnbindKeyring, id, token_slot) } -pub fn pool_unbind_clevis(id: PoolIdentifier) -> StratisResult<()> { - do_request_standard!(PoolUnbindClevis, id) +pub fn pool_unbind_clevis( + id: PoolIdentifier, + token_slot: Option, +) -> StratisResult<()> { + do_request_standard!(PoolUnbindClevis, id, token_slot) } pub fn pool_rebind_keyring( id: PoolIdentifier, + token_slot: Option, key_desc: KeyDescription, ) -> StratisResult<()> { - do_request_standard!(PoolRebindKeyring, id, key_desc) + do_request_standard!(PoolRebindKeyring, id, token_slot, key_desc) } -pub fn pool_rebind_clevis(id: PoolIdentifier) -> StratisResult<()> { - do_request_standard!(PoolRebindClevis, id) +pub fn pool_rebind_clevis( + id: PoolIdentifier, + token_slot: Option, +) -> StratisResult<()> { + do_request_standard!(PoolRebindClevis, id, token_slot) } diff --git a/src/jsonrpc/interface.rs b/src/jsonrpc/interface.rs index e9744e6da0..394d9a72f6 100644 --- a/src/jsonrpc/interface.rs +++ b/src/jsonrpc/interface.rs @@ -9,7 +9,8 @@ use std::{os::unix::io::RawFd, path::PathBuf}; use serde_json::Value; use crate::engine::{ - EncryptionInfo, FilesystemUuid, KeyDescription, PoolIdentifier, PoolUuid, UnlockMethod, + FilesystemUuid, InputEncryptionInfo, KeyDescription, OptionalTokenSlotInput, PoolIdentifier, + PoolUuid, TokenUnlockMethod, }; pub type PoolListType = ( @@ -35,26 +36,34 @@ pub enum StratisParamType { KeySet(KeyDescription), KeyUnset(KeyDescription), KeyList, - PoolCreate(String, Vec, Option), + PoolCreate(String, Vec, Option), PoolRename(String, String), PoolAddData(String, Vec), PoolInitCache(String, Vec), PoolAddCache(String, Vec), PoolDestroy(String), - PoolStart(PoolIdentifier, Option), + PoolStart(PoolIdentifier, TokenUnlockMethod), PoolStop(PoolIdentifier), PoolList, - PoolBindKeyring(PoolIdentifier, KeyDescription), - PoolBindClevis(PoolIdentifier, String, Value), - PoolUnbindKeyring(PoolIdentifier), - PoolUnbindClevis(PoolIdentifier), - PoolRebindKeyring(PoolIdentifier, KeyDescription), - PoolRebindClevis(PoolIdentifier), + PoolBindKeyring( + PoolIdentifier, + OptionalTokenSlotInput, + KeyDescription, + ), + PoolBindClevis( + PoolIdentifier, + OptionalTokenSlotInput, + String, + Value, + ), + PoolUnbindKeyring(PoolIdentifier, Option), + PoolUnbindClevis(PoolIdentifier, Option), + PoolRebindKeyring(PoolIdentifier, Option, KeyDescription), + PoolRebindClevis(PoolIdentifier, Option), PoolIsEncrypted(PoolIdentifier), PoolIsStopped(PoolIdentifier), PoolIsBound(PoolIdentifier), PoolHasPassphrase(PoolIdentifier), - PoolClevisPin(PoolIdentifier), FsCreate(String, String), FsDestroy(String, String), FsRename(String, String, String), @@ -92,9 +101,8 @@ pub enum StratisRet { PoolRebindClevis((bool, u16, String)), PoolIsEncrypted((bool, u16, String)), PoolIsStopped((bool, u16, String)), - PoolIsBound((bool, u16, String)), PoolHasPassphrase((bool, u16, String)), - PoolClevisPin((Option, u16, String)), + PoolIsBound((bool, u16, String)), FsCreate((bool, u16, String)), FsList(FsListType), FsDestroy((bool, u16, String)), diff --git a/src/jsonrpc/server/pool.rs b/src/jsonrpc/server/pool.rs index 3b1d584591..25c5e5787c 100644 --- a/src/jsonrpc/server/pool.rs +++ b/src/jsonrpc/server/pool.rs @@ -4,13 +4,15 @@ use std::{os::unix::io::RawFd, path::Path, sync::Arc}; +use either::Either; use serde_json::Value; use tokio::task::block_in_place; use crate::{ engine::{ - BlockDevTier, CreateAction, DeleteAction, EncryptionInfo, Engine, EngineAction, - IntegritySpec, KeyDescription, Name, PoolIdentifier, PoolUuid, RenameAction, UnlockMethod, + BlockDevTier, CreateAction, DeleteAction, Engine, EngineAction, InputEncryptionInfo, + IntegritySpec, KeyDescription, Name, OptionalTokenSlotInput, PoolIdentifier, PoolUuid, + RenameAction, TokenUnlockMethod, }, jsonrpc::interface::PoolListType, stratis::{StratisError, StratisResult}, @@ -20,7 +22,7 @@ use crate::{ pub async fn pool_start( engine: Arc, id: PoolIdentifier, - unlock_method: Option, + unlock_method: TokenUnlockMethod, prompt: Option, ) -> StratisResult { Ok(engine @@ -42,7 +44,7 @@ pub async fn pool_create<'a>( engine: Arc, name: &'a str, blockdev_paths: &'a [&'a Path], - enc_info: Option<&'a EncryptionInfo>, + enc_info: Option<&'a InputEncryptionInfo>, ) -> StratisResult { Ok( match engine @@ -166,6 +168,7 @@ pub async fn pool_list(engine: Arc) -> PoolListType { pub async fn pool_bind_keyring( engine: Arc, id: PoolIdentifier, + token_slot: OptionalTokenSlotInput, key_desc: &KeyDescription, ) -> StratisResult { let mut guard = engine @@ -174,7 +177,7 @@ pub async fn pool_bind_keyring( .ok_or_else(|| StratisError::Msg(format!("Pool with {id} not found")))?; let (_, _, pool) = guard.as_mut_tuple(); - match pool.bind_keyring(key_desc)? { + match pool.bind_keyring(token_slot, key_desc)? { CreateAction::Created(_key) => Ok(true), CreateAction::Identity => Ok(false), } @@ -184,6 +187,7 @@ pub async fn pool_bind_keyring( pub async fn pool_bind_clevis<'a>( engine: Arc, id: PoolIdentifier, + token_slot: OptionalTokenSlotInput, pin: &'a str, clevis_info: &'a Value, ) -> StratisResult { @@ -193,7 +197,7 @@ pub async fn pool_bind_clevis<'a>( .ok_or_else(|| StratisError::Msg(format!("Pool with {id} not found")))?; let (_, _, pool) = guard.as_mut_tuple(); - match pool.bind_clevis(pin, clevis_info)? { + match pool.bind_clevis(token_slot, pin, clevis_info)? { CreateAction::Created(_clevis) => Ok(true), CreateAction::Identity => Ok(false), } @@ -203,6 +207,7 @@ pub async fn pool_bind_clevis<'a>( pub async fn pool_unbind_keyring( engine: Arc, id: PoolIdentifier, + token_slot: Option, ) -> StratisResult { let mut guard = engine .get_mut_pool(id.clone()) @@ -210,7 +215,7 @@ pub async fn pool_unbind_keyring( .ok_or_else(|| StratisError::Msg(format!("Pool with {id} not found")))?; let (_, _, pool) = guard.as_mut_tuple(); - match pool.unbind_keyring()? { + match pool.unbind_keyring(token_slot)? { DeleteAction::Deleted(_key_desc) => Ok(true), DeleteAction::Identity => Ok(false), } @@ -220,6 +225,7 @@ pub async fn pool_unbind_keyring( pub async fn pool_unbind_clevis( engine: Arc, id: PoolIdentifier, + token_slot: Option, ) -> StratisResult { let mut guard = engine .get_mut_pool(id.clone()) @@ -227,7 +233,7 @@ pub async fn pool_unbind_clevis( .ok_or_else(|| StratisError::Msg(format!("Pool with {id} not found")))?; let (_, _, pool) = guard.as_mut_tuple(); - match pool.unbind_clevis()? { + match pool.unbind_clevis(token_slot)? { DeleteAction::Deleted(_clevis) => Ok(true), DeleteAction::Identity => Ok(false), } @@ -237,6 +243,7 @@ pub async fn pool_unbind_clevis( pub async fn pool_rebind_keyring( engine: Arc, id: PoolIdentifier, + token_slot: Option, key_desc: KeyDescription, ) -> StratisResult { let mut guard = engine @@ -245,7 +252,7 @@ pub async fn pool_rebind_keyring( .ok_or_else(|| StratisError::Msg(format!("Pool with {id} not found")))?; let (_, _, pool) = guard.as_mut_tuple(); - match pool.rebind_keyring(&key_desc)? { + match pool.rebind_keyring(token_slot, &key_desc)? { RenameAction::Renamed(_key) => Ok(true), RenameAction::Identity => Ok(false), RenameAction::NoSource => Ok(false), @@ -256,6 +263,7 @@ pub async fn pool_rebind_keyring( pub async fn pool_rebind_clevis( engine: Arc, id: PoolIdentifier, + token_slot: Option, ) -> StratisResult { let mut guard = engine .get_mut_pool(id.clone()) @@ -263,7 +271,7 @@ pub async fn pool_rebind_clevis( .ok_or_else(|| StratisError::Msg(format!("Pool with {id} not found")))?; let (_, _, pool) = guard.as_mut_tuple(); - pool.rebind_clevis()?; + pool.rebind_clevis(token_slot)?; Ok(true) } @@ -326,7 +334,8 @@ pub async fn pool_is_bound( let guard = engine.get_pool(id.clone()).await; if let Some((_, _, pool)) = guard.as_ref().map(|guard| guard.as_tuple()) { Ok(match pool.encryption_info() { - Some(ei) => ei.clevis_info()?.is_some(), + Some(Either::Left(ei)) => ei.all_clevis_infos().count() > 0, + Some(Either::Right(ei)) => ei.clevis_info()?.is_some(), None => false, }) } else if let Some(info) = locked.locked.get(match id { @@ -351,7 +360,8 @@ pub async fn pool_has_passphrase( let guard = engine.get_pool(id.clone()).await; if let Some((_, _, pool)) = guard.as_ref().map(|guard| guard.as_tuple()) { Ok(match pool.encryption_info() { - Some(ei) => ei.key_description()?.is_some(), + Some(Either::Left(ei)) => ei.all_key_descriptions().count() > 0, + Some(Either::Right(ei)) => ei.key_description()?.is_some(), None => false, }) } else if let Some(info) = locked.locked.get(match id { @@ -366,29 +376,3 @@ pub async fn pool_has_passphrase( Err(StratisError::Msg(format!("Pool with {id} not found"))) } } - -// stratis-min pool clevis-pin -pub async fn pool_clevis_pin( - engine: Arc, - id: PoolIdentifier, -) -> StratisResult> { - let locked = engine.locked_pools().await; - let guard = engine.get_pool(id.clone()).await; - if let Some((_, _, pool)) = guard.as_ref().map(|guard| guard.as_tuple()) { - let encryption_info = match pool.encryption_info() { - Some(ei) => EncryptionInfo::try_from(ei)?, - None => return Ok(None), - }; - Ok(encryption_info.clevis_info().map(|(pin, _)| pin.clone())) - } else if let Some(info) = locked.locked.get(match id { - PoolIdentifier::Uuid(ref u) => u, - PoolIdentifier::Name(ref n) => locked - .name_to_uuid - .get(n) - .ok_or_else(|| StratisError::Msg(format!("Could not find pool with name {n}")))?, - }) { - Ok(info.info.clevis_info()?.map(|(pin, _)| pin.clone())) - } else { - Err(StratisError::Msg(format!("Pool with {id} not found"))) - } -} diff --git a/src/jsonrpc/server/server.rs b/src/jsonrpc/server/server.rs index e791f7d1a8..8d483d4dc5 100644 --- a/src/jsonrpc/server/server.rs +++ b/src/jsonrpc/server/server.rs @@ -135,45 +135,45 @@ impl StratisParams { expects_fd!(self.fd_opt, false); Ok(StratisRet::PoolList(pool::pool_list(engine).await)) } - StratisParamType::PoolBindKeyring(id, key_desc) => { + StratisParamType::PoolBindKeyring(id, token_slot, key_desc) => { expects_fd!(self.fd_opt, false); Ok(StratisRet::PoolBindKeyring(stratis_result_to_return( - pool::pool_bind_keyring(engine, id, &key_desc).await, + pool::pool_bind_keyring(engine, id, token_slot, &key_desc).await, false, ))) } - StratisParamType::PoolBindClevis(id, pin, clevis_info) => { + StratisParamType::PoolBindClevis(id, token_slot, pin, clevis_info) => { expects_fd!(self.fd_opt, false); Ok(StratisRet::PoolBindClevis(stratis_result_to_return( - pool::pool_bind_clevis(engine, id, &pin, &clevis_info).await, + pool::pool_bind_clevis(engine, id, token_slot, &pin, &clevis_info).await, false, ))) } - StratisParamType::PoolUnbindKeyring(id) => { + StratisParamType::PoolUnbindKeyring(id, token_slot) => { expects_fd!(self.fd_opt, false); Ok(StratisRet::PoolUnbindKeyring(stratis_result_to_return( - pool::pool_unbind_keyring(engine, id).await, + pool::pool_unbind_keyring(engine, id, token_slot).await, false, ))) } - StratisParamType::PoolUnbindClevis(id) => { + StratisParamType::PoolUnbindClevis(id, token_slot) => { expects_fd!(self.fd_opt, false); Ok(StratisRet::PoolUnbindClevis(stratis_result_to_return( - pool::pool_unbind_clevis(engine, id).await, + pool::pool_unbind_clevis(engine, id, token_slot).await, false, ))) } - StratisParamType::PoolRebindKeyring(id, key_desc) => { + StratisParamType::PoolRebindKeyring(id, token_slot, key_desc) => { expects_fd!(self.fd_opt, false); Ok(StratisRet::PoolRebindKeyring(stratis_result_to_return( - pool::pool_rebind_keyring(engine, id, key_desc).await, + pool::pool_rebind_keyring(engine, id, token_slot, key_desc).await, false, ))) } - StratisParamType::PoolRebindClevis(id) => { + StratisParamType::PoolRebindClevis(id, token_slot) => { expects_fd!(self.fd_opt, false); Ok(StratisRet::PoolRebindClevis(stratis_result_to_return( - pool::pool_rebind_clevis(engine, id).await, + pool::pool_rebind_clevis(engine, id, token_slot).await, false, ))) } @@ -205,13 +205,6 @@ impl StratisParams { false, ))) } - StratisParamType::PoolClevisPin(id) => { - expects_fd!(self.fd_opt, false); - Ok(StratisRet::PoolClevisPin(stratis_result_to_return( - pool::pool_clevis_pin(engine, id).await, - None, - ))) - } StratisParamType::FsCreate(pool_name, fs_name) => { expects_fd!(self.fd_opt, false); Ok(StratisRet::FsCreate(stratis_result_to_return( diff --git a/tests/stratis_min.rs b/tests/stratis_min.rs index 68867a78b8..9007ee3f1e 100644 --- a/tests/stratis_min.rs +++ b/tests/stratis_min.rs @@ -75,20 +75,15 @@ fn test_stratis_min_create_with_clevis_1() { let mut cmd = Command::cargo_bin("stratis-min").unwrap(); cmd.arg("pool") .arg("create") - .arg("--clevis") - .arg("tang") - .arg("--tang-url") - .arg("url") - .arg("--thumbprint") - .arg("jkj") - .arg("--trust-url") + .arg("--clevis-infos") + .arg("pin=tang tang_url=url thumbprint=jkj trust_url") .arg("pn") .arg("/dev/n"); cmd.assert() .failure() - .code(2) + .code(1) .stderr(predicate::str::contains( - "'--thumbprint ' cannot be used with '--trust-url'", + "thumbprint= cannot be used with trust_url", )); } @@ -97,11 +92,8 @@ fn stratis_min_create_with_clevis_url() { let mut cmd = Command::cargo_bin("stratis-min").unwrap(); cmd.arg("pool") .arg("create") - .arg("--clevis") - .arg("tang") - .arg("--tang-url") - .arg("url") - .arg("--trust-url") + .arg("--clevis-infos") + .arg("pin=tang tang_url=url trust_url") .arg("pn") .arg("/dev/n"); cmd.assert().success(); @@ -117,12 +109,8 @@ fn stratis_min_create_with_clevis_thumbprint() { let mut cmd = Command::cargo_bin("stratis-min").unwrap(); cmd.arg("pool") .arg("create") - .arg("--clevis") - .arg("tang") - .arg("--tang-url") - .arg("url") - .arg("--thumbprint") - .arg("jkj") + .arg("--clevis-infos") + .arg("pin=tang tang_url=url thumbprint=jkj") .arg("pn") .arg("/dev/n"); cmd.assert().success(); @@ -138,8 +126,8 @@ fn stratis_min_create_with_clevis_tpm() { let mut cmd = Command::cargo_bin("stratis-min").unwrap(); cmd.arg("pool") .arg("create") - .arg("--clevis") - .arg("tpm2") + .arg("--clevis-infos") + .arg("pin=tpm2") .arg("pn") .arg("/dev/n"); cmd.assert().success(); @@ -156,16 +144,14 @@ fn test_stratis_min_create_with_clevis_invalid() { let mut cmd = Command::cargo_bin("stratis-min").unwrap(); cmd.arg("pool") .arg("create") - .arg("--clevis") - .arg("nosuch") + .arg("--clevis-infos") + .arg("pin=nosuch") .arg("pn") .arg("/dev/n"); cmd.assert() .failure() - .code(2) - .stderr(predicate::str::contains( - "invalid value 'nosuch' for '--clevis '", - )); + .code(1) + .stderr(predicate::str::contains("Invalid pin")); } #[test] @@ -175,16 +161,14 @@ fn test_stratis_min_create_with_clevis_missing_args() { let mut cmd = Command::cargo_bin("stratis-min").unwrap(); cmd.arg("pool") .arg("create") - .arg("--clevis") - .arg("tang") + .arg("--clevis-infos") + .arg("pin=tang") .arg("pn") .arg("/dev/n"); cmd.assert() .failure() - .code(2) - .stderr(predicate::str::contains( - "required arguments were not provided", - )); + .code(1) + .stderr(predicate::str::contains("tang_url is required")); } #[test] @@ -194,17 +178,15 @@ fn test_stratis_min_create_with_clevis_invalid_2() { let mut cmd = Command::cargo_bin("stratis-min").unwrap(); cmd.arg("pool") .arg("create") - .arg("--clevis") - .arg("tang") - .arg("--tang-url") - .arg("url") + .arg("--clevis-infos") + .arg("pin=tang tang_url=url") .arg("pn") .arg("/dev/n"); cmd.assert() .failure() - .code(2) + .code(1) .stderr(predicate::str::contains( - "required arguments were not provided", + "Missing required argument trust_url or thumbprint=", )); } @@ -367,13 +349,6 @@ fn test_stratis_min_pool_properties_invalid_uuid() { .failure() .code(1) .stderr(predicate::str::contains("Uuid error")); - - let mut cmd = Command::cargo_bin("stratis-min").unwrap(); - cmd.arg("pool").arg("clevis-pin").arg("pn"); - cmd.assert() - .failure() - .code(1) - .stderr(predicate::str::contains("Uuid error")); } #[test] @@ -446,8 +421,8 @@ fn stratis_min_create_encrypted_keydesc() { let mut cmd = Command::cargo_bin("stratis-min").unwrap(); cmd.arg("pool") .arg("create") - .arg("--key-desc") - .arg("testkey") + .arg("--key-descs") + .arg("testkey:0") .arg("pn") .arg("/dev/n"); cmd.assert().success(); @@ -467,10 +442,19 @@ fn stratis_min_create_encrypted_keydesc_bind_clevis() { .arg("testkey"); cmd.assert().success(); let mut cmd = Command::cargo_bin("stratis-min").unwrap(); + cmd.write_stdin("thisisanewtestpassphrase\n") + .arg("key") + .arg("set") + .arg("--capture-key") + .arg("testkey1"); + cmd.assert().success(); + let mut cmd = Command::cargo_bin("stratis-min").unwrap(); cmd.arg("pool") .arg("create") - .arg("--key-desc") - .arg("testkey") + .arg("--key-descs") + .arg("testkey:0") + .arg("--key-descs") + .arg("testkey1:1") .arg("pn") .arg("/dev/n"); cmd.assert().success(); @@ -481,7 +465,9 @@ fn stratis_min_create_encrypted_keydesc_bind_clevis() { .arg("--name") .arg("pn") .arg("url") - .arg("--trust-url"); + .arg("--trust-url") + .arg("--token-slot") + .arg("2"); cmd.assert().success(); } @@ -775,15 +761,3 @@ fn stratis_min_key_set_empty() { fn test_stratis_min_key_set_empty() { test_with_stratisd_min_sim(stratis_min_key_set_empty); } - -fn stratis_min_pool_clevis_pin() { - stratis_min_create_pool_and_fs(); - let mut cmd = Command::cargo_bin("stratis-min").unwrap(); - cmd.arg("pool").arg("clevis-pin").arg("--name").arg("pn"); - cmd.assert().success(); -} - -#[test] -fn test_stratis_min_pool_clevis_pin() { - test_with_stratisd_min_sim(stratis_min_pool_clevis_pin); -} From f3637dfdfb3cde8ba53ebe3beea57668a642a372 Mon Sep 17 00:00:00 2001 From: John Baublitz Date: Wed, 13 Nov 2024 11:26:05 -0500 Subject: [PATCH 2/4] Modify D-Bus interface to support multiple token slots --- src/dbus_api/api/manager_3_8/api.rs | 24 +- src/dbus_api/api/manager_3_8/methods.rs | 81 ++-- src/dbus_api/consts.rs | 2 + src/dbus_api/pool/mod.rs | 21 +- src/dbus_api/pool/pool_3_0/methods.rs | 42 +- src/dbus_api/pool/pool_3_8/api.rs | 103 +++++ src/dbus_api/pool/pool_3_8/methods.rs | 399 ++++++++++++++++++ src/dbus_api/pool/pool_3_8/mod.rs | 12 + src/dbus_api/pool/pool_3_8/props.rs | 27 ++ src/dbus_api/pool/prop_conv.rs | 98 ++++- src/dbus_api/pool/shared.rs | 16 +- src/dbus_api/tree.rs | 226 ++++++---- src/dbus_api/types.rs | 27 +- src/engine/engine.rs | 4 +- src/engine/sim_engine/pool.rs | 8 +- .../strat_engine/backstore/backstore/v2.rs | 50 ++- src/engine/strat_engine/crypt/handle/v2.rs | 28 +- src/engine/strat_engine/crypt/shared.rs | 4 +- src/engine/strat_engine/pool/dispatch.rs | 4 +- src/engine/strat_engine/pool/v1.rs | 9 +- src/engine/strat_engine/pool/v2.rs | 18 +- src/engine/types/actions.rs | 12 +- src/engine/types/keys.rs | 17 + 23 files changed, 984 insertions(+), 248 deletions(-) create mode 100644 src/dbus_api/pool/pool_3_8/api.rs create mode 100644 src/dbus_api/pool/pool_3_8/methods.rs create mode 100644 src/dbus_api/pool/pool_3_8/mod.rs create mode 100644 src/dbus_api/pool/pool_3_8/props.rs diff --git a/src/dbus_api/api/manager_3_8/api.rs b/src/dbus_api/api/manager_3_8/api.rs index 5b8e468599..0e1a5d2464 100644 --- a/src/dbus_api/api/manager_3_8/api.rs +++ b/src/dbus_api/api/manager_3_8/api.rs @@ -20,7 +20,7 @@ pub fn start_pool_method(f: &Factory, TData>) -> Method, TData>) -> Method + .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: (bool, (String, String)) - .in_arg(("clevis_info", "(b(ss))")) + // Rust representation: Vec<((bool, u32), String, String)> + .in_arg(("clevis_info", "a((bu)ss)")) // Optional journal size for integrity metadata reservation. // b: true if the size should be specified. // false if the default should be used. diff --git a/src/dbus_api/api/manager_3_8/methods.rs b/src/dbus_api/api/manager_3_8/methods.rs index a8c50e245b..512d52068b 100644 --- a/src/dbus_api/api/manager_3_8/methods.rs +++ b/src/dbus_api/api/manager_3_8/methods.rs @@ -10,12 +10,12 @@ use dbus::{ }; use dbus_tree::{MTSync, MethodInfo, MethodResult}; use futures::executor::block_on; +use serde_json::from_str; use devicemapper::Bytes; use crate::{ dbus_api::{ - api::shared::EncryptionParams, blockdev::create_dbus_blockdev, filesystem::create_dbus_filesystem, pool::create_dbus_pool, @@ -24,11 +24,16 @@ use crate::{ }, engine::{ CreateAction, InputEncryptionInfo, IntegritySpec, IntegrityTagSpec, KeyDescription, Name, - PoolIdentifier, PoolUuid, StartAction, TokenUnlockMethod, UnlockMethod, + PoolIdentifier, PoolUuid, StartAction, TokenUnlockMethod, }, 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; @@ -64,29 +69,15 @@ pub fn start_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { } } }; - let unlock_method = { - let unlock_method_tup: (bool, &str) = get_next_arg(&mut iter, 2)?; - match tuple_to_option(unlock_method_tup) { - Some(unlock_method_str) => { - match UnlockMethod::try_from(unlock_method_str).map_err(|_| { - StratisError::Msg(format!("{unlock_method_str} is an invalid unlock method")) - }) { - Ok(um) => Some(um), - Err(e) => { - let (rc, rs) = engine_to_dbus_err_tuple(&e); - return Ok(vec![return_message.append3(default_return, rc, rs)]); - } - } - } - None => None, - } - }; + let unlock_method_tup: (bool, (bool, u32)) = get_next_arg(&mut iter, 2)?; + let unlock_method = + TokenUnlockMethod::from_options(tuple_to_option(unlock_method_tup).map(tuple_to_option)); let fd_opt: (bool, OwnedFd) = get_next_arg(&mut iter, 3)?; let fd = tuple_to_option(fd_opt); let ret = match handle_action!(block_on(dbus_context.engine.start_pool( id.clone(), - TokenUnlockMethod::from(unlock_method), + unlock_method, fd.map(|f| f.into_fd()), ))) { Ok(StartAction::Started(_)) => { @@ -153,10 +144,8 @@ pub fn create_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { let name: &str = get_next_arg(&mut iter, 0)?; let devs: Array<'_, &str, _> = get_next_arg(&mut iter, 1)?; - let (key_desc_tuple, clevis_tuple): EncryptionParams = ( - Some(get_next_arg(&mut iter, 2)?), - Some(get_next_arg(&mut iter, 3)?), - ); + let (key_desc_array, clevis_array): EncryptionInfos<'_> = + (get_next_arg(&mut iter, 2)?, get_next_arg(&mut iter, 3)?); let journal_size_tuple: (bool, u64) = get_next_arg(&mut iter, 4)?; let tag_spec_tuple: (bool, String) = get_next_arg(&mut iter, 5)?; let allocate_superblock_tuple: (bool, bool) = get_next_arg(&mut iter, 6)?; @@ -166,26 +155,44 @@ pub fn create_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { let default_return: (bool, (dbus::Path<'static>, Vec>)) = (false, (dbus::Path::default(), Vec::new())); - let key_desc = match key_desc_tuple.and_then(tuple_to_option) { - Some(kds) => match KeyDescription::try_from(kds) { - Ok(kd) => Some(kd), + 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)]); } - }, - None => None, - }; + }; - let clevis_info = match clevis_tuple.and_then(tuple_to_option) { - Some((pin, json_string)) => match serde_json::from_str(json_string.as_str()) { - Ok(j) => Some((pin, j)), + 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(&StratisError::Serde(e)); + let (rc, rs) = engine_to_dbus_err_tuple(&e); return Ok(vec![return_message.append3(default_return, rc, rs)]); } - }, - None => None, + }; + + let ei = match InputEncryptionInfo::new(key_descs, clevis_infos) { + Ok(opt) => opt, + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&e); + return Ok(vec![return_message.append3(default_return, rc, rs)]); + } }; let journal_size = tuple_to_option(journal_size_tuple).map(Bytes::from); @@ -208,7 +215,7 @@ pub fn create_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { let create_result = handle_action!(block_on(dbus_context.engine.create_pool( name, &devs.map(Path::new).collect::>(), - InputEncryptionInfo::new_legacy(key_desc, clevis_info).as_ref(), + ei.as_ref(), IntegritySpec { journal_size, tag_spec, diff --git a/src/dbus_api/consts.rs b/src/dbus_api/consts.rs index ada3d96bac..41b6391246 100644 --- a/src/dbus_api/consts.rs +++ b/src/dbus_api/consts.rs @@ -44,9 +44,11 @@ pub const POOL_HAS_CACHE_PROP: &str = "HasCache"; pub const POOL_ENCRYPTED_PROP: &str = "Encrypted"; pub const POOL_AVAIL_ACTIONS_PROP: &str = "AvailableActions"; pub const POOL_KEY_DESC_PROP: &str = "KeyDescription"; +pub const POOL_KEY_DESCS_PROP: &str = "KeyDescriptions"; pub const POOL_TOTAL_SIZE_PROP: &str = "TotalPhysicalSize"; pub const POOL_TOTAL_USED_PROP: &str = "TotalPhysicalUsed"; pub const POOL_CLEVIS_INFO_PROP: &str = "ClevisInfo"; +pub const POOL_CLEVIS_INFOS_PROP: &str = "ClevisInfos"; pub const POOL_ALLOC_SIZE_PROP: &str = "AllocatedSize"; pub const POOL_FS_LIMIT_PROP: &str = "FsLimit"; pub const POOL_OVERPROV_PROP: &str = "Overprovisioning"; diff --git a/src/dbus_api/pool/mod.rs b/src/dbus_api/pool/mod.rs index 57a63cf335..c414b62dd6 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; @@ -281,14 +282,14 @@ pub fn create_dbus_pool<'a>( .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_8::bind_clevis_method(&f)) + .add_m(pool_3_8::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_8::bind_keyring_method(&f)) + .add_m(pool_3_8::unbind_keyring_method(&f)) + .add_m(pool_3_8::rebind_keyring_method(&f)) + .add_m(pool_3_8::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)) @@ -297,8 +298,8 @@ pub fn create_dbus_pool<'a>( .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_8::key_descs_property(&f)) + .add_p(pool_3_8::clevis_infos_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)) @@ -444,8 +445,8 @@ pub fn get_pool_properties( 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_KEY_DESCS_PROP => shared::pool_key_descs_prop(pool), + consts::POOL_CLEVIS_INFOS_PROP => shared::pool_clevis_infos_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), diff --git a/src/dbus_api/pool/pool_3_0/methods.rs b/src/dbus_api/pool/pool_3_0/methods.rs index 30b7a19df0..32997cd4bd 100644 --- a/src/dbus_api/pool/pool_3_0/methods.rs +++ b/src/dbus_api/pool/pool_3_0/methods.rs @@ -384,8 +384,11 @@ pub fn bind_clevis(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Ok(CreateAction::Created(_)) => { - dbus_context - .push_pool_clevis_info_change(pool_path.get_name(), pool.encryption_info_legacy()); + dbus_context.push_pool_clevis_info_change( + pool_path.get_name(), + pool.encryption_info() + .map(|either| either.map_left(|ei| (true, ei))), + ); return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Err(e) => { @@ -422,8 +425,11 @@ pub fn unbind_clevis(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Ok(DeleteAction::Deleted(_)) => { - dbus_context - .push_pool_clevis_info_change(pool_path.get_name(), pool.encryption_info_legacy()); + dbus_context.push_pool_clevis_info_change( + pool_path.get_name(), + pool.encryption_info() + .map(|either| either.map_left(|ei| (true, ei))), + ); return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Err(e) => { @@ -474,8 +480,11 @@ pub fn bind_keyring(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Ok(CreateAction::Created(_)) => { - dbus_context - .push_pool_key_desc_change(pool_path.get_name(), pool.encryption_info_legacy()); + dbus_context.push_pool_key_desc_change( + pool_path.get_name(), + pool.encryption_info() + .map(|either| either.map_left(|ei| (true, ei))), + ); return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Err(e) => { @@ -516,8 +525,11 @@ pub fn unbind_keyring(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Ok(DeleteAction::Deleted(_)) => { - dbus_context - .push_pool_key_desc_change(pool_path.get_name(), pool.encryption_info_legacy()); + dbus_context.push_pool_key_desc_change( + pool_path.get_name(), + pool.encryption_info() + .map(|either| either.map_left(|ei| (true, ei))), + ); return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Err(e) => { @@ -568,8 +580,11 @@ pub fn rebind_keyring(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Ok(RenameAction::Renamed(_)) => { - dbus_context - .push_pool_key_desc_change(pool_path.get_name(), pool.encryption_info_legacy()); + dbus_context.push_pool_key_desc_change( + pool_path.get_name(), + pool.encryption_info() + .map(|either| either.map_left(|ei| (true, ei))), + ); return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Ok(RenameAction::NoSource) => { @@ -610,8 +625,11 @@ pub fn rebind_clevis(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { let msg = match handle_action!(pool.rebind_clevis(None), dbus_context, pool_path.get_name()) { Ok(_) => { - dbus_context - .push_pool_clevis_info_change(pool_path.get_name(), pool.encryption_info_legacy()); + dbus_context.push_pool_clevis_info_change( + pool_path.get_name(), + pool.encryption_info() + .map(|either| either.map_left(|ei| (true, ei))), + ); return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) } Err(e) => { 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..a590bf68fd --- /dev/null +++ b/src/dbus_api/pool/pool_3_8/api.rs @@ -0,0 +1,103 @@ +// 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::{Access, EmitsChangedSignal, Factory, MTSync, Method, Property}; + +use crate::dbus_api::{ + pool::{ + consts, + pool_3_8::{ + methods::{ + bind_clevis, bind_keyring, rebind_clevis, rebind_keyring, unbind_clevis, + unbind_keyring, + }, + props::{get_pool_clevis_infos, get_pool_key_descs}, + }, + }, + types::TData, +}; + +pub fn bind_clevis_method(f: &Factory, TData>) -> Method, TData> { + f.method("BindClevis", (), bind_clevis) + .in_arg(("pin", "s")) + .in_arg(("json", "s")) + .in_arg(("token_slot", "(bu)")) + // b: Indicates if new clevis bindings were added + // + // Rust representation: bool + .out_arg(("results", "b")) + .out_arg(("return_code", "q")) + .out_arg(("return_string", "s")) +} + +pub fn unbind_clevis_method(f: &Factory, TData>) -> Method, TData> { + f.method("UnbindClevis", (), unbind_clevis) + .in_arg(("token_slot", "(bu)")) + // b: Indicates if clevis bindings were removed + // + // Rust representation: bool + .out_arg(("results", "b")) + .out_arg(("return_code", "q")) + .out_arg(("return_string", "s")) +} + +pub fn bind_keyring_method(f: &Factory, TData>) -> Method, TData> { + f.method("BindKeyring", (), bind_keyring) + .in_arg(("key_desc", "s")) + .in_arg(("token_slot", "(bu)")) + // b: Indicates if new keyring bindings were added + // + // Rust representation: bool + .out_arg(("results", "b")) + .out_arg(("return_code", "q")) + .out_arg(("return_string", "s")) +} + +pub fn unbind_keyring_method(f: &Factory, TData>) -> Method, TData> { + f.method("UnbindKeyring", (), unbind_keyring) + .in_arg(("token_slot", "(bu)")) + // b: Indicates if keyring bindings were removed + // + // Rust representation: bool + .out_arg(("results", "b")) + .out_arg(("return_code", "q")) + .out_arg(("return_string", "s")) +} + +pub fn rebind_keyring_method(f: &Factory, TData>) -> Method, TData> { + f.method("RebindKeyring", (), rebind_keyring) + .in_arg(("key_desc", "s")) + .in_arg(("token_slot", "(bu)")) + // b: Indicates if keyring bindings were changed + // + // Rust representation: bool + .out_arg(("results", "b")) + .out_arg(("return_code", "q")) + .out_arg(("return_string", "s")) +} + +pub fn rebind_clevis_method(f: &Factory, TData>) -> Method, TData> { + f.method("RebindClevis", (), rebind_clevis) + .in_arg(("token_slot", "(bu)")) + // b: Indicates if Clevis bindings were changed + // + // Rust representation: bool + .out_arg(("results", "b")) + .out_arg(("return_code", "q")) + .out_arg(("return_string", "s")) +} + +pub fn key_descs_property(f: &Factory, TData>) -> Property, TData> { + f.property::, _>(consts::POOL_KEY_DESCS_PROP, ()) + .access(Access::Read) + .emits_changed(EmitsChangedSignal::True) + .on_get(get_pool_key_descs) +} + +pub fn clevis_infos_property(f: &Factory, TData>) -> Property, TData> { + f.property::, _>(consts::POOL_CLEVIS_INFOS_PROP, ()) + .access(Access::Read) + .emits_changed(EmitsChangedSignal::True) + .on_get(get_pool_clevis_infos) +} 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..f1ac66b536 --- /dev/null +++ b/src/dbus_api/pool/pool_3_8/methods.rs @@ -0,0 +1,399 @@ +// 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::Message; +use dbus_tree::{MTSync, MethodInfo, MethodResult}; +use serde_json::Value; + +use crate::{ + dbus_api::{ + types::{DbusErrorEnum, TData, OK_STRING}, + util::{engine_to_dbus_err_tuple, get_next_arg, tuple_to_option}, + }, + engine::{CreateAction, DeleteAction, KeyDescription, OptionalTokenSlotInput, RenameAction}, + stratis::StratisError, +}; + +pub fn bind_clevis(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { + let message: &Message = m.msg; + let mut iter = message.iter_init(); + let pin: String = get_next_arg(&mut iter, 0)?; + let json_string: String = get_next_arg(&mut iter, 1)?; + let token_slot_tuple: (bool, u32) = get_next_arg(&mut iter, 2)?; + + 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 pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message); + let lowest_token_slot = pool + .encryption_info() + .and_then(|either| either.left()) + .as_ref() + .and_then(|ei| ei.single_clevis_info()) + .map(|(token_slot, _)| token_slot); + + let json: Value = match serde_json::from_str(&json_string) { + Ok(j) => j, + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&StratisError::Serde(e)); + return Ok(vec![return_message.append3(default_return, rc, rs)]); + } + }; + + let token_slot = match tuple_to_option(token_slot_tuple) { + Some(t) => OptionalTokenSlotInput::Some(t), + None => OptionalTokenSlotInput::None, + }; + + let msg = match handle_action!( + pool.bind_clevis(token_slot, pin.as_str(), &json), + dbus_context, + pool_path.get_name() + ) { + Ok(CreateAction::Identity) => { + return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) + } + Ok(CreateAction::Created(_)) => { + dbus_context.push_pool_clevis_info_change( + pool_path.get_name(), + pool.encryption_info() + .map(|either| either.map_left(|ei| (lowest_token_slot.is_none(), ei))), + ); + return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) + } + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&e); + return_message.append3(default_return, rc, rs) + } + }; + Ok(vec![msg]) +} + +pub fn unbind_clevis(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { + let message: &Message = m.msg; + + let mut iter = message.iter_init(); + let token_slot_tuple: (bool, u32) = 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 token_slot = tuple_to_option(token_slot_tuple); + + 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 pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message); + let lowest_token_slot = pool + .encryption_info() + .and_then(|either| either.left()) + .as_ref() + .and_then(|ei| ei.single_clevis_info()) + .map(|(token_slot, _)| token_slot); + + let msg = match handle_action!( + pool.unbind_clevis(token_slot), + dbus_context, + pool_path.get_name() + ) { + Ok(DeleteAction::Identity) => { + return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) + } + Ok(DeleteAction::Deleted(_)) => { + dbus_context.push_pool_clevis_info_change( + pool_path.get_name(), + pool.encryption_info().map(|either| { + either.map_left(|ei| { + (token_slot.is_some() && token_slot == lowest_token_slot, ei) + }) + }), + ); + return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) + } + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&e); + return_message.append3(default_return, rc, rs) + } + }; + Ok(vec![msg]) +} + +pub fn bind_keyring(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { + let message: &Message = m.msg; + let mut iter = message.iter_init(); + let key_desc_str: String = get_next_arg(&mut iter, 0)?; + let token_slot_tuple: (bool, u32) = 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_desc = match KeyDescription::try_from(key_desc_str) { + Ok(kd) => kd, + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&e); + return Ok(vec![return_message.append3(default_return, rc, rs)]); + } + }; + + let token_slot = match tuple_to_option(token_slot_tuple) { + Some(t) => OptionalTokenSlotInput::Some(t), + None => OptionalTokenSlotInput::None, + }; + + 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 pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message); + let lowest_token_slot = pool + .encryption_info() + .and_then(|either| either.left()) + .as_ref() + .and_then(|ei| ei.single_key_description()) + .map(|(token_slot, _)| token_slot); + + let msg = match handle_action!( + pool.bind_keyring(token_slot, &key_desc), + dbus_context, + pool_path.get_name() + ) { + Ok(CreateAction::Identity) => { + return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) + } + Ok(CreateAction::Created(_)) => { + dbus_context.push_pool_key_desc_change( + pool_path.get_name(), + pool.encryption_info() + .map(|ei| ei.map_left(|e| (lowest_token_slot.is_none(), e))), + ); + return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) + } + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&e); + return_message.append3(default_return, rc, rs) + } + }; + Ok(vec![msg]) +} + +pub fn unbind_keyring(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { + let message: &Message = m.msg; + + let mut iter = message.iter_init(); + let token_slot_tuple: (bool, u32) = 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 token_slot = tuple_to_option(token_slot_tuple); + + 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 pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message); + let lowest_token_slot = pool + .encryption_info() + .and_then(|either| either.left()) + .as_ref() + .and_then(|ei| ei.single_key_description()) + .map(|(token_slot, _)| token_slot); + + let msg = match handle_action!( + pool.unbind_keyring(token_slot), + dbus_context, + pool_path.get_name() + ) { + Ok(DeleteAction::Identity) => { + return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) + } + Ok(DeleteAction::Deleted(_)) => { + dbus_context.push_pool_key_desc_change( + pool_path.get_name(), + pool.encryption_info().map(|either| { + either.map_left(|ei| { + (token_slot.is_some() && token_slot == lowest_token_slot, ei) + }) + }), + ); + return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) + } + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&e); + return_message.append3(default_return, rc, rs) + } + }; + Ok(vec![msg]) +} + +pub fn rebind_keyring(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { + let message: &Message = m.msg; + let mut iter = message.iter_init(); + let key_desc_str: String = get_next_arg(&mut iter, 0)?; + let token_slot_tuple: (bool, u32) = 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_desc = match KeyDescription::try_from(key_desc_str) { + Ok(kd) => kd, + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&e); + return Ok(vec![return_message.append3(default_return, rc, rs)]); + } + }; + + let token_slot = tuple_to_option(token_slot_tuple); + + 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 pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message); + let lowest_token_slot = pool + .encryption_info() + .and_then(|either| either.left()) + .as_ref() + .and_then(|ei| ei.single_key_description()) + .map(|(token_slot, _)| token_slot); + + let msg = match handle_action!( + pool.rebind_keyring(token_slot, &key_desc), + dbus_context, + pool_path.get_name() + ) { + Ok(RenameAction::Identity) => { + return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string()) + } + Ok(RenameAction::Renamed(_)) => { + dbus_context.push_pool_key_desc_change( + pool_path.get_name(), + pool.encryption_info().map(|either| { + either.map_left(|ei| { + (token_slot.is_some() && token_slot == lowest_token_slot, ei) + }) + }), + ); + return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) + } + Ok(RenameAction::NoSource) => { + let error_message = format!( + "pool with UUID {pool_uuid} is not currently bound to a keyring passphrase" + ); + let (rc, rs) = (DbusErrorEnum::ERROR as u16, error_message); + return_message.append3(default_return, rc, rs) + } + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&e); + return_message.append3(default_return, rc, rs) + } + }; + Ok(vec![msg]) +} + +pub fn rebind_clevis(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { + let message: &Message = m.msg; + + let mut iter = message.iter_init(); + let token_slot_tuple: (bool, u32) = 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 token_slot = tuple_to_option(token_slot_tuple); + + 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 pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message); + let lowest_token_slot = pool + .encryption_info() + .and_then(|either| either.left()) + .as_ref() + .and_then(|ei| ei.single_clevis_info()) + .map(|(token_slot, _)| token_slot); + + let msg = match handle_action!( + pool.rebind_clevis(token_slot), + dbus_context, + pool_path.get_name() + ) { + Ok(_) => { + dbus_context.push_pool_clevis_info_change( + pool_path.get_name(), + pool.encryption_info().map(|either| { + either.map_left(|ei| { + (token_slot.is_some() && token_slot == lowest_token_slot, ei) + }) + }), + ); + return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string()) + } + Err(e) => { + let (rc, rs) = engine_to_dbus_err_tuple(&e); + 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..4ec70ddccb --- /dev/null +++ b/src/dbus_api/pool/pool_3_8/mod.rs @@ -0,0 +1,12 @@ +// 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; +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, +}; diff --git a/src/dbus_api/pool/pool_3_8/props.rs b/src/dbus_api/pool/pool_3_8/props.rs new file mode 100644 index 0000000000..c218f37f61 --- /dev/null +++ b/src/dbus_api/pool/pool_3_8/props.rs @@ -0,0 +1,27 @@ +// 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::arg::IterAppend; +use dbus_tree::{MTSync, MethodErr, PropInfo}; + +use crate::dbus_api::{ + pool::shared::{self, get_pool_property}, + types::TData, +}; + +pub fn get_pool_key_descs( + i: &mut IterAppend<'_>, + p: &PropInfo<'_, MTSync, TData>, +) -> Result<(), MethodErr> { + get_pool_property(i, p, |(_, _, pool)| Ok(shared::pool_key_descs_prop(pool))) +} + +pub fn get_pool_clevis_infos( + i: &mut IterAppend<'_>, + p: &PropInfo<'_, MTSync, TData>, +) -> Result<(), MethodErr> { + get_pool_property(i, p, |(_, _, pool)| { + Ok(shared::pool_clevis_infos_prop(pool)) + }) +} diff --git a/src/dbus_api/pool/prop_conv.rs b/src/dbus_api/pool/prop_conv.rs index 512872cf9a..6143f77026 100644 --- a/src/dbus_api/pool/prop_conv.rs +++ b/src/dbus_api/pool/prop_conv.rs @@ -2,19 +2,26 @@ // 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::arg::{RefArg, Variant}; +use either::Either; + use devicemapper::Bytes; use crate::{ dbus_api::util::option_to_tuple, - engine::{ActionAvailability, PoolEncryptionInfo}, + engine::{ActionAvailability, EncryptionInfo, PoolEncryptionInfo}, stratis::StratisResult, }; /// Convert an encryption information data structure to a /// D-Bus type. -fn enc_to_prop(ei: Option, f: F, default: T) -> (bool, (bool, T)) +fn enc_to_prop( + ei: Option>, + f: F, + default: T, +) -> (bool, (bool, T)) where - F: Fn(PoolEncryptionInfo) -> StratisResult>, + F: Fn(Either) -> StratisResult>, T: Clone, { option_to_tuple( @@ -28,29 +35,102 @@ where /// Fetch the key description and handle converting it into a /// D-Bus type. -pub fn key_desc_to_prop(ei: Option) -> (bool, (bool, String)) { +pub fn key_desc_to_prop( + ei: Option>, +) -> (bool, (bool, String)) { enc_to_prop( ei, |ei| { - ei.key_description() - .map(|kd_opt| kd_opt.map(|kd| kd.as_application_str().to_string())) + ei.either( + |ei| { + Ok(ei + .single_key_description() + .map(|(_, kd)| kd.as_application_str().to_owned())) + }, + |pei| { + pei.key_description() + .map(|opt| opt.map(|k| k.as_application_str().to_owned())) + }, + ) }, String::new(), ) } +/// Fetch the key descriptions and handle converting it into a +/// D-Bus type. +pub fn key_descs_to_prop( + eei: Option>, +) -> Variant> { + match eei { + Some(Either::Left(ei)) => Variant(Box::new( + ei.all_key_descriptions() + .map(|(i, kd)| (*i, kd.as_application_str().to_string())) + .collect::>(), + )), + Some(Either::Right(pei)) => Variant(Box::new(option_to_tuple( + pei.key_description() + .map(|kd_opt| { + option_to_tuple( + kd_opt.map(|kd| kd.as_application_str().to_string()), + String::new(), + ) + }) + .ok(), + (false, String::new()), + ))), + None => Variant(Box::new("Unencrypted".to_string())), + } +} + /// Fetch the Clevis information and handle converting it into a /// D-Bus type. -pub fn clevis_info_to_prop(ei: Option) -> (bool, (bool, (String, String))) { +pub fn clevis_info_to_prop( + ei: Option>, +) -> (bool, (bool, (String, String))) { enc_to_prop( ei, |ei| { - ei.clevis_info() - .map(|ci_opt| ci_opt.map(|(pin, cfg)| (pin.to_owned(), cfg.to_string()))) + ei.either( + |ei| { + Ok(ei + .single_clevis_info() + .map(|(_, (pin, ci))| (pin.to_owned(), ci.to_string()))) + }, + |pei| { + pei.clevis_info() + .map(|opt| opt.map(|(pin, ci)| (pin.to_owned(), ci.to_string()))) + }, + ) }, (String::new(), String::new()), ) } +/// Fetch the Clevis infos and handle converting it into a +/// D-Bus type. +pub fn clevis_infos_to_prop( + eei: Option>, +) -> Variant> { + match eei { + Some(Either::Left(ei)) => Variant(Box::new( + ei.all_clevis_infos() + .map(|(i, (pin, config))| (*i, (pin.to_owned(), config.to_string()))) + .collect::>(), + )), + Some(Either::Right(pei)) => Variant(Box::new(option_to_tuple( + pei.clevis_info() + .map(|ci_opt| { + option_to_tuple( + ci_opt.map(|(pin, config)| (pin.to_owned(), config.to_string())), + (String::new(), String::new()), + ) + }) + .ok(), + (false, (String::new(), String::new())), + ))), + None => Variant(Box::new("Unencrypted".to_string())), + } +} /// Generate D-Bus representation of pool state property. #[inline] diff --git a/src/dbus_api/pool/shared.rs b/src/dbus_api/pool/shared.rs index 41100fca0d..b77fa4c639 100644 --- a/src/dbus_api/pool/shared.rs +++ b/src/dbus_api/pool/shared.rs @@ -5,7 +5,7 @@ use std::{fmt::Display, path::Path}; use dbus::{ - arg::{Array, IterAppend}, + arg::{Array, IterAppend, RefArg, Variant}, Message, }; use dbus_tree::{MTSync, MethodErr, MethodInfo, MethodResult, PropInfo, Tree}; @@ -283,12 +283,22 @@ pub fn pool_avail_actions_prop(pool: &dyn Pool) -> String { /// Generate D-Bus representation of a pool key description property. pub fn pool_key_desc_prop(pool: &dyn Pool) -> (bool, (bool, String)) { - prop_conv::key_desc_to_prop(pool.encryption_info_legacy()) + prop_conv::key_desc_to_prop(pool.encryption_info()) +} + +/// Generate D-Bus representation of a pool key descriptions property. +pub fn pool_key_descs_prop(pool: &dyn Pool) -> Variant> { + prop_conv::key_descs_to_prop(pool.encryption_info()) } /// Generate D-Bus representation of a pool Clevis info property. pub fn pool_clevis_info_prop(pool: &dyn Pool) -> (bool, (bool, (String, String))) { - prop_conv::clevis_info_to_prop(pool.encryption_info_legacy()) + prop_conv::clevis_info_to_prop(pool.encryption_info()) +} + +/// Generate D-Bus representation of a pool Clevis infos property. +pub fn pool_clevis_infos_prop(pool: &dyn Pool) -> Variant> { + prop_conv::clevis_infos_to_prop(pool.encryption_info()) } /// Generate D-Bus representation of a boolean indicating whether the pool diff --git a/src/dbus_api/tree.rs b/src/dbus_api/tree.rs index 4ffab6c470..c094493c88 100644 --- a/src/dbus_api/tree.rs +++ b/src/dbus_api/tree.rs @@ -18,6 +18,7 @@ use dbus::{ Path, }; use dbus_tree::{MTSync, ObjectPath}; +use either::Either; use tokio::sync::{broadcast::Receiver, mpsc::UnboundedReceiver}; use devicemapper::{Bytes, Sectors}; @@ -44,12 +45,14 @@ use crate::{ util::{poll_exit_and_future, thread_safe_to_dbus_sendable}, }, engine::{ - ActionAvailability, DevUuid, FilesystemUuid, LockedPoolsInfo, PoolEncryptionInfo, PoolUuid, - StoppedPoolsInfo, StratisUuid, + ActionAvailability, DevUuid, EncryptionInfo, FilesystemUuid, LockedPoolsInfo, + PoolEncryptionInfo, PoolUuid, StoppedPoolsInfo, StratisUuid, }, stratis::{StratisError, StratisResult}, }; +use super::pool::prop_conv::{clevis_infos_to_prop, key_descs_to_prop}; + type PropertySignal = HashMap>>, Vec)>; /// Handler for a D-Bus tree. @@ -412,56 +415,76 @@ impl DbusTreeHandler { } /// Handle a change of the key description for a pool in the engine. - fn handle_pool_key_desc_change(&self, item: Path<'static>, ei: Option) { - let kd_prop = key_desc_to_prop(ei); + fn handle_pool_key_desc_change( + &self, + item: Path<'static>, + ei: Option>, + ) { + let kd_prop = key_desc_to_prop(ei.clone().map(|either| either.map_left(|(_, ei)| ei))); + if ei + .as_ref() + .map(|either| { + either.as_ref().left().map(|(b, _)| *b).unwrap_or(false) || either.is_right() + }) + .expect("Should be encrypted to send signal") + && self + .property_changed_invalidated_signal( + &item, + prop_hashmap! { + consts::POOL_INTERFACE_NAME_3_0 => { + Vec::new(), + consts::POOL_KEY_DESC_PROP.to_string() => + box_variant!(kd_prop.clone()) + }, + consts::POOL_INTERFACE_NAME_3_1 => { + Vec::new(), + consts::POOL_KEY_DESC_PROP.to_string() => + box_variant!(kd_prop.clone()) + }, + consts::POOL_INTERFACE_NAME_3_2 => { + Vec::new(), + consts::POOL_KEY_DESC_PROP.to_string() => + box_variant!(kd_prop.clone()) + }, + consts::POOL_INTERFACE_NAME_3_3 => { + Vec::new(), + consts::POOL_KEY_DESC_PROP.to_string() => + box_variant!(kd_prop.clone()) + }, + consts::POOL_INTERFACE_NAME_3_4 => { + Vec::new(), + consts::POOL_KEY_DESC_PROP.to_string() => + box_variant!(kd_prop.clone()) + }, + consts::POOL_INTERFACE_NAME_3_5 => { + Vec::new(), + consts::POOL_KEY_DESC_PROP.to_string() => + box_variant!(kd_prop.clone()) + }, + consts::POOL_INTERFACE_NAME_3_6 => { + Vec::new(), + consts::POOL_KEY_DESC_PROP.to_string() => + 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()) + } + }, + ) + .is_err() + { + warn!("Signal on pool key description change was not sent to the D-Bus client"); + } if self .property_changed_invalidated_signal( &item, prop_hashmap! { - consts::POOL_INTERFACE_NAME_3_0 => { - Vec::new(), - consts::POOL_KEY_DESC_PROP.to_string() => - box_variant!(kd_prop.clone()) - }, - consts::POOL_INTERFACE_NAME_3_1 => { - Vec::new(), - consts::POOL_KEY_DESC_PROP.to_string() => - box_variant!(kd_prop.clone()) - }, - consts::POOL_INTERFACE_NAME_3_2 => { - Vec::new(), - consts::POOL_KEY_DESC_PROP.to_string() => - box_variant!(kd_prop.clone()) - }, - consts::POOL_INTERFACE_NAME_3_3 => { - Vec::new(), - consts::POOL_KEY_DESC_PROP.to_string() => - box_variant!(kd_prop.clone()) - }, - consts::POOL_INTERFACE_NAME_3_4 => { - Vec::new(), - consts::POOL_KEY_DESC_PROP.to_string() => - box_variant!(kd_prop.clone()) - }, - consts::POOL_INTERFACE_NAME_3_5 => { - Vec::new(), - consts::POOL_KEY_DESC_PROP.to_string() => - box_variant!(kd_prop.clone()) - }, - consts::POOL_INTERFACE_NAME_3_6 => { - Vec::new(), - consts::POOL_KEY_DESC_PROP.to_string() => - 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) + consts::POOL_KEY_DESCS_PROP.to_string() => + key_descs_to_prop(ei.clone().map(|either| either.map_left(|(_, ei)| ei))) } }, ) @@ -471,57 +494,78 @@ impl DbusTreeHandler { } } - /// Handle a change of the key description for a pool in the engine. - fn handle_pool_clevis_info_change(&self, item: Path<'static>, ei: Option) { - let ci_prop = clevis_info_to_prop(ei); + /// Handle a change of the Clevis info for a pool in the engine. + fn handle_pool_clevis_info_change( + &self, + item: Path<'static>, + ei: Option>, + ) { + let ci_prop = clevis_info_to_prop(ei.clone().map(|either| either.map_left(|(_, ei)| ei))); + + if ei + .as_ref() + .map(|either| { + either.as_ref().left().map(|(b, _)| *b).unwrap_or(false) || either.is_right() + }) + .expect("Should be encrypted to send signal") + && self + .property_changed_invalidated_signal( + &item, + prop_hashmap! { + consts::POOL_INTERFACE_NAME_3_0 => { + Vec::new(), + consts::POOL_CLEVIS_INFO_PROP.to_string() => + box_variant!(ci_prop.clone()) + }, + consts::POOL_INTERFACE_NAME_3_1 => { + Vec::new(), + consts::POOL_CLEVIS_INFO_PROP.to_string() => + box_variant!(ci_prop.clone()) + }, + consts::POOL_INTERFACE_NAME_3_2 => { + Vec::new(), + consts::POOL_CLEVIS_INFO_PROP.to_string() => + box_variant!(ci_prop.clone()) + }, + consts::POOL_INTERFACE_NAME_3_3 => { + Vec::new(), + consts::POOL_CLEVIS_INFO_PROP.to_string() => + box_variant!(ci_prop.clone()) + }, + consts::POOL_INTERFACE_NAME_3_4 => { + Vec::new(), + consts::POOL_CLEVIS_INFO_PROP.to_string() => + box_variant!(ci_prop.clone()) + }, + consts::POOL_INTERFACE_NAME_3_5 => { + Vec::new(), + consts::POOL_CLEVIS_INFO_PROP.to_string() => + box_variant!(ci_prop.clone()) + }, + consts::POOL_INTERFACE_NAME_3_6 => { + Vec::new(), + consts::POOL_CLEVIS_INFO_PROP.to_string() => + 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()) + } + }, + ) + .is_err() + { + warn!("Signal on pool Clevis information change was not sent to the D-Bus client"); + } if self .property_changed_invalidated_signal( &item, prop_hashmap! { - consts::POOL_INTERFACE_NAME_3_0 => { - Vec::new(), - consts::POOL_CLEVIS_INFO_PROP.to_string() => - box_variant!(ci_prop.clone()) - }, - consts::POOL_INTERFACE_NAME_3_1 => { - Vec::new(), - consts::POOL_CLEVIS_INFO_PROP.to_string() => - box_variant!(ci_prop.clone()) - }, - consts::POOL_INTERFACE_NAME_3_2 => { - Vec::new(), - consts::POOL_CLEVIS_INFO_PROP.to_string() => - box_variant!(ci_prop.clone()) - }, - consts::POOL_INTERFACE_NAME_3_3 => { - Vec::new(), - consts::POOL_CLEVIS_INFO_PROP.to_string() => - box_variant!(ci_prop.clone()) - }, - consts::POOL_INTERFACE_NAME_3_4 => { - Vec::new(), - consts::POOL_CLEVIS_INFO_PROP.to_string() => - box_variant!(ci_prop.clone()) - }, - consts::POOL_INTERFACE_NAME_3_5 => { - Vec::new(), - consts::POOL_CLEVIS_INFO_PROP.to_string() => - box_variant!(ci_prop.clone()) - }, - consts::POOL_INTERFACE_NAME_3_6 => { - Vec::new(), - consts::POOL_CLEVIS_INFO_PROP.to_string() => - 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) + consts::POOL_CLEVIS_INFOS_PROP.to_string() => + clevis_infos_to_prop(ei.clone().map(|either| either.map_left(|(_, ei)| ei))) } }, ) diff --git a/src/dbus_api/types.rs b/src/dbus_api/types.rs index 328dbaf830..bfa2d66afc 100644 --- a/src/dbus_api/types.rs +++ b/src/dbus_api/types.rs @@ -18,6 +18,7 @@ use dbus::{ Path, }; use dbus_tree::{DataType, MTSync, ObjectPath, Tree}; +use either::Either; use tokio::sync::{ mpsc::UnboundedSender as TokioSender, OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock, }; @@ -27,10 +28,10 @@ use devicemapper::{Bytes, Sectors}; use crate::{ dbus_api::{connection::DbusConnectionHandler, tree::DbusTreeHandler, udev::DbusUdevHandler}, engine::{ - total_allocated, total_used, ActionAvailability, DevUuid, Diff, Engine, ExclusiveGuard, - FilesystemUuid, Lockable, LockedPoolsInfo, PoolDiff, PoolEncryptionInfo, PoolUuid, - SharedGuard, StoppedPoolsInfo, StratBlockDevDiff, StratFilesystemDiff, StratPoolDiff, - StratisUuid, ThinPoolDiff, + total_allocated, total_used, ActionAvailability, DevUuid, Diff, EncryptionInfo, Engine, + ExclusiveGuard, FilesystemUuid, Lockable, LockedPoolsInfo, PoolDiff, PoolEncryptionInfo, + PoolUuid, SharedGuard, StoppedPoolsInfo, StratBlockDevDiff, StratFilesystemDiff, + StratPoolDiff, StratisUuid, ThinPoolDiff, }, }; @@ -97,8 +98,14 @@ pub enum DbusAction { FsNameChange(Path<'static>, String), PoolNameChange(Path<'static>, String), PoolAvailActions(Path<'static>, ActionAvailability), - PoolKeyDescChange(Path<'static>, Option), - PoolClevisInfoChange(Path<'static>, Option), + PoolKeyDescChange( + Path<'static>, + Option>, + ), + PoolClevisInfoChange( + Path<'static>, + Option>, + ), PoolCacheChange(Path<'static>, bool), PoolFsLimitChange(Path<'static>, u64), PoolOverprovModeChange(Path<'static>, bool), @@ -297,7 +304,11 @@ impl DbusContext { } /// Send changed signal for KeyDesc property. - pub fn push_pool_key_desc_change(&self, item: &Path<'static>, ei: Option) { + pub fn push_pool_key_desc_change( + &self, + item: &Path<'static>, + ei: Option>, + ) { if let Err(e) = self .sender .send(DbusAction::PoolKeyDescChange(item.clone(), ei)) @@ -313,7 +324,7 @@ impl DbusContext { pub fn push_pool_clevis_info_change( &self, item: &Path<'static>, - ei: Option, + ei: Option>, ) { if let Err(e) = self .sender diff --git a/src/engine/engine.rs b/src/engine/engine.rs index 6d0f543825..03d6c815ff 100644 --- a/src/engine/engine.rs +++ b/src/engine/engine.rs @@ -193,7 +193,7 @@ pub trait Pool: Debug + Send + Sync { token_slot: OptionalTokenSlotInput, pin: &str, clevis_info: &Value, - ) -> StratisResult>; + ) -> StratisResult>; /// V1: Binds all devices in the pool to a given key description. /// * token_slot is always None @@ -207,7 +207,7 @@ pub trait Pool: Debug + Send + Sync { &mut self, token_slot: OptionalTokenSlotInput, key_desc: &KeyDescription, - ) -> StratisResult>; + ) -> StratisResult>; /// V1: Rebinds all devices in the pool to a given key description. /// * token_slot is always None diff --git a/src/engine/sim_engine/pool.rs b/src/engine/sim_engine/pool.rs index 73de8c9dc1..349eac86d0 100644 --- a/src/engine/sim_engine/pool.rs +++ b/src/engine/sim_engine/pool.rs @@ -311,7 +311,7 @@ impl Pool for SimPool { token_slot: OptionalTokenSlotInput, pin: &str, clevis_info: &Value, - ) -> StratisResult> { + ) -> StratisResult> { let encryption_info = match self.encryption_info.as_mut() { Some(ei) => ei, None => { @@ -361,14 +361,14 @@ impl Pool for SimPool { token_slot_to_add, UnlockMechanism::ClevisInfo((pin.to_owned(), clevis_info.to_owned())), )?; - Ok(CreateAction::Created(Clevis)) + Ok(CreateAction::Created((Clevis, token_slot_to_add))) } fn bind_keyring( &mut self, token_slot: OptionalTokenSlotInput, key_description: &KeyDescription, - ) -> StratisResult> { + ) -> StratisResult> { let encryption_info = match self.encryption_info.as_mut() { Some(ei) => ei, None => { @@ -434,7 +434,7 @@ impl Pool for SimPool { token_slot_to_add, UnlockMechanism::KeyDesc(key_description.to_owned()), )?; - Ok(CreateAction::Created(Key)) + Ok(CreateAction::Created((Key, token_slot_to_add))) } fn unbind_keyring(&mut self, token_slot: Option) -> StratisResult> { diff --git a/src/engine/strat_engine/backstore/backstore/v2.rs b/src/engine/strat_engine/backstore/backstore/v2.rs index 761a56b675..9510af2092 100644 --- a/src/engine/strat_engine/backstore/backstore/v2.rs +++ b/src/engine/strat_engine/backstore/backstore/v2.rs @@ -936,7 +936,7 @@ impl Backstore { token_slot: OptionalTokenSlotInput, pin: &str, clevis_info: &Value, - ) -> StratisResult { + ) -> StratisResult> { let handle = self .enc .as_mut() @@ -952,7 +952,7 @@ impl Backstore { let ci = handle.encryption_info().single_clevis_info(); if let Some((_, (existing_pin, existing_info))) = ci { if existing_pin.as_str() == pin { - Ok(false) + Ok(None) } else { Err(StratisError::Msg(format!( "Crypt device has already been bound with pin {existing_pin} and config {existing_info}; \ @@ -960,8 +960,7 @@ impl Backstore { ))) } } else { - handle.bind_clevis(None, pin, clevis_info)?; - Ok(true) + handle.bind_clevis(None, pin, clevis_info).map(Some) } } OptionalTokenSlotInput::Some(k) => { @@ -970,7 +969,7 @@ impl Backstore { let ci = handle.encryption_info().get_info(k); if let Some(UnlockMechanism::ClevisInfo((existing_pin, existing_info))) = ci { if existing_pin == pin { - Ok(false) + Ok(None) } else { Err(StratisError::Msg(format!( "Crypt device has already been bound with pin {existing_pin} and config {existing_info}; \ @@ -978,16 +977,14 @@ impl Backstore { ))) } } else { - handle.bind_clevis(Some(k), pin, clevis_info)?; - Ok(true) + handle.bind_clevis(Some(k), pin, clevis_info).map(Some) } } OptionalTokenSlotInput::None => { // Because idemptotence is checked based on pin, we can't reliably check whether // the binding has already been applied when no token slot is specified. As a // result, we default to adding the new config unless a token slot is specified. - handle.bind_clevis(None, pin, clevis_info)?; - Ok(true) + handle.bind_clevis(None, pin, clevis_info).map(Some) } } } @@ -1087,7 +1084,7 @@ impl Backstore { &mut self, token_slot: OptionalTokenSlotInput, key_desc: &KeyDescription, - ) -> StratisResult { + ) -> StratisResult> { let handle = self .enc .as_mut() @@ -1103,7 +1100,7 @@ impl Backstore { let info = handle.encryption_info().single_key_description(); if let Some((_, kd)) = info { if kd == key_desc { - Ok(false) + Ok(None) } else { Err(StratisError::Msg(format!( "Crypt device has already been bound with key description {}; \ @@ -1113,8 +1110,7 @@ impl Backstore { ))) } } else { - handle.bind_keyring(None, key_desc)?; - Ok(true) + handle.bind_keyring(None, key_desc).map(Some) } } OptionalTokenSlotInput::Some(k) => { @@ -1123,7 +1119,7 @@ impl Backstore { let info = handle.encryption_info().get_info(k); if let Some(UnlockMechanism::KeyDesc(ref kd)) = info { if kd == key_desc { - Ok(false) + Ok(None) } else { Err(StratisError::Msg(format!( "Crypt device has already been bound with key description {}; \ @@ -1133,8 +1129,7 @@ impl Backstore { ))) } } else { - handle.bind_keyring(Some(k), key_desc)?; - Ok(true) + handle.bind_keyring(Some(k), key_desc).map(Some) } } OptionalTokenSlotInput::None => { @@ -1145,10 +1140,9 @@ impl Backstore { .all_key_descriptions() .find(|(_, kd)| *kd == key_desc); if existing_config.is_some() { - Ok(false) + Ok(None) } else { - handle.bind_keyring(None, key_desc)?; - Ok(true) + handle.bind_keyring(None, key_desc).map(Some) } } } @@ -1587,7 +1581,7 @@ mod tests { "tang", &json!({"url": env::var("TANG_URL").expect("TANG_URL env var required")}) ), - Ok(false) + Ok(None) ); assert_matches!( @@ -1596,7 +1590,7 @@ mod tests { "tang", &json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}) ), - Ok(false) + Ok(None) ); invariant(&backstore); @@ -1680,7 +1674,7 @@ mod tests { OptionalTokenSlotInput::Some(ci_slot), "tang", &json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), - ).unwrap() { + ).unwrap().is_some() { panic!( "Clevis bind idempotence test failed" ); @@ -1691,6 +1685,7 @@ mod tests { if backstore .bind_keyring(OptionalTokenSlotInput::None, key_desc) .unwrap() + .is_some() { panic!("Keyring bind idempotence test failed") } @@ -1698,6 +1693,7 @@ mod tests { if backstore .bind_keyring(OptionalTokenSlotInput::Some(kd_slot), key_desc) .unwrap() + .is_some() { panic!("Keyring bind idempotence test failed") } @@ -1722,11 +1718,11 @@ mod tests { invariant(&backstore); - if !backstore.bind_clevis( + if backstore.bind_clevis( OptionalTokenSlotInput::Some(10), "tang", &json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}), - ).unwrap() { + ).unwrap().is_none() { panic!( "Clevis bind test failed" ); @@ -1752,16 +1748,18 @@ mod tests { invariant(&backstore); - if !backstore + if backstore .bind_keyring(OptionalTokenSlotInput::Some(11), key_desc) .unwrap() + .is_none() { panic!("Keyring bind test failed"); } - if !backstore + if backstore .bind_keyring(OptionalTokenSlotInput::Some(12), key_desc) .unwrap() + .is_none() { panic!("Keyring bind test failed"); } diff --git a/src/engine/strat_engine/crypt/handle/v2.rs b/src/engine/strat_engine/crypt/handle/v2.rs index 93af52cecd..0d65f41ae1 100644 --- a/src/engine/strat_engine/crypt/handle/v2.rs +++ b/src/engine/strat_engine/crypt/handle/v2.rs @@ -553,14 +553,11 @@ impl CryptHandle { token_slot: Option, pin: &str, json: &Value, - ) -> StratisResult<()> { + ) -> StratisResult { let mut json_owned = json.clone(); let yes = interpret_clevis_config(pin, &mut json_owned)?; - let t = match token_slot { - Some(t) => t, - None => self.metadata.encryption_info.free_token_slot(), - }; + let old_encryption_info = self.encryption_info(); let (_, key) = get_passphrase( &mut acquire_crypt_device(self.luks2_device_path())?, @@ -570,17 +567,25 @@ impl CryptHandle { clevis_luks_bind( self.luks2_device_path(), &Either::Right(key), - Some(t), + token_slot, pin, &json_owned, yes, )?; + + let encryption_info = + encryption_info_from_metadata(&mut acquire_crypt_device(self.luks2_device_path())?)?; + + let new_slot = encryption_info + .diff(old_encryption_info) + .expect("just added a token slot"); + self.metadata.encryption_info.add_info( - t, + new_slot, UnlockMechanism::ClevisInfo((pin.to_owned(), json.to_owned())), )?; - Ok(()) + Ok(new_slot) } /// Unbind the given device using clevis. @@ -646,7 +651,7 @@ impl CryptHandle { &mut self, token_slot: Option, key_desc: &KeyDescription, - ) -> StratisResult<()> { + ) -> StratisResult { let mut device = self.acquire_crypt_device()?; let (_, key) = get_passphrase(&mut device, self.encryption_info())?; @@ -655,12 +660,13 @@ impl CryptHandle { None => self.metadata.encryption_info.free_token_slot(), }; - add_keyring_keyslot(&mut device, Some(t), key_desc, Some(Either::Left(key)))?; + let token_slot = + add_keyring_keyslot(&mut device, Some(t), key_desc, Some(Either::Left(key)))?; self.metadata .encryption_info .add_info(t, UnlockMechanism::KeyDesc(key_desc.to_owned()))?; - Ok(()) + Ok(token_slot) } /// Remove keyring binding from the underlying LUKS2 volume. diff --git a/src/engine/strat_engine/crypt/shared.rs b/src/engine/strat_engine/crypt/shared.rs index d3d8e803ec..b19d40c55a 100644 --- a/src/engine/strat_engine/crypt/shared.rs +++ b/src/engine/strat_engine/crypt/shared.rs @@ -104,7 +104,7 @@ pub fn add_keyring_keyslot( token_slot: Option, key_description: &KeyDescription, pass: Option>, -) -> StratisResult<()> { +) -> StratisResult { let key = key_desc_to_passphrase(key_description)?; let keyslot = match pass { Some(Either::Left(ref pass)) => { @@ -157,7 +157,7 @@ pub fn add_keyring_keyslot( "Failed to assign the LUKS2 keyring token to the Stratis keyslot" ); - Ok(()) + Ok(new_token_slot) } /// Create a device handle and load the LUKS2 header into memory from diff --git a/src/engine/strat_engine/pool/dispatch.rs b/src/engine/strat_engine/pool/dispatch.rs index 89e5d659ad..861114683c 100644 --- a/src/engine/strat_engine/pool/dispatch.rs +++ b/src/engine/strat_engine/pool/dispatch.rs @@ -48,7 +48,7 @@ impl Pool for AnyPool { token_slot: OptionalTokenSlotInput, pin: &str, clevis_info: &Value, - ) -> StratisResult> { + ) -> StratisResult> { match self { AnyPool::V1(p) => p.bind_clevis(token_slot, pin, clevis_info), AnyPool::V2(p) => p.bind_clevis(token_slot, pin, clevis_info), @@ -59,7 +59,7 @@ impl Pool for AnyPool { &mut self, token_slot: OptionalTokenSlotInput, key_description: &KeyDescription, - ) -> StratisResult> { + ) -> StratisResult> { match self { AnyPool::V1(p) => p.bind_keyring(token_slot, key_description), AnyPool::V2(p) => p.bind_keyring(token_slot, key_description), diff --git a/src/engine/strat_engine/pool/v1.rs b/src/engine/strat_engine/pool/v1.rs index 95c63780b0..a3f86b1ca6 100644 --- a/src/engine/strat_engine/pool/v1.rs +++ b/src/engine/strat_engine/pool/v1.rs @@ -38,6 +38,7 @@ use crate::{ blockdev::{v1::StratBlockDev, InternalBlockDev}, ProcessedPathInfos, }, + crypt::{CLEVIS_LUKS_TOKEN_ID, LUKS2_TOKEN_ID}, liminal::DeviceSet, metadata::BDA, serde_structs::{FlexDevsSave, PoolSave, Recordable}, @@ -708,14 +709,14 @@ impl Pool for StratPool { token_slot: OptionalTokenSlotInput, pin: &str, clevis_info: &Value, - ) -> StratisResult> { + ) -> StratisResult> { if token_slot != OptionalTokenSlotInput::Legacy { return Err(StratisError::Msg("Specifying the token slot for binding is not supported in V1 pools; please migrate to V2 pools to use this feature".to_string())); } let changed = self.backstore.bind_clevis(pin, clevis_info)?; if changed { - Ok(CreateAction::Created(Clevis)) + Ok(CreateAction::Created((Clevis, CLEVIS_LUKS_TOKEN_ID))) } else { Ok(CreateAction::Identity) } @@ -727,14 +728,14 @@ impl Pool for StratPool { &mut self, token_slot: OptionalTokenSlotInput, key_description: &KeyDescription, - ) -> StratisResult> { + ) -> StratisResult> { if token_slot != OptionalTokenSlotInput::Legacy { return Err(StratisError::Msg("Specifying the token slot for binding is not supported in V1 pools; please migrate to V2 pools to use this feature".to_string())); } let changed = self.backstore.bind_keyring(key_description)?; if changed { - Ok(CreateAction::Created(Key)) + Ok(CreateAction::Created((Key, LUKS2_TOKEN_ID))) } else { Ok(CreateAction::Identity) } diff --git a/src/engine/strat_engine/pool/v2.rs b/src/engine/strat_engine/pool/v2.rs index 71263769b7..f76fa832b2 100644 --- a/src/engine/strat_engine/pool/v2.rs +++ b/src/engine/strat_engine/pool/v2.rs @@ -649,12 +649,11 @@ impl Pool for StratPool { token_slot: OptionalTokenSlotInput, pin: &str, clevis_info: &Value, - ) -> StratisResult> { + ) -> StratisResult> { let changed = self.backstore.bind_clevis(token_slot, pin, clevis_info)?; - if changed { - Ok(CreateAction::Created(Clevis)) - } else { - Ok(CreateAction::Identity) + match changed { + Some(t) => Ok(CreateAction::Created((Clevis, t))), + None => Ok(CreateAction::Identity), } } @@ -663,12 +662,11 @@ impl Pool for StratPool { &mut self, token_slot: OptionalTokenSlotInput, key_description: &KeyDescription, - ) -> StratisResult> { + ) -> StratisResult> { let changed = self.backstore.bind_keyring(token_slot, key_description)?; - if changed { - Ok(CreateAction::Created(Key)) - } else { - Ok(CreateAction::Identity) + match changed { + Some(t) => Ok(CreateAction::Created((Key, t))), + None => Ok(CreateAction::Identity), } } diff --git a/src/engine/types/actions.rs b/src/engine/types/actions.rs index dbd45cfcf2..5737d5949d 100644 --- a/src/engine/types/actions.rs +++ b/src/engine/types/actions.rs @@ -75,13 +75,13 @@ impl Display for CreateAction { } } -impl Display for CreateAction { +impl Display for CreateAction<(Clevis, u32)> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - CreateAction::Created(Clevis) => { + CreateAction::Created((_, t)) => { write!( f, - "Pool successfully bound to an unlocking mechanism using clevis" + "Pool successfully bound an unlocking mechanism to token slot {t} using clevis" ) } CreateAction::Identity => { @@ -94,13 +94,13 @@ impl Display for CreateAction { } } -impl Display for CreateAction { +impl Display for CreateAction<(Key, u32)> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - CreateAction::Created(Key) => { + CreateAction::Created((_, t)) => { write!( f, - "Pool successfully bound to a passphrase in the kernel keyring" + "Pool successfully bound to a passphrase to token slot {t} in the kernel keyring" ) } CreateAction::Identity => { diff --git a/src/engine/types/keys.rs b/src/engine/types/keys.rs index 4421e2bbe9..b25402eb30 100644 --- a/src/engine/types/keys.rs +++ b/src/engine/types/keys.rs @@ -390,6 +390,23 @@ impl EncryptionInfo { pub fn is_empty(&self) -> bool { self.encryption_infos.is_empty() } + + /// Returns the values that are in &self but not in &other. + pub fn diff(&self, other: &Self) -> Option { + self.encryption_infos + .keys() + .cloned() + .collect::>() + .difference( + &other + .encryption_infos + .keys() + .cloned() + .collect::>(), + ) + .next() + .cloned() + } } impl IntoIterator for EncryptionInfo { From 6ba40ad65843c516e402e0ec013b8f5d5e078005 Mon Sep 17 00:00:00 2001 From: John Baublitz Date: Tue, 16 Jul 2024 14:21:11 -0400 Subject: [PATCH 3/4] Add online encrypt ability for Stratis pools --- src/dbus_api/api/manager_3_8/methods.rs | 7 +- src/dbus_api/pool/mod.rs | 1 + src/dbus_api/pool/pool_3_0/api.rs | 2 +- src/dbus_api/pool/pool_3_8/api.rs | 31 +++- src/dbus_api/pool/pool_3_8/methods.rs | 122 ++++++++++++- src/dbus_api/pool/pool_3_8/mod.rs | 5 +- src/dbus_api/tree.rs | 63 +++++++ src/dbus_api/types.rs | 20 +++ src/engine/engine.rs | 22 ++- src/engine/sim_engine/engine.rs | 37 +--- src/engine/sim_engine/mod.rs | 1 + src/engine/sim_engine/pool.rs | 23 ++- src/engine/sim_engine/shared.rs | 42 +++++ .../strat_engine/backstore/backstore/v2.rs | 20 ++- src/engine/strat_engine/crypt/handle/v2.rs | 167 +++++++++++++++++- src/engine/strat_engine/crypt/shared.rs | 3 +- src/engine/strat_engine/pool/dispatch.rs | 19 +- src/engine/strat_engine/pool/v1.rs | 41 +++-- src/engine/strat_engine/pool/v2.rs | 44 ++++- .../strat_engine/thinpool/dm_structs.rs | 55 +++--- .../strat_engine/thinpool/filesystem.rs | 8 + src/engine/strat_engine/thinpool/thinpool.rs | 93 +++++++--- src/engine/types/actions.rs | 16 ++ src/engine/types/mod.rs | 31 +++- 24 files changed, 737 insertions(+), 136 deletions(-) create mode 100644 src/engine/sim_engine/shared.rs 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 349eac86d0..99940703fc 100644 --- a/src/engine/sim_engine/pool.rs +++ b/src/engine/sim_engine/pool.rs @@ -21,16 +21,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}, }; @@ -870,6 +871,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 0d65f41ae1..d388771e7a 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()?; @@ -450,6 +457,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) } @@ -724,6 +746,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, + Path::new(&tmp_header), + 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( From 14e1d854ee9fb40a46916be07e8205bfbbbaab45 Mon Sep 17 00:00:00 2001 From: John Baublitz Date: Tue, 17 Sep 2024 15:32:38 -0400 Subject: [PATCH 4/4] Add online reencrypt ability for Stratis pools --- src/dbus_api/pool/mod.rs | 1 + src/dbus_api/pool/pool_3_8/api.rs | 12 +- src/dbus_api/pool/pool_3_8/methods.rs | 33 +++ src/dbus_api/pool/pool_3_8/mod.rs | 4 +- src/engine/engine.rs | 7 +- src/engine/sim_engine/pool.rs | 8 +- .../strat_engine/backstore/backstore/v1.rs | 20 ++ .../strat_engine/backstore/backstore/v2.rs | 14 ++ .../strat_engine/backstore/blockdev/v1.rs | 10 + src/engine/strat_engine/crypt/handle/v1.rs | 11 +- src/engine/strat_engine/crypt/handle/v2.rs | 69 +----- src/engine/strat_engine/crypt/shared.rs | 209 +++++++++++++++++- src/engine/strat_engine/pool/dispatch.rs | 11 +- src/engine/strat_engine/pool/v1.rs | 7 +- src/engine/strat_engine/pool/v2.rs | 10 +- src/engine/types/actions.rs | 9 + src/engine/types/mod.rs | 2 +- 17 files changed, 362 insertions(+), 75 deletions(-) diff --git a/src/dbus_api/pool/mod.rs b/src/dbus_api/pool/mod.rs index 339869e984..ac7d47227b 100644 --- a/src/dbus_api/pool/mod.rs +++ b/src/dbus_api/pool/mod.rs @@ -295,6 +295,7 @@ pub fn create_dbus_pool<'a>( .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_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)) diff --git a/src/dbus_api/pool/pool_3_8/api.rs b/src/dbus_api/pool/pool_3_8/api.rs index ec4db87dd8..b3789a3617 100644 --- a/src/dbus_api/pool/pool_3_8/api.rs +++ b/src/dbus_api/pool/pool_3_8/api.rs @@ -10,7 +10,7 @@ use crate::dbus_api::{ pool_3_8::{ methods::{ bind_clevis, bind_keyring, encrypt_pool, rebind_clevis, rebind_keyring, - unbind_clevis, unbind_keyring, + reencrypt_pool, unbind_clevis, unbind_keyring, }, props::{get_pool_clevis_infos, get_pool_key_descs}, }, @@ -128,3 +128,13 @@ pub fn encrypt_pool_method(f: &Factory, TData>) -> Method, 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 index 7f3519d5b6..898512c893 100644 --- a/src/dbus_api/pool/pool_3_8/methods.rs +++ b/src/dbus_api/pool/pool_3_8/methods.rs @@ -513,3 +513,36 @@ pub fn encrypt_pool(m: &MethodInfo<'_, MTSync, TData>) -> MethodResult { }; 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 index 28f1a2588b..7515193f9d 100644 --- a/src/dbus_api/pool/pool_3_8/mod.rs +++ b/src/dbus_api/pool/pool_3_8/mod.rs @@ -8,6 +8,6 @@ mod props; pub use api::{ 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, + key_descs_property, rebind_clevis_method, rebind_keyring_method, reencrypt_pool_method, + unbind_clevis_method, unbind_keyring_method, }; diff --git a/src/engine/engine.rs b/src/engine/engine.rs index 216d452eb3..9bdc247a35 100644 --- a/src/engine/engine.rs +++ b/src/engine/engine.rs @@ -25,8 +25,8 @@ use crate::{ 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, + PoolIdentifier, PoolUuid, PropChangeAction, Reencrypt, RegenAction, RenameAction, + ReportType, SetCreateAction, SetDeleteAction, SetUnlockAction, StartAction, StopAction, StoppedPoolsInfo, StratBlockDevDiff, StratFilesystemDiff, StratSigblockVersion, TokenUnlockMethod, UdevEngineEvent, UnlockMethod, }, @@ -396,6 +396,9 @@ pub trait Pool: Debug + Send + Sync { encryption_info: &InputEncryptionInfo, ) -> 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 99940703fc..60b7311b11 100644 --- a/src/engine/sim_engine/pool.rs +++ b/src/engine/sim_engine/pool.rs @@ -29,8 +29,8 @@ use crate::{ ActionAvailability, BlockDevTier, Clevis, CreateAction, DeleteAction, DevUuid, EncryptedDevice, EncryptionInfo, FilesystemUuid, GrowAction, InputEncryptionInfo, Key, KeyDescription, Name, OptionalTokenSlotInput, PoolDiff, PoolEncryptionInfo, PoolUuid, - PropChangeAction, RegenAction, RenameAction, SetCreateAction, SetDeleteAction, - StratSigblockVersion, UnlockMechanism, ValidatedIntegritySpec, + PropChangeAction, Reencrypt, RegenAction, RenameAction, SetCreateAction, + SetDeleteAction, StratSigblockVersion, UnlockMechanism, ValidatedIntegritySpec, }, }, stratis::{StratisError, StratisResult}, @@ -881,6 +881,10 @@ impl Pool for SimPool { 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 d61c5ae45a..4e3299d09a 100644 --- a/src/engine/strat_engine/backstore/backstore/v1.rs +++ b/src/engine/strat_engine/backstore/backstore/v1.rs @@ -862,6 +862,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 f25fa9c20c..9788cf5d93 100644 --- a/src/engine/strat_engine/backstore/backstore/v2.rs +++ b/src/engine/strat_engine/backstore/backstore/v2.rs @@ -1258,6 +1258,20 @@ impl Backstore { 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 dd628fca0f..5f346de826 100644 --- a/src/engine/strat_engine/backstore/blockdev/v1.rs +++ b/src/engine/strat_engine/backstore/blockdev/v1.rs @@ -416,6 +416,16 @@ 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 43af0cf138..3d0320f01a 100644 --- a/src/engine/strat_engine/crypt/handle/v1.rs +++ b/src/engine/strat_engine/crypt/handle/v1.rs @@ -45,7 +45,7 @@ use crate::{ check_luks2_token, clevis_decrypt, device_from_physical_path, encryption_info_from_metadata, ensure_inactive, ensure_wiped, get_keyslot_number, interpret_clevis_config, luks2_token_type_is_valid, - read_key, wipe_fallback, + read_key, reencrypt_shared, wipe_fallback, }, }, dm::DEVICEMAPPER_PATH, @@ -981,6 +981,15 @@ impl CryptHandle { Ok(()) } + /// Encrypt an unencrypted pool. + pub fn reencrypt(&self) -> StratisResult<()> { + reencrypt_shared( + &format_crypt_name(&self.metadata.identifiers.device_uuid).to_string(), + 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()?; diff --git a/src/engine/strat_engine/crypt/handle/v2.rs b/src/engine/strat_engine/crypt/handle/v2.rs index d388771e7a..0984599bc4 100644 --- a/src/engine/strat_engine/crypt/handle/v2.rs +++ b/src/engine/strat_engine/crypt/handle/v2.rs @@ -43,9 +43,10 @@ use crate::{ }, shared::{ acquire_crypt_device, activate, activate_by_token, add_keyring_keyslot, - clevis_decrypt, clevis_info_from_json, device_from_physical_path, + clevis_info_from_json, device_from_physical_path, encryption_info_from_metadata, ensure_wiped, get_keyslot_number, - interpret_clevis_config, read_key, wipe_fallback, + get_passphrase, interpret_clevis_config, read_key, reencrypt_shared, + wipe_fallback, }, }, device::blkdev_size, @@ -182,61 +183,6 @@ fn setup_crypt_handle( ))) } -/// Get one of the passphrases for the encrypted device. -fn get_passphrase( - device: &mut CryptDevice, - encryption_info: &EncryptionInfo, -) -> StratisResult<(c_uint, SizedKeyMemory)> { - for (ts, mech) in encryption_info.all_infos() { - match mech { - UnlockMechanism::KeyDesc(kd) => match read_key(kd) { - Ok(Some(pass)) => { - let keyslot = match get_keyslot_number(device, *ts) { - Ok(Some(ks)) => ks, - Ok(None) => { - warn!("Failed to find keyslot associated with the given token"); - continue; - } - Err(e) => { - warn!("Failure while finding keyslot associated with the given token: {e}"); - continue; - } - }; - return Ok((keyslot, pass)); - } - Ok(None) => { - info!("Key description was not in keyring; trying next unlock mechanism") - } - Err(e) => info!("Error searching keyring: {e}"), - }, - UnlockMechanism::ClevisInfo(_) => match clevis_decrypt(device, *ts) { - Ok(Some(pass)) => { - let keyslot = match get_keyslot_number(device, *ts) { - Ok(Some(ks)) => ks, - Ok(None) => { - warn!("Failed to find keyslot associated with the given token"); - continue; - } - Err(e) => { - warn!("Failure while finding keyslot associated with the given token: {e}"); - continue; - } - }; - return Ok((keyslot, pass)); - } - Ok(None) => { - info!("Failed to find the given token; trying next unlock method"); - } - Err(e) => info!("Error searching keyring: {e}"), - }, - } - } - - Err(StratisError::Msg( - "Unable to get passphrase for any available token slots".to_string(), - )) -} - /// Handle for performing all operations on an encrypted device. /// /// `Clone` is derived for this data structure because `CryptHandle` acquires @@ -873,6 +819,15 @@ impl CryptHandle { .map(|h| h.expect("should have crypt device after online encrypt")) } + /// Encrypt an unencrypted pool. + pub fn reencrypt(&self) -> StratisResult<()> { + reencrypt_shared( + &format_crypt_backstore_name(&self.metadata.pool_uuid).to_string(), + self.luks2_device_path(), + self.encryption_info(), + ) + } + /// 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 4f4e547816..7b2c4114bb 100644 --- a/src/engine/strat_engine/crypt/shared.rs +++ b/src/engine/strat_engine/crypt/shared.rs @@ -21,12 +21,14 @@ 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, + get_sector_size, register, set_debug_level, set_log_callback, CryptDevice, CryptInit, + CryptParamsLuks2, CryptParamsReencrypt, SafeMemHandle, TokenInput, }; use crate::{ @@ -36,8 +38,8 @@ use crate::{ crypt::consts::{ CLEVIS_RECURSION_LIMIT, CLEVIS_TANG_TRUST_URL, CLEVIS_TOKEN_NAME, CLEVIS_TOKEN_TYPE, DEFAULT_CRYPT_KEYSLOTS_SIZE, DEFAULT_CRYPT_METADATA_SIZE, - LUKS2_SECTOR_SIZE, LUKS2_TOKEN_ID, LUKS2_TOKEN_TYPE, TOKEN_KEYSLOTS_KEY, - TOKEN_TYPE_KEY, + LUKS2_SECTOR_SIZE, LUKS2_TOKEN_ID, LUKS2_TOKEN_TYPE, STRATIS_MEK_SIZE, + TOKEN_KEYSLOTS_KEY, TOKEN_TYPE_KEY, }, dm::get_dm, keys, @@ -969,6 +971,203 @@ pub fn clevis_decrypt( 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)> { + for (ts, mech) in encryption_info.all_infos() { + match mech { + UnlockMechanism::KeyDesc(kd) => match read_key(kd) { + Ok(Some(pass)) => { + let keyslot = match get_keyslot_number(device, *ts) { + Ok(Some(ks)) => ks, + Ok(None) => { + warn!("Failed to find keyslot associated with the given token"); + continue; + } + Err(e) => { + warn!("Failure while finding keyslot associated with the given token: {e}"); + continue; + } + }; + return Ok((keyslot, pass)); + } + Ok(None) => { + info!("Key description was not in keyring; trying next unlock mechanism") + } + Err(e) => info!("Error searching keyring: {e}"), + }, + UnlockMechanism::ClevisInfo(_) => match clevis_decrypt(device, *ts) { + Ok(Some(pass)) => { + let keyslot = match get_keyslot_number(device, *ts) { + Ok(Some(ks)) => ks, + Ok(None) => { + warn!("Failed to find keyslot associated with the given token"); + continue; + } + Err(e) => { + warn!("Failure while finding keyslot associated with the given token: {e}"); + continue; + } + }; + return Ok((keyslot, pass)); + } + Ok(None) => { + info!("Failed to find the given token; trying next unlock method"); + } + Err(e) => info!("Error searching keyring: {e}"), + }, + } + } + + Err(StratisError::Msg( + "Unable to get passphrase for any available token slots".to_string(), + )) +} + +/// Get all of the passphrases for the encrypted device for online reencryption. +pub fn get_all_passphrases( + device: &mut CryptDevice, + encryption_info: &EncryptionInfo, +) -> StratisResult> { + let mut passphrases = Vec::new(); + for (ts, mech) in encryption_info.all_infos() { + match mech { + UnlockMechanism::KeyDesc(kd) => match read_key(kd) { + Ok(Some(pass)) => { + passphrases.push((*ts, pass)); + } + Ok(None) => { + return Err(StratisError::Msg(format!( + "Key description {} was not in keyring", + kd.as_application_str(), + ))) + } + Err(e) => { + return Err(StratisError::Chained( + "Error searching keyring".to_string(), + Box::new(e), + )) + } + }, + UnlockMechanism::ClevisInfo(_) => match clevis_decrypt(device, *ts) { + Ok(Some(pass)) => { + passphrases.push((*ts, pass)); + } + Ok(None) => { + return Err(StratisError::Msg( + "Error getting Clevis passphrase".to_string(), + )) + } + Err(e) => { + return Err(StratisError::Chained( + "Error getting Clevis passphrase".to_string(), + Box::new(e), + )) + } + }, + } + } + + Ok(passphrases) +} + +pub fn reencrypt_shared( + device_name: &str, + luks2_path: &Path, + encryption_info: &EncryptionInfo, +) -> StratisResult<()> { + let mut device = acquire_crypt_device(luks2_path)?; + + let mut keys = get_all_passphrases(&mut device, encryption_info)?; + let (single_ts, single_key) = keys.pop().expect("Must have at least one unlock mechanism"); + let mut single_token_contents = device.token_handle().json_get(single_ts)?; + let single_keyslot = get_keyslot_number(&mut device, single_ts)?.ok_or_else(|| { + StratisError::Msg(format!( + "Could not find keyslot associated with token slot {single_ts}" + )) + })?; + + let single_new_keyslot = device.keyslot_handle().add_by_key( + None, + Some(Either::Right(STRATIS_MEK_SIZE)), + single_key.as_ref(), + CryptVolumeKey::NO_SEGMENT, + )?; + if let Some(obj) = single_token_contents.as_object_mut() { + obj.remove(TOKEN_KEYSLOTS_KEY); + obj.insert( + TOKEN_KEYSLOTS_KEY.to_string(), + Value::Array(vec![Value::String(single_new_keyslot.to_string())]), + ); + } + device + .token_handle() + .json_set(TokenInput::ReplaceToken(single_ts, &single_token_contents))?; + + let mut new_vk = SafeMemHandle::alloc(STRATIS_MEK_SIZE)?; + device + .volume_key_handle() + .get(single_new_keyslot, new_vk.as_mut(), single_key.as_ref())?; + + for (ts, key) in keys { + let mut token_contents = device.token_handle().json_get(ts)?; + + let new_keyslot = device.keyslot_handle().add_by_key( + None, + Some(Either::Left(new_vk.as_ref())), + key.as_ref(), + CryptVolumeKey::NO_SEGMENT | CryptVolumeKey::DIGEST_REUSE, + )?; + if let Some(obj) = token_contents.as_object_mut() { + obj.remove(TOKEN_KEYSLOTS_KEY); + obj.insert( + TOKEN_KEYSLOTS_KEY.to_string(), + Value::Array(vec![Value::String(new_keyslot.to_string())]), + ); + } + device + .token_handle() + .json_set(TokenInput::ReplaceToken(ts, &token_contents))?; + } + + 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( + Some(device_name), + single_key.as_ref(), + Some(single_keyslot), + single_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::empty(), + }, + )?; + + device.reencrypt_handle().reencrypt2::<()>(None, None)?; + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/engine/strat_engine/pool/dispatch.rs b/src/engine/strat_engine/pool/dispatch.rs index 244a328606..4034e1bdcc 100644 --- a/src/engine/strat_engine/pool/dispatch.rs +++ b/src/engine/strat_engine/pool/dispatch.rs @@ -17,8 +17,8 @@ use crate::{ ActionAvailability, BlockDevTier, Clevis, CreateAction, DeleteAction, DevUuid, EncryptedDevice, EncryptionInfo, FilesystemUuid, GrowAction, InputEncryptionInfo, Key, KeyDescription, Name, OptionalTokenSlotInput, PoolDiff, PoolEncryptionInfo, PoolUuid, - PropChangeAction, RegenAction, RenameAction, SetCreateAction, SetDeleteAction, - StratSigblockVersion, + PropChangeAction, Reencrypt, RegenAction, RenameAction, SetCreateAction, + SetDeleteAction, StratSigblockVersion, }, }, stratis::StratisResult, @@ -353,6 +353,13 @@ impl Pool for AnyPool { } } + 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 92c23a0114..d876608fec 100644 --- a/src/engine/strat_engine/pool/v1.rs +++ b/src/engine/strat_engine/pool/v1.rs @@ -47,7 +47,7 @@ use crate::{ ActionAvailability, BlockDevTier, Clevis, Compare, CreateAction, DeleteAction, DevUuid, Diff, EncryptedDevice, EncryptionInfo, FilesystemUuid, GrowAction, InputEncryptionInfo, Key, KeyDescription, Name, OffsetDirection, OptionalTokenSlotInput, PoolDiff, - PoolEncryptionInfo, PoolUuid, PropChangeAction, RegenAction, RenameAction, + PoolEncryptionInfo, PoolUuid, PropChangeAction, Reencrypt, RegenAction, RenameAction, SetCreateAction, SetDeleteAction, StratFilesystemDiff, StratPoolDiff, StratSigblockVersion, }, @@ -1306,6 +1306,11 @@ impl Pool for StratPool { )) } + 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 602a989f6b..b9fc9fc004 100644 --- a/src/engine/strat_engine/pool/v2.rs +++ b/src/engine/strat_engine/pool/v2.rs @@ -40,7 +40,7 @@ use crate::{ ActionAvailability, BlockDevTier, Clevis, Compare, CreateAction, DeleteAction, DevUuid, Diff, EncryptedDevice, EncryptionInfo, FilesystemUuid, GrowAction, InputEncryptionInfo, Key, KeyDescription, Name, OffsetDirection, OptionalTokenSlotInput, PoolDiff, - PoolEncryptionInfo, PoolUuid, PropChangeAction, RegenAction, RenameAction, + PoolEncryptionInfo, PoolUuid, PropChangeAction, Reencrypt, RegenAction, RenameAction, SetCreateAction, SetDeleteAction, SizedKeyMemory, StratFilesystemDiff, StratPoolDiff, StratSigblockVersion, TokenUnlockMethod, ValidatedIntegritySpec, }, @@ -1199,6 +1199,14 @@ impl Pool for StratPool { } } + #[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/types/actions.rs b/src/engine/types/actions.rs index a4ea0ff733..d7e73bb18d 100644 --- a/src/engine/types/actions.rs +++ b/src/engine/types/actions.rs @@ -851,3 +851,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 18a7f2d55a..f7a53919ee 100644 --- a/src/engine/types/mod.rs +++ b/src/engine/types/mod.rs @@ -28,7 +28,7 @@ pub use crate::{ types::{ actions::{ Clevis, CreateAction, DeleteAction, EncryptedDevice, EngineAction, GrowAction, Key, - MappingCreateAction, MappingDeleteAction, PropChangeAction, RegenAction, + MappingCreateAction, MappingDeleteAction, PropChangeAction, Reencrypt, RegenAction, RenameAction, SetCreateAction, SetDeleteAction, SetUnlockAction, StartAction, StopAction, ToDisplay, },