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
100 changes: 100 additions & 0 deletions core/services/s3/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use md5::Md5;
use reqsign_aws_v4::AssumeRoleCredentialProvider;
use reqsign_aws_v4::Credential;
use reqsign_aws_v4::DefaultCredentialProvider;
use reqsign_aws_v4::ECSCredentialProvider;
use reqsign_aws_v4::RequestSigner as AwsV4Signer;
use reqsign_aws_v4::StaticCredentialProvider;
use reqsign_core::Context;
Expand Down Expand Up @@ -412,6 +413,69 @@ impl S3Builder {
self
}

/// Set container credentials relative URI for ECS Task IAM roles.
///
/// Used in ECS environments where the base metadata endpoint is known.
/// The relative URI is appended to the default ECS endpoint (169.254.170.2).
///
/// Example: "/v2/credentials/my-role-name"
pub fn container_credentials_relative_uri(mut self, uri: &str) -> Self {
if !uri.is_empty() {
self.config.container_credentials_relative_uri = Some(uri.to_string());
}
self
}

/// Set container credentials endpoint for EKS Pod Identity, Fargate, or custom setups.
///
/// Complete URL for fetching credentials. Used in:
/// - EKS Pod Identity environments
/// - AWS Fargate environments
/// - Custom container credential endpoints
///
/// Example: "http://169.254.170.2/v2/credentials/my-role"
pub fn container_credentials_endpoint(mut self, endpoint: &str) -> Self {
if !endpoint.is_empty() {
self.config.container_credentials_endpoint = Some(endpoint.to_string());
}
self
}

/// Set authorization token for container credentials requests.
///
/// Token used for authenticating with the container credentials endpoint.
/// This is an alternative to `container_authorization_token_file`.
pub fn container_authorization_token(mut self, token: &str) -> Self {
if !token.is_empty() {
self.config.container_authorization_token = Some(token.to_string());
}
self
}

/// Set path to file containing authorization token for container credentials.
///
/// File should contain the authorization token for authenticating with the
/// container credentials endpoint. Required for EKS Pod Identity.
///
/// This is an alternative to `container_authorization_token`.
pub fn container_authorization_token_file(mut self, file_path: &str) -> Self {
if !file_path.is_empty() {
self.config.container_authorization_token_file = Some(file_path.to_string());
}
self
}

/// Set override for the container metadata URI base endpoint.
///
/// Used to override the default http://169.254.170.2 endpoint.
/// Typically used for testing or custom container credential setups.
pub fn container_metadata_uri_override(mut self, uri: &str) -> Self {
if !uri.is_empty() {
self.config.container_metadata_uri_override = Some(uri.to_string());
}
self
}

/// Disable load credential from ec2 metadata.
///
/// This option is used to disable the default behavior of opendal
Expand Down Expand Up @@ -459,6 +523,15 @@ impl S3Builder {
self
}

/// Check if any ECS container credentials configuration is present.
fn has_ecs_config(config: &S3Config) -> bool {
config.container_credentials_endpoint.is_some()
|| config.container_credentials_relative_uri.is_some()
|| config.container_authorization_token.is_some()
|| config.container_authorization_token_file.is_some()
|| config.container_metadata_uri_override.is_some()
}

/// Check if `bucket` is valid.
/// `bucket` must be not empty and if `enable_virtual_host_style` is true
/// it could not contain dot (.) character.
Expand Down Expand Up @@ -830,6 +903,33 @@ impl Builder for S3Builder {
provider = provider.push_front(static_provider);
}

// Insert ECS credential provider if container credentials are configured.
if Self::has_ecs_config(&config) {
let mut ecs_provider = ECSCredentialProvider::new();

if let Some(ref endpoint) = config.container_credentials_endpoint {
ecs_provider = ecs_provider.with_endpoint(endpoint);
}

if let Some(ref relative_uri) = config.container_credentials_relative_uri {
ecs_provider = ecs_provider.with_relative_uri(relative_uri);
}

if let Some(ref token) = config.container_authorization_token {
ecs_provider = ecs_provider.with_auth_token(token);
}

if let Some(ref token_file) = config.container_authorization_token_file {
ecs_provider = ecs_provider.with_auth_token_file(token_file);
}

if let Some(ref uri_override) = config.container_metadata_uri_override {
ecs_provider = ecs_provider.with_metadata_uri_override(uri_override);
}

provider = provider.push_front(ecs_provider);
}

