You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在ECS/Fargate的AWS Batch作业中获取Job Role凭证的方案

解决方案:ECS/Fargate上Batch作业获取Job Role凭证及Rust手动实现AssumeRole

一、凭证来源解析

你发现的http://169.254.170.2/v2/credentials/UUID是**ECS任务元数据服务(v2)**的凭证获取端点,其中的UUID是当前任务的角色凭证ID——由Fargate在启动容器时自动注入,可直接读取容器内的文件拿到:

cat /run/secrets/ecs/credentials/id

访问该端点返回的就是Job Role的临时凭证(包含AccessKeyId、SecretAccessKey、SessionToken和过期时间),这也是AWS CLI能自动获取凭证的核心原因。

二、无需AssumeRole:直接获取Job Role凭证

在Rust里无需依赖AWS SDK,直接发起HTTP请求即可拿到凭证:

use reqwest::blocking::Client;
use std::fs;
use serde_json::Value;

fn get_job_role_credentials() -> Result<Value, Box<dyn std::error::Error>> {
    // 读取凭证ID
    let cred_id = fs::read_to_string("/run/secrets/ecs/credentials/id")?.trim().to_string();
    // 调用元数据端点
    let client = Client::new();
    let resp = client.get(format!("http://169.254.170.2/v2/credentials/{}", cred_id))
        .send()?
        .json()?;
    Ok(resp)
}

返回的JSON结构示例:

{
  "AccessKeyId": "ASIAXXX",
  "SecretAccessKey": "XXX",
  "SessionToken": "XXX",
  "Expiration": "2024-05-20T12:34:56Z"
}

三、必须调用AssumeRole:Rust手动实现AWS签名v4

如果需要用当前Job Role去Assume另一个角色,需手动实现AWS Signature Version 4完成请求签名,步骤如下:

1. 核心逻辑说明

  • 用上述拿到的Job Role临时凭证作为签名密钥
  • 构造STS的AssumeRole请求参数
  • 按照SigV4规范生成签名并添加到请求头

2. Rust代码示例(仅依赖基础加密/HTTP库,无AWS SDK)

use hmac::{Hmac, Mac};
use reqwest::blocking::Client;
use sha2::Sha256;
use std::collections::BTreeMap;
use chrono::Utc;
use hex::encode;
use urlencoding::encode;
use roxmltree::Document;
use serde_json::Value;

type HmacSha256 = Hmac<Sha256>;

// 生成SigV4签名密钥
fn get_signing_key(secret_key: &str, date: &str, region: &str, service: &str) -> Vec<u8> {
    let k_date = HmacSha256::new_from_slice(format!("AWS4{}", secret_key).as_bytes())
        .unwrap()
        .chain_update(date.as_bytes())
        .finalize()
        .into_bytes();
    
    let k_region = HmacSha256::new_from_slice(&k_date)
        .unwrap()
        .chain_update(region.as_bytes())
        .finalize()
        .into_bytes();
    
    let k_service = HmacSha256::new_from_slice(&k_region)
        .unwrap()
        .chain_update(service.as_bytes())
        .finalize()
        .into_bytes();
    
    HmacSha256::new_from_slice(&k_service)
        .unwrap()
        .chain_update("aws4_request".as_bytes())
        .finalize()
        .into_bytes()
        .to_vec()
}

// 生成规范请求字符串
fn get_canonical_request(
    method: &str,
    uri: &str,
    query_params: &BTreeMap<&str, &str>,
    headers: &BTreeMap<&str, &str>,
    payload_hash: &str,
) -> String {
    let canonical_query = query_params
        .iter()
        .map(|(k, v)| format!("{}={}", encode(k), encode(v)))
        .collect::<Vec<_>>()
        .join("&");
    
    let canonical_headers = headers
        .iter()
        .map(|(k, v)| format!("{}:{}\n", k.to_lowercase(), v.trim()))
        .collect::<String>();
    
    let signed_headers = headers
        .keys()
        .map(|k| k.to_lowercase())
        .collect::<Vec<_>>()
        .join(";");
    
    format!(
        "{}\n{}\n{}\n{}\n{}\n{}",
        method, uri, canonical_query, canonical_headers, signed_headers, payload_hash
    )
}

