Skip to content

Commit

Permalink
Prototype of online encrypt for Stratis pools
Browse files Browse the repository at this point in the history
  • Loading branch information
jbaublitz committed Jul 16, 2024
1 parent 76e6ccb commit 8180d36
Show file tree
Hide file tree
Showing 7 changed files with 322 additions and 124 deletions.
6 changes: 2 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,14 @@ optional = true
version = "0.9.3"
features = ["mutex"]
optional = true
git = "https://github.com/jbaublitz/libcryptsetup-rs"
branch = "reencrypt-fixes"

[dependencies.libcryptsetup-rs-sys]
version = "0.4.0"
optional = true
git = "https://github.com/jbaublitz/libcryptsetup-rs"
branch = "reencrypt-fixes"

[dependencies.libmount]
version = "0.1.9"
Expand Down
28 changes: 5 additions & 23 deletions src/engine/strat_engine/crypt/handle/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -42,8 +42,8 @@ use crate::{
},
shared::{
acquire_crypt_device, activate, add_keyring_keyslot, check_luks2_token,
clevis_info_from_metadata, device_from_physical_path, ensure_inactive,
ensure_wiped, get_keyslot_number, interpret_clevis_config,
clevis_decrypt, clevis_info_from_metadata, device_from_physical_path,
ensure_inactive, ensure_wiped, get_keyslot_number, interpret_clevis_config,
key_desc_from_metadata, luks2_token_type_is_valid, read_key, wipe_fallback,
},
},
Expand Down Expand Up @@ -956,7 +956,7 @@ impl CryptHandle {
/// Add a keyring binding to the underlying LUKS2 volume.
pub fn bind_keyring(&mut self, key_desc: &KeyDescription) -> StratisResult<()> {
let mut device = self.acquire_crypt_device()?;
let key = Self::clevis_decrypt(&mut device)?.ok_or_else(|| {
let key = clevis_decrypt(&mut device)?.ok_or_else(|| {
StratisError::Msg(
"The Clevis token appears to have been wiped outside of \
Stratis; cannot add a keyring key binding without an existing \
Expand Down Expand Up @@ -1030,24 +1030,6 @@ impl CryptHandle {
replace_pool_name(&mut device, pool_name)
}

/// Decrypt a Clevis passphrase and return it securely.
fn clevis_decrypt(device: &mut CryptDevice) -> StratisResult<Option<SizedKeyMemory>> {
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())
Expand Down Expand Up @@ -1097,7 +1079,7 @@ impl CryptHandle {
StratisError::Msg("Failed to find key with key description".to_string())
})?
} else if self.encryption_info().clevis_info().is_some() {
Self::clevis_decrypt(&mut crypt)?.expect("Already checked token exists")
clevis_decrypt(&mut crypt)?.expect("Already checked token exists")
} else {
unreachable!("Must be encrypted")
};
Expand Down
192 changes: 163 additions & 29 deletions src/engine/strat_engine/crypt/handle/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

use std::{
fmt::Debug,
fs::File,
fs::{File, OpenOptions},
io::Write,
iter::once,
path::{Path, PathBuf},
};
Expand All @@ -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)]
Expand All @@ -29,23 +35,24 @@ use crate::{
engine::{
engine::MAX_STRATIS_PASS_SIZE,
strat_engine::{
backstore::get_devno_from_path,
cmd::{clevis_decrypt, clevis_luks_bind, clevis_luks_regen, clevis_luks_unbind},
backstore::{backstore::v2, get_devno_from_path},
cmd::{clevis_luks_bind, clevis_luks_regen, clevis_luks_unbind},
crypt::{
consts::{
CLEVIS_LUKS_TOKEN_ID, DEFAULT_CRYPT_KEYSLOTS_SIZE, DEFAULT_CRYPT_METADATA_SIZE,
LUKS2_TOKEN_ID, STRATIS_MEK_SIZE,
},
shared::{
acquire_crypt_device, activate, add_keyring_keyslot, clevis_info_from_metadata,
device_from_physical_path, ensure_wiped, get_keyslot_number,
interpret_clevis_config, key_desc_from_metadata, luks2_token_type_is_valid,
wipe_fallback,
acquire_crypt_device, activate, add_keyring_keyslot, clevis_decrypt,
clevis_info_from_metadata, device_from_physical_path, ensure_wiped,
get_keyslot_number, get_passphrase, interpret_clevis_config,
key_desc_from_metadata, luks2_token_type_is_valid, wipe_fallback,
},
},
device::blkdev_size,
dm::DEVICEMAPPER_PATH,
names::format_crypt_backstore_name,
thinpool::{StratFilesystem, ThinPool},
},
types::{
DevicePath, EncryptionInfo, KeyDescription, PoolUuid, SizedKeyMemory, UnlockMethod,
Expand Down Expand Up @@ -404,10 +411,10 @@ impl CryptHandle {
Ok(())
}

fn initialize_with_err(
/// Format the device and initialize the unlock methods.
fn initialize_unlock_methods(
device: &mut CryptDevice,
physical_path: &Path,
pool_uuid: PoolUuid,
encryption_info: &EncryptionInfo,
luks2_params: Option<&CryptParamsLuks2>,
) -> StratisResult<()> {
Expand Down Expand Up @@ -440,6 +447,20 @@ impl CryptHandle {
}
};

Ok(())
}

/// Format the device and initialize the unlock methods, activating the device once it is
/// successfully set up.
fn initialize_with_err(
device: &mut CryptDevice,
physical_path: &Path,
pool_uuid: PoolUuid,
encryption_info: &EncryptionInfo,
luks2_params: Option<&CryptParamsLuks2>,
) -> StratisResult<()> {
Self::initialize_unlock_methods(device, physical_path, encryption_info, luks2_params)?;

let activation_name = format_crypt_backstore_name(&pool_uuid);
activate(
device,
Expand Down Expand Up @@ -639,7 +660,7 @@ impl CryptHandle {
/// Add a keyring binding to the underlying LUKS2 volume.
pub fn bind_keyring(&mut self, key_desc: &KeyDescription) -> StratisResult<()> {
let mut device = self.acquire_crypt_device()?;
let key = Self::clevis_decrypt(&mut device)?.ok_or_else(|| {
let key = clevis_decrypt(&mut device)?.ok_or_else(|| {
StratisError::Msg(
"The Clevis token appears to have been wiped outside of \
Stratis; cannot add a keyring key binding without an existing \
Expand Down Expand Up @@ -707,22 +728,135 @@ impl CryptHandle {
Ok(())
}

/// Decrypt a Clevis passphrase and return it securely.
fn clevis_decrypt(device: &mut CryptDevice) -> StratisResult<Option<SizedKeyMemory>> {
let mut token = match device.token_handle().json_get(CLEVIS_LUKS_TOKEN_ID).ok() {
Some(t) => t,
None => return Ok(None),
/// Encrypt an unencrypted pool.
#[allow(dead_code)]
pub fn encrypt(
thinpool: &mut ThinPool<v2::Backstore>,
pool_uuid: PoolUuid,
unencrypted_path: &Path,
encryption_info: &EncryptionInfo,
fs: &[StratFilesystem],
) -> StratisResult<Self> {
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 = fs
.iter()
.map(|fs| fs.block_size())
.collect::<StratisResult<Vec<u64>>>()?;
let min_sector = sectors.iter().min();
let sector_size = match min_sector {
Some(min) => convert_int!(*min, u64, u32)?,
None => {
let mut probe = BlkidProbe::new_from_filename(unencrypted_path)?;
let top = probe.get_topology()?;
convert_int!(top.get_logical_sector_size(), u64, u32)?
}
};
let jwe = token
.as_object_mut()
.and_then(|map| map.remove("jwe"))
.ok_or_else(|| {
StratisError::Msg(format!(
"Token slot {CLEVIS_LUKS_TOKEN_ID} is occupied but does not appear to be a Clevis \
token; aborting"
))
})?;
clevis_decrypt(&jwe).map(Some)
let params = CryptParamsLuks2 {
data_alignment: 0,
data_device: None,
integrity: None,
integrity_params: None,
pbkdf: None,
label: None,
sector_size,
subsystem: None,
};

Self::initialize_unlock_methods(
&mut device,
unencrypted_path,
encryption_info,
Some(&params),
)?;
let (keyslot, key) = get_passphrase(&mut device, encryption_info)?;
device.reencrypt_handle().reencrypt_init_by_passphrase(
None,
key.as_ref(),
None,
keyslot,
("aes", "xts-plain"),
CryptParamsReencrypt {
mode: CryptReencryptModeInfo::Encrypt,
direction: CryptReencryptDirectionInfo::Forward,
resilience: "checksum".to_string(),
hash: "sha256".to_string(),
data_shift: 0,
max_hotzone_size: 0,
device_size: 0,
luks2: CryptParamsLuks2 {
data_alignment: 0,
data_device: None,
integrity: None,
integrity_params: None,
pbkdf: None,
label: None,
sector_size,
subsystem: None,
},
flags: CryptReencrypt::INITIALIZE_ONLY,
},
)?;

let mut device = CryptInit::init(unencrypted_path)?;
device
.backup_handle()
.header_restore(Some(EncryptionFormat::Luks2), Path::new(&tmp_header))?;

let activation_name = &format_crypt_backstore_name(&pool_uuid).to_string();
device.activate_handle().activate_by_passphrase(
Some(activation_name),
None,
key.as_ref(),
CryptActivate::SHARED,
)?;

let devno = get_devno_from_path(Path::new(&format!("/dev/mapper/{activation_name}")))?;
thinpool.set_device(devno)?;

device.reencrypt_handle().reencrypt_init_by_passphrase(
Some(activation_name),
key.as_ref(),
None,
keyslot,
("aes", "xts-plain"),
CryptParamsReencrypt {
mode: CryptReencryptModeInfo::Encrypt,
direction: CryptReencryptDirectionInfo::Forward,
resilience: "checksum".to_string(),
hash: "sha256".to_string(),
data_shift: 0,
max_hotzone_size: 0,
device_size: 0,
luks2: CryptParamsLuks2 {
data_alignment: 0,
data_device: None,
integrity: None,
integrity_params: None,
pbkdf: None,
label: None,
sector_size,
subsystem: None,
},
flags: CryptReencrypt::RESUME_ONLY,
},
)?;
device.reencrypt_handle().reencrypt2::<()>(None, None)?;

CryptHandle::setup(unencrypted_path, pool_uuid, UnlockMethod::Any, None)
.map(|h| h.expect("should have crypt device after online encrypt"))
}

/// Deactivate the device referenced by the current device handle.
Expand Down
Loading

0 comments on commit 8180d36

Please sign in to comment.