Skip to content

Commit

Permalink
cdh/storage: mount encrypted alibaba cloud oss
Browse files Browse the repository at this point in the history
Signed-off-by: Linda Yu <linda.yu@intel.com>
  • Loading branch information
LindaYu17 committed Sep 20, 2023
1 parent 9984af1 commit 796d763
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 0 deletions.
16 changes: 16 additions & 0 deletions Cargo.lock

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

26 changes: 26 additions & 0 deletions confidential-data-hub/storage/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 = []
14 changes: 14 additions & 0 deletions confidential-data-hub/storage/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) 2023 Intel
//
// SPDX-License-Identifier: Apache-2.0
//

use thiserror::Error;

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

#[derive(Error, Debug)]
pub enum Error {
#[error("secure mount failed: {0}")]
SecureMountFailed(String),
}
9 changes: 9 additions & 0 deletions confidential-data-hub/storage/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) 2023 Intel
//
// SPDX-License-Identifier: Apache-2.0
//

pub mod error;
pub mod volume_type;

pub use error::*;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) 2023 Intel
//
// SPDX-License-Identifier: Apache-2.0
//

pub mod oss;
142 changes: 142 additions & 0 deletions confidential-data-hub/storage/src/volume_type/alibaba_cloud_oss/oss.rs
Original file line number Diff line number Diff line change
@@ -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<u8>) -> Result<Vec<u8>> {
// 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<String> {
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!(

Check failure on line 82 in confidential-data-hub/storage/src/volume_type/alibaba_cloud_oss/oss.rs

View workflow job for this annotation

GitHub Actions / Check (stable)

useless use of `format!`
"sealed secret format error!"
)))
}

impl Oss {
pub(crate) async fn mount(&self, source: String, mount_point: String) -> Result<String> {
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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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
55 changes: 55 additions & 0 deletions confidential-data-hub/storage/src/volume_type/mod.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
pub source: String,
pub fstype: String,
pub options: Vec<String>,
pub mount_point: String,
}

impl Storage {
pub async fn mount(&self) -> Result<String> {
for driver_option in &self.driver_options {

Check failure on line 26 in confidential-data-hub/storage/src/volume_type/mod.rs

View workflow job for this annotation

GitHub Actions / Check (stable)

this loop never actually loops
let (volumetype, metadata) = driver_option
.split_once("=")

Check failure on line 28 in confidential-data-hub/storage/src/volume_type/mod.rs

View workflow job for this annotation

GitHub Actions / Check (stable)

single-character string constant used as pattern
.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| {

Check failure on line 35 in confidential-data-hub/storage/src/volume_type/mod.rs

View workflow job for this annotation

GitHub Actions / Check (stable)

this expression creates a reference which is immediately dereferenced by the compiler
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!(

Check failure on line 45 in confidential-data-hub/storage/src/volume_type/mod.rs

View workflow job for this annotation

GitHub Actions / Check (stable)

useless use of `format!`
"illegal mount info with unsupported volumetype"
)))
}
};
}
Err(Error::SecureMountFailed(format!(

Check failure on line 51 in confidential-data-hub/storage/src/volume_type/mod.rs

View workflow job for this annotation

GitHub Actions / Check (stable)

useless use of `format!`
"illegal mount info as no driver_options"
)))
}
}

0 comments on commit 796d763

Please sign in to comment.