Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions core/services/cos/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,27 @@ impl CosBuilder {
self
}

/// Set security_token (a.k.a. session token) of this backend.
///
/// This is used when authenticating via Tencent Cloud STS temporary
/// credentials (e.g. obtained from `GetFederationToken` or
/// `AssumeRole`). When provided, it will be combined with `secret_id`
/// and `secret_key` to sign requests, and the `x-cos-security-token`
/// header will be attached automatically.
///
/// - If this is set, it takes precedence over the environment variables
/// `TENCENTCLOUD_TOKEN`, `TENCENTCLOUD_SECURITY_TOKEN`, and
/// `QCLOUD_SECRET_TOKEN`.
/// - If this is not set, OpenDAL will try to read from those
/// environment variables (unless `disable_config_load` is enabled).
pub fn security_token(mut self, security_token: &str) -> Self {
if !security_token.is_empty() {
self.config.security_token = Some(security_token.to_string());
}

self
}

/// Set bucket of this backend.
/// The param is required.
pub fn bucket(mut self, bucket: &str) -> Self {
Expand Down Expand Up @@ -199,14 +220,21 @@ impl Builder for CosBuilder {
self.config.secret_id.as_deref(),
self.config.secret_key.as_deref(),
) {
let security_token = envs
.get("TENCENTCLOUD_TOKEN")
.or_else(|| envs.get("TENCENTCLOUD_SECURITY_TOKEN"))
.or_else(|| envs.get("QCLOUD_SECRET_TOKEN"));
// Precedence:
// 1. explicit `config.security_token` set by the user;
// 2. environment variables when config load is NOT disabled.
let env_token = if self.config.disable_config_load {
None
} else {
envs.get("TENCENTCLOUD_TOKEN")
.or_else(|| envs.get("TENCENTCLOUD_SECURITY_TOKEN"))
.or_else(|| envs.get("QCLOUD_SECRET_TOKEN"))
.map(|s| s.as_str())
};

let static_provider = if self.config.disable_config_load {
StaticCredentialProvider::new(secret_id, secret_key)
} else if let Some(token) = security_token {
let security_token = self.config.security_token.as_deref().or(env_token);

let static_provider = if let Some(token) = security_token {
StaticCredentialProvider::with_security_token(secret_id, secret_key, token)
} else {
StaticCredentialProvider::new(secret_id, secret_key)
Expand Down
57 changes: 57 additions & 0 deletions core/services/cos/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ pub struct CosConfig {
pub secret_id: Option<String>,
/// Secret key of this backend.
pub secret_key: Option<String>,
/// Security token (a.k.a. session token) of this backend.
///
/// This is used for temporary credentials issued by Tencent Cloud STS
/// (e.g. `GetFederationToken` / `AssumeRole`). When `security_token` is
/// provided, it will be used together with `secret_id` and `secret_key`
/// to sign requests, and the `x-cos-security-token` header will be
/// attached automatically by the signer.
///
/// If this field is not set, OpenDAL will also fall back to reading
/// the token from environment variables `TENCENTCLOUD_TOKEN`,
/// `TENCENTCLOUD_SECURITY_TOKEN` or `QCLOUD_SECRET_TOKEN` (unless
/// `disable_config_load` is enabled).
pub security_token: Option<String>,
/// Bucket of this backend.
pub bucket: Option<String>,
/// is bucket versioning enabled for this bucket
Expand All @@ -52,6 +65,10 @@ impl Debug for CosConfig {
.field("root", &self.root)
.field("endpoint", &self.endpoint)
.field("bucket", &self.bucket)
.field(
"security_token",
&self.security_token.as_ref().map(|_| "<redacted>"),
)
.field("enable_versioning", &self.enable_versioning)
.field("disable_config_load", &self.disable_config_load)
.finish_non_exhaustive()
Expand Down Expand Up @@ -97,4 +114,44 @@ mod tests {
assert_eq!(cfg.bucket.as_deref(), Some("example-bucket"));
assert_eq!(cfg.root.as_deref(), Some("path/to/root"));
}

#[test]
fn from_uri_accepts_cosn_scheme() {
let uri = OperatorUri::new(
"cosn://example-bucket/path/to/root",
Vec::<(String, String)>::new(),
)
.unwrap();
let cfg = CosConfig::from_uri(&uri).unwrap();
assert_eq!(cfg.bucket.as_deref(), Some("example-bucket"));
assert_eq!(cfg.root.as_deref(), Some("path/to/root"));
}

#[test]
fn from_uri_extracts_security_token() {
let uri = OperatorUri::new(
"cos://example-bucket/",
vec![
("secret_id".to_string(), "id".to_string()),
("secret_key".to_string(), "key".to_string()),
("security_token".to_string(), "token".to_string()),
],
)
.unwrap();
let cfg = CosConfig::from_uri(&uri).unwrap();
assert_eq!(cfg.secret_id.as_deref(), Some("id"));
assert_eq!(cfg.secret_key.as_deref(), Some("key"));
assert_eq!(cfg.security_token.as_deref(), Some("token"));
}

#[test]
fn debug_redacts_security_token() {
let cfg = CosConfig {
security_token: Some("super-secret-token".to_string()),
..Default::default()
};
let debug_output = format!("{cfg:?}");
assert!(!debug_output.contains("super-secret-token"));
assert!(debug_output.contains("<redacted>"));
}
}
13 changes: 13 additions & 0 deletions core/services/cos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,22 @@
/// Default scheme for cos service.
pub const COS_SCHEME: &str = "cos";

/// Alternative scheme for cos service.
///
/// `cosn://` is the canonical Hadoop / EMR URI scheme used by many
/// big-data ecosystems (Spark, Hive, Flink, etc.) to access Tencent Cloud
/// COS. OpenDAL accepts it as an alias of `cos://` so that existing
/// workloads can switch to OpenDAL without changing their URIs.
pub const COSN_SCHEME: &str = "cosn";

/// Register this service into the given registry.
///
/// Both the canonical `cos` scheme and the Hadoop-compatible `cosn`
/// alias are registered so that URIs like `cos://bucket/key` and
/// `cosn://bucket/key` are both resolvable to this backend.
pub fn register_cos_service(registry: &opendal_core::OperatorRegistry) {
registry.register::<Cos>(COS_SCHEME);
registry.register::<Cos>(COSN_SCHEME);
}

mod backend;
Expand Down
Loading