// 执行AssumeRole请求
fn assume_role(
    job_role_creds: &Value,
    role_arn: &str,
    session_name: &str,
    region: &str,
) -> Result<Value, Box<dyn std::error::Error>> {
    let access_key = job_role_creds["AccessKeyId"].as_str().unwrap();
    let secret_key = job_role_creds["SecretAccessKey"].as_str().unwrap();
    let session_token = job_role_creds["SessionToken"].as_str().unwrap();

    // 生成SigV4要求的时间格式
    let datetime_str = Utc::now().format("%Y%m%dT%H%M%SZ").to_string();
    let date_str = Utc::now().format("%Y%m%d").to_string();

    // STS请求参数
    let mut query_params = BTreeMap::new();
    query_params.insert("Action", "AssumeRole");
    query_params.insert("RoleArn", role_arn);
    query_params.insert("RoleSessionName", session_name);
    query_params.insert("Version", "2011-06-15");

    // 请求头
    let mut headers = BTreeMap::new();
    headers.insert("Host", "sts.amazonaws.com");
    headers.insert("X-Amz-Date", &datetime_str);
    headers.insert("X-Amz-Security-Token", session_token);

    // 计算空payload的哈希
    let payload_hash = encode(Sha256::digest("".as_bytes()));

    // 构造规范请求和待签名字符串
    let canonical_request = get_canonical_request("GET", "/", &query_params, &headers, &payload_hash);
    let credential_scope = format!("{}/{}/sts/aws4_request", date_str, region);
    let string_to_sign = format!(
        "AWS4-HMAC-SHA256\n{}\n{}\n{}",
        datetime_str,
        credential_scope,
        encode(Sha256::digest(canonical_request.as_bytes()))
    );

    // 生成签名
    let signing_key = get_signing_key(secret_key, &date_str, region, "sts");
    let signature = encode(HmacSha256::new_from_slice(&signing_key)
        .unwrap()
        .chain_update(string_to_sign.as_bytes())
        .finalize()
        .into_bytes());

    // 构造Authorization头
    let auth_header = format!(
        "AWS4-HMAC-SHA256 Credential={}/{}, SignedHeaders={}, Signature={}",
        access_key,
        credential_scope,
        headers.keys().map(|k| k.to_lowercase()).collect::<Vec<_>>().join(";"),
        signature
    );

    // 发起请求并解析XML响应
    let client = Client::new();
    let resp = client.get("https://sts.amazonaws.com")
        .query(&query_params)
        .header("X-Amz-Date", datetime_str)
        .header("X-Amz-Security-Token", session_token)
        .header("Authorization", auth_header)
        .send()?
        .text()?;

    let xml_doc = Document::parse(&resp)?;
    let mut result = serde_json::Map::new();
    if let Some(elem) = xml_doc.descendants().find(|n| n.tag_name().name() == "AccessKeyId") {
        result.insert("AccessKeyId".into(), elem.text().unwrap_or("").into());
    }
    if let Some(elem) = xml_doc.descendants().find(|n| n.tag_name().name() == "SecretAccessKey") {
        result.insert("SecretAccessKey".into(), elem.text().unwrap_or("").into());
    }
    if let Some(elem) = xml_doc.descendants().find(|n| n.tag_name().name() == "SessionToken") {
        result.insert("SessionToken".into(), elem.text().unwrap_or("").into());
    }
    if let Some(elem) = xml_doc.descendants().find(|n| n.tag_name().name() == "Expiration") {
        result.insert("Expiration".into(), elem.text().unwrap_or("").into());
    }

    Ok(Value::Object(result))
}

需在Cargo.toml中添加依赖:

[dependencies]
reqwest = { version = "0.11", features = ["blocking"] }
serde_json = "1.0"
sha2 = "0.10"
hmac = "0.12"
hex = "0.4"
urlencoding = "2.1"
chrono = "0.4"
roxmltree = "0.18"

补充说明

  • 你之前没找到环境变量的原因:Fargate使用ECS任务元数据服务v2时,不再设置AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,而是通过文件和固定IP端点提供凭证信息。
  • 若无需切换角色,直接使用元数据服务返回的凭证即可访问AWS服务,无需调用STS的AssumeRole接口。

内容的提问来源于stack exchange,提问作者Wolfgang Kuehn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 13:44:52