// Insert assume role provider if user provided.
if let Some(role_arn) = &config.role_arn {
let sts_ctx = ctx.clone();
Expand Down
120 changes: 120 additions & 0 deletions core/services/s3/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,56 @@ pub struct S3Config {
/// Indicates whether the client agrees to pay for the requests made to the S3 bucket.
#[serde(alias = "aws_request_payer", alias = "request_payer")]
pub enable_request_payer: bool,

/// Container credentials relative URI for ECS Task IAM roles.
///
/// Used in ECS environments where the base metadata endpoint is known.
/// The relative URI is appended to the default ECS endpoint (169.254.170.2).
///
/// Example: "/v2/credentials/my-role-name"
#[serde(alias = "aws_container_credentials_relative_uri")]
pub container_credentials_relative_uri: Option<String>,

/// Container credentials full endpoint for EKS Pod Identity, Fargate, or custom setups.
///
/// Complete URL for fetching credentials. Used in:
/// - EKS Pod Identity environments
/// - AWS Fargate environments
/// - Custom container credential endpoints
///
/// Example: "http://169.254.170.2/v2/credentials/my-role"
#[serde(
alias = "container_credentials_full_uri",
alias = "aws_container_credentials_full_uri"
)]
pub container_credentials_endpoint: Option<String>,

/// Authorization token for container credentials requests.
///
/// Token used for authenticating with the container credentials endpoint.
/// This is an alternative to `container_authorization_token_file`.
#[serde(alias = "aws_container_authorization_token")]
pub container_authorization_token: Option<String>,

/// Path to file containing authorization token for container credentials.
///
/// File should contain the authorization token for authenticating with the
/// container credentials endpoint. Required for EKS Pod Identity.
///
/// This is an alternative to `container_authorization_token`.
#[serde(alias = "aws_container_authorization_token_file")]
pub container_authorization_token_file: Option<String>,

/// Override for the container metadata URI base endpoint.
///
/// Used to override the default http://169.254.170.2 endpoint.
/// Typically used for testing or custom container credential setups.
#[serde(
alias = "aws_container_metadata_uri_override",
alias = "aws_metadata_endpoint",
alias = "metadata_endpoint"
)]
pub container_metadata_uri_override: Option<String>,
}

impl Debug for S3Config {
Expand Down Expand Up @@ -382,4 +432,74 @@ mod tests {
Some("https://custom-s3-endpoint.com")
);
}

#[test]
fn test_s3_config_container_credentials() {
let json = r#"{
"bucket": "test-bucket",
"container_credentials_relative_uri": "/v2/credentials/my-role",
"container_credentials_full_uri": "http://169.254.170.2/v2/credentials/my-role",
"container_authorization_token": "test-token",
"container_authorization_token_file": "/path/to/token",
"container_metadata_uri_override": "http://custom-endpoint"
}"#;

let config: S3Config = serde_json::from_str(json).unwrap();
assert_eq!(config.bucket, "test-bucket");
assert_eq!(
config.container_credentials_relative_uri,
Some("/v2/credentials/my-role".to_string())
);
assert_eq!(
config.container_credentials_endpoint,
Some("http://169.254.170.2/v2/credentials/my-role".to_string())
);
assert_eq!(
config.container_authorization_token,
Some("test-token".to_string())
);
assert_eq!(
config.container_authorization_token_file,
Some("/path/to/token".to_string())
);
assert_eq!(
config.container_metadata_uri_override,
Some("http://custom-endpoint".to_string())
);
}

#[test]
fn test_s3_config_container_credentials_aws_aliases() {
let json = r#"{
"bucket": "test-bucket",
"aws_container_credentials_relative_uri": "/v2/credentials/my-role",
"aws_container_credentials_full_uri": "http://169.254.170.2/v2/credentials/my-role",
"aws_container_authorization_token": "test-token",
"aws_container_authorization_token_file": "/path/to/token",
"aws_container_metadata_uri_override": "http://custom-endpoint"
}"#;

let config: S3Config = serde_json::from_str(json).unwrap();
assert_eq!(config.bucket, "test-bucket");
assert_eq!(
config.container_credentials_relative_uri,
Some("/v2/credentials/my-role".to_string())
);
assert_eq!(
config.container_credentials_endpoint,
Some("http://169.254.170.2/v2/credentials/my-role".to_string())
);
assert_eq!(
config.container_authorization_token,
Some("test-token".to_string())
);
assert_eq!(
config.container_authorization_token_file,
Some("/path/to/token".to_string())
);
assert_eq!(
config.container_metadata_uri_override,
Some("http://custom-endpoint".to_string())
);
}
}
Loading