Skip to main content

reqsign_aws_core/provide_credential/
assume_role.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::fmt::{Debug, Formatter};
19
20use reqsign_core::{Context, ProvideCredential, Result, Signer};
21
22use crate::Credential;
23use crate::assume_role::{AssumeRoleGrant, AssumeRoleOperation};
24use crate::provide_credential::utils::sts_endpoint;
25
26/// Loads credentials through one fixed AWS STS `AssumeRole` flow.
27///
28/// Use [`reqsign_core::Granter`] with the SigV4 crate's explicit AssumeRole
29/// granter when the source credential and grant are supplied per vending
30/// operation.
31pub struct AssumeRoleCredentialProvider {
32    grant: AssumeRoleGrant,
33    duration_seconds: Option<u32>,
34    region: Option<String>,
35    use_regional_sts_endpoint: bool,
36    sts_signer: Signer<Credential>,
37}
38
39impl Debug for AssumeRoleCredentialProvider {
40    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
41        f.debug_struct("AssumeRoleCredentialProvider")
42            .finish_non_exhaustive()
43    }
44}
45
46impl AssumeRoleCredentialProvider {
47    /// Create a new fixed-flow AssumeRole credential provider.
48    pub fn new(role_arn: String, sts_signer: Signer<Credential>) -> Self {
49        Self {
50            grant: AssumeRoleGrant::new(role_arn, "reqsign"),
51            duration_seconds: Some(3_600),
52            region: None,
53            use_regional_sts_endpoint: false,
54            sts_signer,
55        }
56    }
57
58    /// Set the role session name.
59    pub fn with_role_session_name(mut self, name: String) -> Self {
60        self.grant.role_session_name = name;
61        self
62    }
63
64    /// Set the external ID.
65    pub fn with_external_id(mut self, id: String) -> Self {
66        self.grant.external_id = Some(id);
67        self
68    }
69
70    /// Set the duration in seconds.
71    pub fn with_duration_seconds(mut self, seconds: u32) -> Self {
72        self.duration_seconds = Some(seconds);
73        self
74    }
75
76    /// Set the session policy.
77    pub fn with_policy(mut self, policy: String) -> Self {
78        self.grant.policy = Some(policy);
79        self
80    }
81
82    /// Set the session policy ARNs.
83    pub fn with_policy_arns(mut self, policy_arns: Vec<String>) -> Self {
84        self.grant.policy_arns = policy_arns;
85        self
86    }
87
88    /// Set the session tags.
89    pub fn with_tags(mut self, tags: Vec<(String, String)>) -> Self {
90        self.grant.tags = tags;
91        self
92    }
93
94    /// Set the region used to select a regional STS endpoint.
95    pub fn with_region(mut self, region: String) -> Self {
96        self.region = Some(region);
97        self
98    }
99
100    /// Use a regional STS endpoint.
101    pub fn with_regional_sts_endpoint(mut self) -> Self {
102        self.use_regional_sts_endpoint = true;
103        self
104    }
105
106    /// Set the MFA serial number.
107    pub fn with_mfa_serial(mut self, serial_number: String) -> Self {
108        self.grant.serial_number = Some(serial_number);
109        self
110    }
111
112    /// Set the MFA token code.
113    pub fn with_mfa_code(mut self, token_code: String) -> Self {
114        self.grant.token_code = Some(token_code);
115        self
116    }
117
118    /// Create a fixed-flow provider from AWS environment variables.
119    pub fn from_env(ctx: &Context, sts_signer: Signer<Credential>) -> Option<Self> {
120        let role_arn = ctx.env_var("AWS_ROLE_ARN")?;
121        let mut provider = Self::new(role_arn, sts_signer);
122
123        if let Some(name) = ctx.env_var("AWS_ROLE_SESSION_NAME") {
124            provider = provider.with_role_session_name(name);
125        }
126        if let Some(id) = ctx.env_var("AWS_EXTERNAL_ID") {
127            provider = provider.with_external_id(id);
128        }
129        if let Some(region) = ctx.env_var("AWS_REGION") {
130            provider = provider.with_region(region);
131        }
132        if ctx.env_var("AWS_STS_REGIONAL_ENDPOINTS").as_deref() == Some("regional") {
133            provider = provider.with_regional_sts_endpoint();
134        }
135
136        Some(provider)
137    }
138}
139
140impl ProvideCredential for AssumeRoleCredentialProvider {
141    type Credential = Credential;
142
143    async fn provide_credential(&self, ctx: &Context) -> Result<Option<Self::Credential>> {
144        match self.region.as_deref() {
145            Some(region) => self.grant.validate_for_region(region)?,
146            None => self.grant.validate_for_partition("aws")?,
147        }
148        let endpoint = sts_endpoint(self.region.as_deref(), self.use_regional_sts_endpoint)?;
149        let operation = AssumeRoleOperation::new(endpoint, &self.grant, self.duration_seconds)?;
150        operation.execute(ctx, &self.sts_signer).await.map(Some)
151    }
152}