updates
This commit is contained in:
@@ -34,6 +34,9 @@ use std::fs::File;
|
|||||||
use std::io::{ErrorKind, Read, Write};
|
use std::io::{ErrorKind, Read, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{fs, io};
|
use std::{fs, io};
|
||||||
|
use std::fmt::format;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::str::FromStr;
|
||||||
use tokio::fs::File as TokioFile;
|
use tokio::fs::File as TokioFile;
|
||||||
use tokio_util::bytes::BytesMut;
|
use tokio_util::bytes::BytesMut;
|
||||||
use tokio_util::codec::{Decoder, FramedRead};
|
use tokio_util::codec::{Decoder, FramedRead};
|
||||||
@@ -42,6 +45,13 @@ const OSS_SEND_FILE_CONFIG_FILE: &str = "~/.jssp/config/osssendfile.json";
|
|||||||
const CREATE_STS_URL: &str = "https://global.hatter.ink/oidc/create_sts.json";
|
const CREATE_STS_URL: &str = "https://global.hatter.ink/oidc/create_sts.json";
|
||||||
const ADD_DOC_URL: &str = "https://play.hatter.me/doc/addDoc.jsonp";
|
const ADD_DOC_URL: &str = "https://play.hatter.me/doc/addDoc.jsonp";
|
||||||
|
|
||||||
|
const ASSUME_ROLE_BY_KEY: &str = "https://global.hatter.ink/cloud/alibaba_cloud/assume_role_by_key.json";
|
||||||
|
|
||||||
|
const INSTANCE_IDENTITY_TOKEN: &str = "http://100.100.100.200/latest/api/token";
|
||||||
|
const INSTANCE_IDENTITY_TTL: &str = "X-aliyun-ecs-metadata-token-ttl-seconds";
|
||||||
|
const INSTANCE_IDENTITY_PKCS7: &str = "http://100.100.100.200/latest/dynamic/instance-identity/pkcs7";
|
||||||
|
const INSTANCE_IDENTITY_PKCS7_TOKEN: &str = "X-aliyun-ecs-metadata-token";
|
||||||
|
|
||||||
const ENV_OSS_SEND_FILE_CONFIG: &str = "OSS_SEND_FILE_CONFIG";
|
const ENV_OSS_SEND_FILE_CONFIG: &str = "OSS_SEND_FILE_CONFIG";
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
@@ -77,7 +87,8 @@ struct OssSendFileConfig {
|
|||||||
endpoint: String,
|
endpoint: String,
|
||||||
bucket: String,
|
bucket: String,
|
||||||
token: String,
|
token: String,
|
||||||
oidc: OidcConfig,
|
oidc: Option<OidcConfig>,
|
||||||
|
pkcs7: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
@@ -146,7 +157,6 @@ impl Decoder for CustomBytesCodec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> XResult<()> {
|
async fn main() -> XResult<()> {
|
||||||
let args = OssSendFileArgs::parse();
|
let args = OssSendFileArgs::parse();
|
||||||
@@ -157,9 +167,26 @@ async fn main() -> XResult<()> {
|
|||||||
let sts = request_sts(&client, &oss_send_file_config).await?;
|
let sts = request_sts(&client, &oss_send_file_config).await?;
|
||||||
information!("Get STS success");
|
information!("Get STS success");
|
||||||
|
|
||||||
let source_file = args.file.clone();
|
let mut pending_remove_file = None::<String>;
|
||||||
|
let source_file = args.file.clone(); // TODO
|
||||||
|
|
||||||
|
let metadata = fs::metadata(&source_file)?;
|
||||||
let filename = source_file.file_name().unwrap().to_str().unwrap();
|
let filename = source_file.file_name().unwrap().to_str().unwrap();
|
||||||
let filename_ext = get_filename_ext(filename);
|
let filename_ext = get_filename_ext(filename);
|
||||||
|
|
||||||
|
let temp_zip_file = format!("{}.zip", filename);
|
||||||
|
if metadata.is_dir() {
|
||||||
|
// TODO check temp_zip_file exists
|
||||||
|
match zip_dir(&temp_zip_file, source_file.to_str().unwrap()) {
|
||||||
|
Ok(_) => {
|
||||||
|
information!("Zip {} to {} success", source_file.display(), temp_zip_file);
|
||||||
|
// TODO source_file = PathBuf::from_str(&temp_zip_file)?;
|
||||||
|
pending_remove_file = Some(temp_zip_file.clone());
|
||||||
|
}
|
||||||
|
Err(e) => return simple_error!("Zip {} to {} failed: {}", source_file.display(), temp_zip_file, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let temp_file = match source_file.parent() {
|
let temp_file = match source_file.parent() {
|
||||||
None => PathBuf::from(format!("{}.tmp", filename)),
|
None => PathBuf::from(format!("{}.tmp", filename)),
|
||||||
Some(parent_source_file) => {
|
Some(parent_source_file) => {
|
||||||
@@ -247,6 +274,11 @@ async fn main() -> XResult<()> {
|
|||||||
success!("CDN URL: {}", cnd_url);
|
success!("CDN URL: {}", cnd_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(pending_remove_file) = pending_remove_file {
|
||||||
|
fs::remove_file(&pending_remove_file)?;
|
||||||
|
success!("Remove pending remove file: {} success", &pending_remove_file);
|
||||||
|
}
|
||||||
|
|
||||||
success!("File {} upload success", filename);
|
success!("File {} upload success", filename);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -348,10 +380,20 @@ fn digest_sha256(source_file: &PathBuf) -> XResult<Vec<u8>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn request_sts(client: &Client, oss_send_file_config: &OssSendFileConfig) -> XResult<Sts> {
|
async fn request_sts(client: &Client, oss_send_file_config: &OssSendFileConfig) -> XResult<Sts> {
|
||||||
|
if let Some(oidc_config) = &oss_send_file_config.oidc {
|
||||||
|
Ok(request_sts_via_oidc(client, oidc_config).await?)
|
||||||
|
} else if let Some(role_arn) = &oss_send_file_config.pkcs7 {
|
||||||
|
Ok(request_sts_via_pkcs7(client, role_arn).await?)
|
||||||
|
} else {
|
||||||
|
simple_error!("OIDC or PKCS#7 config not found.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn request_sts_via_oidc(client: &Client, oidc_config: &OidcConfig) -> XResult<Sts> {
|
||||||
let mut params = HashMap::new();
|
let mut params = HashMap::new();
|
||||||
params.insert("client_id", &oss_send_file_config.oidc.client_id);
|
params.insert("client_id", &oidc_config.client_id);
|
||||||
params.insert("client_secret", &oss_send_file_config.oidc.client_secret);
|
params.insert("client_secret", &oidc_config.client_secret);
|
||||||
params.insert("sub", &oss_send_file_config.oidc.sub);
|
params.insert("sub", &oidc_config.sub);
|
||||||
let response = client.post(CREATE_STS_URL)
|
let response = client.post(CREATE_STS_URL)
|
||||||
.form(¶ms)
|
.form(¶ms)
|
||||||
.send()
|
.send()
|
||||||
@@ -360,6 +402,47 @@ async fn request_sts(client: &Client, oss_send_file_config: &OssSendFileConfig)
|
|||||||
parse_sts_response(response).await
|
parse_sts_response(response).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct StsTokenForAssumeRoleByKeyResponse {
|
||||||
|
pub mode: String,
|
||||||
|
pub expiration: String,
|
||||||
|
pub access_key_id: String,
|
||||||
|
pub access_key_secret: String,
|
||||||
|
pub sts_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct AssumeRoleByKeyResponse {
|
||||||
|
pub status: i32,
|
||||||
|
pub message: Option<String>,
|
||||||
|
pub data: StsTokenForAssumeRoleByKeyResponse,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn request_sts_via_pkcs7(client: &Client, role_arn: &str) -> XResult<Sts> {
|
||||||
|
let pkcs7 = fetch_alibaba_cloud_instance_identity_v1(
|
||||||
|
client, "hatter.ink:/cloud/alibaba_cloud/assume_role_by_key.json").await?;
|
||||||
|
|
||||||
|
let assumed_role_response = client.get(format!("{}?roleArn={}", ASSUME_ROLE_BY_KEY, role_arn))
|
||||||
|
.header("Authorization", format!("PKCS7 {}", pkcs7))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !assumed_role_response.status().is_success() {
|
||||||
|
return simple_error!("Assume role by key failed, status: {}", assumed_role_response.status());
|
||||||
|
}
|
||||||
|
|
||||||
|
let assumed_role: AssumeRoleByKeyResponse = serde_json::from_str(&assumed_role_response.text().await?)?;
|
||||||
|
if assumed_role.status != 200 {
|
||||||
|
return simple_error!("Assume role by key failed, response: {:?}", assumed_role);
|
||||||
|
}
|
||||||
|
Ok(Sts{
|
||||||
|
access_key_id: assumed_role.data.access_key_id,
|
||||||
|
access_key_secret: assumed_role.data.access_key_secret,
|
||||||
|
expiration: assumed_role.data.expiration,
|
||||||
|
security_token: assumed_role.data.sts_token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async fn send_file(client: &Client, token: &str, url: &str, title: &str, keywords: &str, key: [u8; 16]) -> XResult<SendFileResponse> {
|
async fn send_file(client: &Client, token: &str, url: &str, title: &str, keywords: &str, key: [u8; 16]) -> XResult<SendFileResponse> {
|
||||||
let mut params = HashMap::new();
|
let mut params = HashMap::new();
|
||||||
params.insert("jsonp", "1".to_string());
|
params.insert("jsonp", "1".to_string());
|
||||||
@@ -400,5 +483,60 @@ async fn parse_sts_response(response: Response) -> XResult<Sts> {
|
|||||||
Ok(sts)
|
Ok(sts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn encrypt_file() {
|
||||||
|
// config == "base64:"
|
||||||
|
// tiny-encrypt ::: encrypt file
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zip_dir(temp_zip_file: &str, target: &str) -> XResult<()> {
|
||||||
|
let mut cmd = Command::new("zip");
|
||||||
|
cmd.args(&["-r", temp_zip_file, target]);
|
||||||
|
if let Err(e) = rust_util::util_cmd::run_command_and_wait(&mut cmd) {
|
||||||
|
return simple_error!("Zip {} to {} failed: {}", target, temp_zip_file, e);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct AlibabaCloudInstanceIdentityAudienceMeta {
|
||||||
|
pub ver: i32,
|
||||||
|
iat: i64,
|
||||||
|
exp: i64,
|
||||||
|
aud: String,
|
||||||
|
jti: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_alibaba_cloud_instance_identity_v1(client: &Client, audience: &str) -> XResult<String> {
|
||||||
|
let instance_identity_token = client.get(INSTANCE_IDENTITY_TOKEN)
|
||||||
|
.header(INSTANCE_IDENTITY_TTL, "60")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
if !instance_identity_token.status().is_success() {
|
||||||
|
return simple_error!("Get instance identity token failed, status: {}", instance_identity_token.status());
|
||||||
|
}
|
||||||
|
let token = opt_result!(instance_identity_token.text().await, "Get instance identity token failed: {}");
|
||||||
|
|
||||||
|
let rand_bytes: [u8; 8] = rand::random();
|
||||||
|
let audience_meta = AlibabaCloudInstanceIdentityAudienceMeta {
|
||||||
|
ver: 1,
|
||||||
|
iat: util_time::get_current_secs() as i64,
|
||||||
|
exp: (util_time::get_current_secs() + 60) as i64,
|
||||||
|
aud: audience.to_string(),
|
||||||
|
jti: format!("jti-{}-{}", util_time::get_current_millis(), hex::encode(rand_bytes)),
|
||||||
|
};
|
||||||
|
let audience_meta_json = serde_json::to_string(&audience_meta)?;
|
||||||
|
let instance_identity_pkcs7 = client.get(
|
||||||
|
format!("{}?audience={}", INSTANCE_IDENTITY_PKCS7, audience_meta_json))
|
||||||
|
.header(INSTANCE_IDENTITY_PKCS7_TOKEN, token)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
if !instance_identity_pkcs7.status().is_success() {
|
||||||
|
return simple_error!("Get instance identity PKCS#7 failed, status: {}", instance_identity_pkcs7.status());
|
||||||
|
}
|
||||||
|
let pkcs7 = opt_result!( instance_identity_pkcs7.text().await, "Get instance identity PKCS#7 failed: {}");
|
||||||
|
|
||||||
|
Ok(pkcs7)
|
||||||
|
}
|
||||||
|
|
||||||
// @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20251015T193822+08:00.MEYCIQCHVvU0SXeLuxIAD19G
|
// @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20251015T193822+08:00.MEYCIQCHVvU0SXeLuxIAD19G
|
||||||
// PbuX9ZsYWBpsHL2aSo7MeelVhAIhAPMrEXIaiyaEDB8YtQfun1Sb+nwiAUeJvIOc9pKd5NwM
|
// PbuX9ZsYWBpsHL2aSo7MeelVhAIhAPMrEXIaiyaEDB8YtQfun1Sb+nwiAUeJvIOc9pKd5NwM
|
||||||
|
|||||||
Reference in New Issue
Block a user