1use anyhow::{bail, Context, Result};
2use regex::Regex;
3use uuid::Uuid;
4
5pub struct AwsTempCreds {
6 pub access_key: String,
7 pub secret_key: String,
8 pub session_token: String,
9 pub region: String,
10}
11
12pub async fn assume_role(arn: &str, external_id: &str, url: &str) -> Result<AwsTempCreds> {
13 let region = extract_region(url);
14
15 let config = aws_config::defaults(aws_config::BehaviorVersion::latest())
16 .region(aws_config::Region::new(region.clone()))
17 .load()
18 .await;
19
20 let sts = aws_sdk_sts::Client::new(&config);
21
22 let resp = sts
23 .assume_role()
24 .role_arn(arn)
25 .role_session_name(format!("session-{}", Uuid::new_v4()))
26 .external_id(external_id)
27 .send()
28 .await
29 .context("STS AssumeRole failed")?;
30
31 let creds = resp
32 .credentials()
33 .ok_or_else(|| anyhow::anyhow!("no credentials in AssumeRole response"))?;
34
35 let access_key = creds.access_key_id().to_string();
36 let secret_key = creds.secret_access_key().to_string();
37 let session_token = creds.session_token().to_string();
38
39 if access_key.is_empty() || secret_key.is_empty() {
40 bail!("empty credentials from STS AssumeRole");
41 }
42
43 Ok(AwsTempCreds {
44 access_key,
45 secret_key,
46 session_token,
47 region,
48 })
49}
50
51fn extract_region(url: &str) -> String {
52 let re = Regex::new(r"codecommit::([a-z0-9-]+)://").unwrap();
53 if let Some(caps) = re.captures(url) {
54 return caps[1].to_string();
55 }
56
57 let re2 = Regex::new(r"git-codecommit\.([a-z0-9-]+)\.amazonaws\.com").unwrap();
59 if let Some(caps) = re2.captures(url) {
60 return caps[1].to_string();
61 }
62
63 "us-east-1".to_string()
64}