From 796d763300ecf23955b330e33351f0fee5f5645c Mon Sep 17 00:00:00 2001 From: Linda Yu Date: Fri, 15 Sep 2023 14:36:56 +0800 Subject: [PATCH] cdh/storage: mount encrypted alibaba cloud oss Signed-off-by: Linda Yu --- Cargo.lock | 16 ++ confidential-data-hub/storage/Cargo.toml | 26 ++++ confidential-data-hub/storage/src/error.rs | 14 ++ confidential-data-hub/storage/src/lib.rs | 9 ++ .../src/volume_type/alibaba_cloud_oss/mod.rs | 6 + .../src/volume_type/alibaba_cloud_oss/oss.rs | 142 ++++++++++++++++++ .../alibaba_cloud_oss/scripts/mount.sh | 63 ++++++++ .../storage/src/volume_type/mod.rs | 55 +++++++ 8 files changed, 331 insertions(+) create mode 100644 confidential-data-hub/storage/Cargo.toml create mode 100644 confidential-data-hub/storage/src/error.rs create mode 100644 confidential-data-hub/storage/src/lib.rs create mode 100644 confidential-data-hub/storage/src/volume_type/alibaba_cloud_oss/mod.rs create mode 100644 confidential-data-hub/storage/src/volume_type/alibaba_cloud_oss/oss.rs create mode 100755 confidential-data-hub/storage/src/volume_type/alibaba_cloud_oss/scripts/mount.sh create mode 100644 confidential-data-hub/storage/src/volume_type/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 689d0fadb..4443d9614 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,6 +278,7 @@ dependencies = [ "hyper-tls", "kbs-types", "log", + "nix 0.26.4", "occlum_dcap", "serde", "serde_json", @@ -886,6 +887,7 @@ dependencies = [ "secret", "serde_json", "sev 0.1.0", + "storage", "thiserror", "tokio", "ttrpc", @@ -5411,6 +5413,20 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "storage" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.21.4", + "rstest", + "secret", + "serde", + "serde_json", + "thiserror", + "tokio", +] + [[package]] name = "string_cache" version = "0.8.7" diff --git a/confidential-data-hub/storage/Cargo.toml b/confidential-data-hub/storage/Cargo.toml new file mode 100644 index 000000000..cac8492d4 --- /dev/null +++ b/confidential-data-hub/storage/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "storage" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = "1" +serde_json = "1" +thiserror.workspace = true +tokio = { workspace = true, features = ["fs"] } +anyhow.workspace = true +secret = { path = "../secret" } +base64.workspace = true + +[dev-dependencies] +rstest.workspace = true +tokio = { workspace = true, features = ["rt", "macros" ] } + +[build-dependencies] +anyhow.workspace = true + +[features] +default = ["aliyun"] +aliyun = [] diff --git a/confidential-data-hub/storage/src/error.rs b/confidential-data-hub/storage/src/error.rs new file mode 100644 index 000000000..80dd80c38 --- /dev/null +++ b/confidential-data-hub/storage/src/error.rs @@ -0,0 +1,14 @@ +// Copyright (c) 2023 Intel +// +// SPDX-License-Identifier: Apache-2.0 +// + +use thiserror::Error; + +pub type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum Error { + #[error("secure mount failed: {0}")] + SecureMountFailed(String), +} diff --git a/confidential-data-hub/storage/src/lib.rs b/confidential-data-hub/storage/src/lib.rs new file mode 100644 index 000000000..391985321 --- /dev/null +++ b/confidential-data-hub/storage/src/lib.rs @@ -0,0 +1,9 @@ +// Copyright (c) 2023 Intel +// +// SPDX-License-Identifier: Apache-2.0 +// + +pub mod error; +pub mod volume_type; + +pub use error::*; diff --git a/confidential-data-hub/storage/src/volume_type/alibaba_cloud_oss/mod.rs b/confidential-data-hub/storage/src/volume_type/alibaba_cloud_oss/mod.rs new file mode 100644 index 000000000..aa50157f8 --- /dev/null +++ b/confidential-data-hub/storage/src/volume_type/alibaba_cloud_oss/mod.rs @@ -0,0 +1,6 @@ +// Copyright (c) 2023 Intel +// +// SPDX-License-Identifier: Apache-2.0 +// + +pub mod oss; diff --git a/confidential-data-hub/storage/src/volume_type/alibaba_cloud_oss/oss.rs b/confidential-data-hub/storage/src/volume_type/alibaba_cloud_oss/oss.rs new file mode 100644 index 000000000..aa7c7ff29 --- /dev/null +++ b/confidential-data-hub/storage/src/volume_type/alibaba_cloud_oss/oss.rs @@ -0,0 +1,142 @@ +// Copyright (c) 2023 Intel +// +// SPDX-License-Identifier: Apache-2.0 +// + +use anyhow::anyhow; +use base64::{engine::general_purpose::STANDARD, Engine}; +use secret::secret::Secret; +use serde::{Deserialize, Serialize}; +use std::process::Command; + +use crate::{Error, Result}; + +const MOUNT_SCRIPT: &str = "/usr/local/bin/mount.sh"; + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct Oss { + #[serde(rename = "akId")] + pub ak_id: String, + #[serde(rename = "akSecret")] + pub ak_secret: String, + #[serde(default)] + pub annotations: String, + pub bucket: String, + #[serde(default)] + pub encrypted: String, + #[serde(rename = "encPasswd", default)] + pub enc_passwd: String, + #[serde(rename = "kmsKeyId", default)] + pub kms_key_id: String, + #[serde(rename = "otherOpts")] + pub other_opts: String, + pub path: String, + pub readonly: String, + #[serde(rename = "targetPath")] + pub target_path: String, + pub url: String, + #[serde(rename = "volumeId")] + pub volume_id: String, +} + +async fn unseal_secret(secret: Vec) -> Result> { + // TODO: verify the jws signature using the key specified by `kid` + // in header. Here we directly get the JWS payload + let payload = secret.split(|c| *c == b'.').nth(1).ok_or_else(|| { + Error::SecureMountFailed("illegal input sealed secret (not a JWS)".into()) + })?; + + let secret_json = STANDARD.decode(payload).map_err(|e| { + Error::SecureMountFailed(format!( + "illegal input sealed secret (JWS body is not standard base64 encoded): {e}" + )) + })?; + let secret: Secret = serde_json::from_slice(&secret_json).map_err(|e| { + Error::SecureMountFailed(format!( + "illegal input sealed secret format (json deseralization failed): {e}" + )) + })?; + + let res = secret + .unseal() + .await + .map_err(|e| Error::SecureMountFailed(format!("unseal failed: {e}")))?; + Ok(res) +} + +async fn get_plain(secret: String) -> Result { + if secret.starts_with("sealed.") { + let tmp = secret + .strip_prefix("sealed.") + .ok_or(anyhow!("strip_prefix \"sealed.\" failed")) + .map_err(|e| { + Error::SecureMountFailed(format!("strip_prefix \"sealed.\" failed: {e}")) + })?; + let unsealed = unseal_secret(tmp.into()) + .await + .map_err(|e| Error::SecureMountFailed(format!("unseal secret failed: {e}")))?; + + return String::from_utf8(unsealed) + .map_err(|e| Error::SecureMountFailed(format!("convert to String failed: {e}"))); + } + Err(Error::SecureMountFailed(format!( + "sealed secret format error!" + ))) +} + +impl Oss { + pub(crate) async fn mount(&self, source: String, mount_point: String) -> Result { + let plain_ak_id = get_plain(self.ak_id.clone()) + .await + .map_err(|e| Error::SecureMountFailed(format!("get_plain failed: {e}")))?; + let plain_ak_secret = get_plain(self.ak_secret.clone()) + .await + .map_err(|e| Error::SecureMountFailed(format!("get_plain failed: {e}")))?; + if self.encrypted == "gocryptfs" { + let plain_passwd = get_plain(self.enc_passwd.clone()) + .await + .map_err(|e| Error::SecureMountFailed(format!("get_plain failed: {e}")))?; + Command::new(MOUNT_SCRIPT) + .arg("-i") + .arg(plain_ak_id) + .arg("-s") + .arg(plain_ak_secret) + .arg("-b") + .arg(self.bucket.clone()) + .arg("-p") + .arg(self.path.clone()) + .arg("-o") + .arg(self.other_opts.clone()) + .arg("-d") + .arg(source) + .arg("-u") + .arg(self.url.clone()) + .arg("-g") + .arg(plain_passwd) + .spawn() + .expect("failed to mount encrypted oss"); + std::thread::sleep(std::time::Duration::from_secs(3)); + Ok(mount_point) + } else { + Command::new(MOUNT_SCRIPT) + .arg("-i") + .arg(plain_ak_id) + .arg("-s") + .arg(plain_ak_secret) + .arg("-b") + .arg(self.bucket.clone()) + .arg("-p") + .arg(self.path.clone()) + .arg("-o") + .arg(self.other_opts.clone()) + .arg("-d") + .arg(source) + .arg("-u") + .arg(self.url.clone()) + .spawn() + .expect("failed to mount oss"); + std::thread::sleep(std::time::Duration::from_secs(3)); + Ok(mount_point) + } + } +} diff --git a/confidential-data-hub/storage/src/volume_type/alibaba_cloud_oss/scripts/mount.sh b/confidential-data-hub/storage/src/volume_type/alibaba_cloud_oss/scripts/mount.sh new file mode 100755 index 000000000..4904d86cd --- /dev/null +++ b/confidential-data-hub/storage/src/volume_type/alibaba_cloud_oss/scripts/mount.sh @@ -0,0 +1,63 @@ +# Copyright (c) 2023 Intel +# +# SPDX-License-Identifier: Apache-2.0 +# + +#!/bin/bash + +set -x + +if [ $# -lt 14 ]; then + echo "ERROR: invalid parameters" + echo "Help: -i akid -s aksecret -b bucket -p path -o option -d mountpoint -u url -g passwd" + exit 0 +fi + +while getopts ":i:s:b:o:d:p:u:g:" opt; do + case $opt in + i) akid="$OPTARG" + ;; + s) aksecret="$OPTARG" + ;; + b) bucket="$OPTARG" + ;; + p) path="$OPTARG" + ;; + o) option="$OPTARG" + ;; + d) mountpoint="$OPTARG" + ;; + u) url="$OPTARG" + ;; + g) passwd="$OPTARG" + ;; + \?) echo "ERROR: invalid option" + exit 0 + ;; + esac +done + +ossfs_passwd=/tmp/ossfs-passwd +echo $bucket:$akid:$aksecret > $ossfs_passwd +chmod 600 $ossfs_passwd +oss_folder=/tmp/oss +if [ -z "$passwd" ]; then + oss_folder=$mountpoint +else + mkdir -p $oss_folder +fi + +# OSS mount +/usr/local/bin/ossfs $bucket:$path \ + $oss_folder -ourl=$url $option -o passwd_file=$ossfs_passwd + +# decrypt if needed +gocryptfs=/tmp/gocryptfs +if [ -n "$passwd" ]; then + echo $passwd + echo $passwd > $gocryptfs + /usr/local/bin/gocryptfs \ + $oss_folder/ \ + $mountpoint \ + -passfile $gocryptfs -nosyslog +fi diff --git a/confidential-data-hub/storage/src/volume_type/mod.rs b/confidential-data-hub/storage/src/volume_type/mod.rs new file mode 100644 index 000000000..8eced126e --- /dev/null +++ b/confidential-data-hub/storage/src/volume_type/mod.rs @@ -0,0 +1,55 @@ +// Copyright (c) 2023 Intel +// +// SPDX-License-Identifier: Apache-2.0 +// + +#[cfg(feature = "aliyun")] +pub mod alibaba_cloud_oss; + +#[cfg(feature = "aliyun")] +use self::alibaba_cloud_oss::oss::Oss; +use crate::{Error, Result}; +use anyhow::anyhow; + +#[derive(PartialEq, Clone, Debug)] +pub struct Storage { + pub driver: String, + pub driver_options: Vec, + pub source: String, + pub fstype: String, + pub options: Vec, + pub mount_point: String, +} + +impl Storage { + pub async fn mount(&self) -> Result { + for driver_option in &self.driver_options { + let (volumetype, metadata) = driver_option + .split_once("=") + .ok_or(anyhow!("split by \"=\" failed")) + .map_err(|e| Error::SecureMountFailed(format!("split by \"=\" failed: {e}")))?; + + match volumetype { + #[cfg(feature = "aliyun")] + "alibaba-cloud-oss" => { + let oss: Oss = serde_json::from_str(&metadata).map_err(|e| { + Error::SecureMountFailed(format!( + "illegal mount info format (json deseralization failed): {e}" + )) + })?; + return oss + .mount(self.source.clone(), self.mount_point.clone()) + .await; + } + &_ => { + return Err(Error::SecureMountFailed(format!( + "illegal mount info with unsupported volumetype" + ))) + } + }; + } + Err(Error::SecureMountFailed(format!( + "illegal mount info as no driver_options" + ))) + } +}