Skip to content

Commit

Permalink
cdh: support to encrypt block device
Browse files Browse the repository at this point in the history
Support to encrypt block device in cdh.

Fixed: confidential-containers#540 -- part II

Signed-off-by: ChengyuZhu6 <[email protected]>
  • Loading branch information
ChengyuZhu6 committed Jul 18, 2024
1 parent 3cbdf1b commit 0e0bad0
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 1 deletion.
1 change: 1 addition & 0 deletions confidential-data-hub/storage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ anyhow.workspace = true
async-trait.workspace = true
base64.workspace = true
log.workspace = true
kms = { path = "../kms", default-features = false }
rand = { workspace = true, optional = true }
secret = { path = "../secret" }
serde.workspace = true
Expand Down
3 changes: 3 additions & 0 deletions confidential-data-hub/storage/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub enum Error {
#[error("Error when mounting Aliyun OSS")]
AliyunOssError(#[from] volume_type::aliyun::error::AliyunError),

#[error("Error when mounting Block device")]
BlockDeviceError(#[from] volume_type::blockdevice::error::BlockDeviceError),

#[error("Failed to recognize the storage type")]
StorageTypeNotRecognized(#[from] strum::ParseError),
}
29 changes: 29 additions & 0 deletions confidential-data-hub/storage/src/volume_type/blockdevice/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2024 Intel
//
// SPDX-License-Identifier: Apache-2.0
//

use thiserror::Error;

pub type Result<T> = std::result::Result<T, BlockDeviceError>;

#[derive(Error, Debug)]
pub enum BlockDeviceError {
#[error("Error when getting encrypt/decrypt keys")]
GetKeysFailure(#[from] anyhow::Error),

#[error("LUKS decryption mount failed")]
LUKSfsMountFailed,

#[error("I/O error")]
IOError(#[from] std::io::Error),

#[error("Failed to mount block device")]
BlockDeviceMountFailed,

#[error("Serialize/Deserialize failed")]
SerdeError(#[from] serde_json::Error),

#[error("Failed to recognize the storage type")]
StorageTypeNotRecognized(#[from] strum::ParseError),
}
173 changes: 173 additions & 0 deletions confidential-data-hub/storage/src/volume_type/blockdevice/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Copyright (c) 2024 Intel
//
// SPDX-License-Identifier: Apache-2.0
//
pub mod error;

use super::SecureMount;
use anyhow::Context;
use async_trait::async_trait;
use base64::Engine;
use error::{BlockDeviceError, Result};
use kms::{Annotations, ProviderSettings};
use log::{debug, error};
use rand::{distributions::Alphanumeric, Rng};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use strum::{Display, EnumString};
use tokio::{
fs,
io::{AsyncReadExt, AsyncWriteExt},
process::Command,
};

/// LUKS encrypt storage binary
const LUKS_ENCRYPT_STORAGE_BIN: &str = "/usr/local/bin/luks-encrypt-storage";

#[derive(EnumString, Serialize, Deserialize, Display, Debug, PartialEq, Eq)]
pub enum BlockDeviceEncryptType {
#[strum(serialize = "luks")]
LUKS,
}

#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct BlockDeviceParameters {
/// The device number, formatted as "MAJ:MIN".
#[serde(rename = "deviceId")]
pub device_id: String,

/// The encryption type. Currently, only LUKS is supported.
#[serde(rename = "encryptType")]
pub encryption_type: BlockDeviceEncryptType,

/// Encryption key. If not set, generate a random 4096-byte key
#[serde(rename = "encryptKey")]
pub encryption_key: Option<String>,

/// Indicates whether to enable dm-integrity.
#[serde(rename = "dataIntegrity")]
pub data_integrity: String,
}
pub(crate) struct BlockDevice;

async fn random_encrypt_key() -> anyhow::Result<String> {
let mut buffer = vec![0u8; 4096];
rand::thread_rng().fill(&mut buffer[..]);
Ok(base64::engine::general_purpose::STANDARD.encode(&buffer))
}

async fn get_plaintext_key(resource: &str) -> anyhow::Result<String> {
if resource.starts_with("sealed.") {
debug!("detected sealed secret");
let unsealed = secret::unseal_secret(resource.as_bytes()).await?;
return String::from_utf8(unsealed).context("convert to String failed");
}

if resource.starts_with("kbs://") {
let secret = kms::new_getter("kbs", ProviderSettings::default())
.await?
.get_secret(resource, &Annotations::default())
.await
.map_err(|e| {
error!("get keys from kbs failed: {e}");
BlockDeviceError::GetKeysFailure(e.into())
})?;
return String::from_utf8(secret).context("convert to String failed");
}

Err(BlockDeviceError::GetKeysFailure(anyhow::anyhow!("unknown resource scheme")).into())
}

async fn create_storage_key_file(
storage_key_path: &str,
encrypt_key: Option<String>,
) -> anyhow::Result<()> {
let mut storage_key_file = fs::File::create(storage_key_path).await?;

let plain_key = match encrypt_key {
Some(encrypt_key) => get_plaintext_key(&encrypt_key).await?,
None => random_encrypt_key().await?,
};

storage_key_file.write_all(plain_key.as_bytes()).await?;
storage_key_file.flush().await?;
Ok(())
}

impl BlockDevice {
async fn real_mount(
&self,
options: &HashMap<String, String>,
_flags: &[String],
mount_point: &str,
) -> Result<()> {
// construct BlockDeviceParameters
let parameters = serde_json::to_string(options)?;
let bd_parameter: BlockDeviceParameters = serde_json::from_str(&parameters)?;

if bd_parameter.encryption_type == BlockDeviceEncryptType::LUKS {
let random_string: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(5)
.map(char::from)
.collect();
let storage_key_path = format!("/tmp/encrypted_storage_key_{}", random_string);
create_storage_key_file(&storage_key_path, bd_parameter.encryption_key).await?;

let parameters = vec![
bd_parameter.device_id,
mount_point.to_string(),
storage_key_path,
bd_parameter.data_integrity,
];

let mut encrypt_device = Command::new(LUKS_ENCRYPT_STORAGE_BIN)
.args(parameters)
.spawn()
.map_err(|e| {
error!("luks-encrypt-storage cmd fork failed: {e}");
BlockDeviceError::BlockDeviceMountFailed
})?;

let bd_res = encrypt_device.wait().await?;
if !bd_res.success() {
{
let mut stderr = String::new();
if let Some(mut err) = encrypt_device.stderr {
err.read_to_string(&mut stderr).await?;
error!("BlockDevice mount failed with stderr: {stderr}");
} else {
error!("BlockDevice mount failed");
}

return Err(BlockDeviceError::BlockDeviceMountFailed);
}
}
};

Ok(())
}
}

#[async_trait]
impl SecureMount for BlockDevice {
/// Mount the block device to the given `mount_point``.
///
/// If `bd.encrypt_type` is set to `LUKS`, the device will be formated as a LUKS-encrypted device.
/// Then use cryptsetup open the device and mount it to `mount_point` as plaintext.
///
/// This is a wrapper for inner function to convert error type.
async fn mount(
&self,
options: &HashMap<String, String>,
flags: &[String],
mount_point: &str,
) -> super::Result<()> {
self.real_mount(options, flags, mount_point)
.await
.map_err(|e| e.into())
}
}

#[cfg(test)]
mod tests {}
9 changes: 8 additions & 1 deletion confidential-data-hub/storage/src/volume_type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#[cfg(feature = "aliyun")]
pub mod aliyun;

pub mod blockdevice;
use std::{collections::HashMap, str::FromStr};

use crate::Result;
Expand All @@ -20,6 +20,7 @@ pub enum Volume {
#[cfg(feature = "aliyun")]
#[strum(serialize = "alibaba-cloud-oss")]
AliOss,
BlockDevice,
}

/// Indicating a mount point and its parameters.
Expand Down Expand Up @@ -61,6 +62,12 @@ impl Storage {
.await?;
Ok(self.mount_point.clone())
}
Volume::BlockDevice => {
let bd = blockdevice::BlockDevice {};
bd.mount(&self.options, &self.flags, &self.mount_point)
.await?;
Ok(self.mount_point.clone())
}
}
}
}

0 comments on commit 0e0bad0

Please sign in to comment.