mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-20 03:22:18 +00:00
2cadbba6ad
# Conflicts: # Cargo.toml # iam/src/manager.rs # iam/src/store/object.rs # rustfs/src/admin/handlers/sts.rs # rustfs/src/main.rs # rustfs/src/storage/ecfs.rs
165 lines
5.8 KiB
Rust
165 lines
5.8 KiB
Rust
use crate::{
|
|
admin::router::Operation,
|
|
auth::{check_key_valid, get_session_token},
|
|
};
|
|
use ecstore::utils::{crypto::base64_encode, xml};
|
|
use http::StatusCode;
|
|
use iam::{manager::get_token_signing_key, sys::SESSION_POLICY_NAME};
|
|
use matchit::Params;
|
|
use policy::{auth::get_new_credentials_with_metadata, policy::Policy};
|
|
use s3s::{
|
|
dto::{AssumeRoleOutput, Credentials, Timestamp},
|
|
s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result,
|
|
};
|
|
use serde::Deserialize;
|
|
use serde_json::Value;
|
|
use serde_urlencoded::from_bytes;
|
|
use std::collections::HashMap;
|
|
use time::{Duration, OffsetDateTime};
|
|
use tracing::{info, warn};
|
|
|
|
const ASSUME_ROLE_ACTION: &str = "AssumeRole";
|
|
const ASSUME_ROLE_VERSION: &str = "2011-06-15";
|
|
|
|
#[derive(Deserialize, Debug, Default)]
|
|
#[serde(rename_all = "PascalCase", default)]
|
|
pub struct AssumeRoleRequest {
|
|
pub action: String,
|
|
pub duration_seconds: usize,
|
|
pub version: String,
|
|
pub role_arn: String,
|
|
pub role_session_name: String,
|
|
pub policy: String,
|
|
pub external_id: String,
|
|
}
|
|
|
|
pub struct AssumeRoleHandle {}
|
|
#[async_trait::async_trait]
|
|
impl Operation for AssumeRoleHandle {
|
|
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
|
warn!("handle AssumeRoleHandle");
|
|
|
|
let Some(user) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) };
|
|
|
|
let session_token = get_session_token(&req.uri, &req.headers);
|
|
if session_token.is_some() {
|
|
return Err(s3_error!(InvalidRequest, "AccessDenied1"));
|
|
}
|
|
|
|
let (cred, _owner) =
|
|
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &user.access_key).await?;
|
|
|
|
// TODO: Check permissions, do not allow STS access
|
|
if cred.is_temp() || cred.is_service_account() {
|
|
return Err(s3_error!(InvalidRequest, "AccessDenied"));
|
|
}
|
|
|
|
let mut input = req.input;
|
|
|
|
let bytes = match input.store_all_unlimited().await {
|
|
Ok(b) => b,
|
|
Err(e) => {
|
|
warn!("get body failed, e: {:?}", e);
|
|
return Err(s3_error!(InvalidRequest, "get body failed"));
|
|
}
|
|
};
|
|
|
|
let body: AssumeRoleRequest = from_bytes(&bytes).map_err(|_e| s3_error!(InvalidRequest, "get body failed"))?;
|
|
|
|
if body.action.as_str() != ASSUME_ROLE_ACTION {
|
|
return Err(s3_error!(InvalidArgument, "not support action"));
|
|
}
|
|
|
|
if body.version.as_str() != ASSUME_ROLE_VERSION {
|
|
return Err(s3_error!(InvalidArgument, "not support version"));
|
|
}
|
|
|
|
let mut claims = cred.claims.unwrap_or_default();
|
|
|
|
populate_session_policy(&mut claims, &body.policy)?;
|
|
|
|
let exp = {
|
|
if body.duration_seconds > 0 {
|
|
body.duration_seconds
|
|
} else {
|
|
3600
|
|
}
|
|
};
|
|
|
|
claims.insert(
|
|
"exp".to_string(),
|
|
serde_json::Value::Number(serde_json::Number::from(OffsetDateTime::now_utc().unix_timestamp() + exp as i64)),
|
|
);
|
|
|
|
claims.insert("parent".to_string(), serde_json::Value::String(cred.access_key.clone()));
|
|
|
|
// warn!("AssumeRole get cred {:?}", &user);
|
|
// warn!("AssumeRole get body {:?}", &body);
|
|
|
|
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
|
|
|
|
if let Err(_err) = iam_store.policy_db_get(&cred.access_key, &cred.groups).await {
|
|
return Err(s3_error!(InvalidArgument, "invalid policy arg"));
|
|
}
|
|
|
|
let Some(secret) = get_token_signing_key() else {
|
|
return Err(s3_error!(InvalidArgument, "global active sk not init"));
|
|
};
|
|
|
|
info!("AssumeRole get claims {:?}", &claims);
|
|
|
|
let mut new_cred = get_new_credentials_with_metadata(&claims, &secret)
|
|
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("get new cred failed {}", e)))?;
|
|
|
|
new_cred.parent_user = cred.access_key.clone();
|
|
|
|
info!("AssumeRole get new_cred {:?}", &new_cred);
|
|
|
|
if let Err(_err) = iam_store.set_temp_user(&new_cred.access_key, &new_cred, None).await {
|
|
return Err(s3_error!(InternalError, "set_temp_user failed"));
|
|
}
|
|
|
|
// TODO: globalSiteReplicationSys
|
|
|
|
let resp = AssumeRoleOutput {
|
|
credentials: Some(Credentials {
|
|
access_key_id: new_cred.access_key,
|
|
expiration: Timestamp::from(
|
|
new_cred
|
|
.expiration
|
|
.unwrap_or(OffsetDateTime::now_utc().saturating_add(Duration::seconds(3600))),
|
|
),
|
|
secret_access_key: new_cred.secret_key,
|
|
session_token: new_cred.session_token,
|
|
}),
|
|
..Default::default()
|
|
};
|
|
|
|
// getAssumeRoleCredentials
|
|
let output = xml::serialize::<AssumeRoleOutput>(&resp).unwrap();
|
|
|
|
Ok(S3Response::new((StatusCode::OK, Body::from(output))))
|
|
}
|
|
}
|
|
|
|
pub fn populate_session_policy(claims: &mut HashMap<String, Value>, policy: &str) -> S3Result<()> {
|
|
if !policy.is_empty() {
|
|
let session_policy = Policy::parse_config(policy.as_bytes())
|
|
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("parse policy err {}", e)))?;
|
|
if session_policy.version.is_empty() {
|
|
return Err(s3_error!(InvalidRequest, "invalid policy"));
|
|
}
|
|
|
|
let policy_buf = serde_json::to_vec(&session_policy)
|
|
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal policy err {}", e)))?;
|
|
|
|
if policy_buf.len() > 2048 {
|
|
return Err(s3_error!(InvalidRequest, "policy too large"));
|
|
}
|
|
|
|
claims.insert(SESSION_POLICY_NAME.to_string(), serde_json::Value::String(base64_encode(&policy_buf)));
|
|
}
|
|
|
|
Ok(())
|
|
}
|