aws_config/sso/
credentials.rs1use super::cache::load_cached_token;
14use crate::identity::IdentityCache;
15use crate::provider_config::ProviderConfig;
16use crate::sso::SsoTokenProvider;
17use aws_credential_types::credential_feature::AwsCredentialFeature;
18use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials};
19use aws_credential_types::Credentials;
20use aws_sdk_sso::types::RoleCredentials;
21use aws_sdk_sso::Client as SsoClient;
22use aws_smithy_async::time::SharedTimeSource;
23use aws_smithy_types::DateTime;
24use aws_types::os_shim_internal::{Env, Fs};
25use aws_types::region::Region;
26use aws_types::SdkConfig;
27
28#[derive(Debug)]
37pub struct SsoCredentialsProvider {
38 fs: Fs,
39 env: Env,
40 sso_provider_config: SsoProviderConfig,
41 sdk_config: SdkConfig,
42 token_provider: Option<SsoTokenProvider>,
43 time_source: SharedTimeSource,
44}
45
46impl SsoCredentialsProvider {
47 pub fn builder() -> Builder {
49 Builder::new()
50 }
51
52 pub(crate) fn new(
53 provider_config: &ProviderConfig,
54 sso_provider_config: SsoProviderConfig,
55 ) -> Self {
56 let fs = provider_config.fs();
57 let env = provider_config.env();
58
59 let token_provider = if let Some(session_name) = &sso_provider_config.session_name {
60 Some(
61 SsoTokenProvider::builder()
62 .configure(&provider_config.client_config())
63 .start_url(&sso_provider_config.start_url)
64 .session_name(session_name)
65 .region(sso_provider_config.region.clone())
66 .build_with(env.clone(), fs.clone()),
67 )
68 } else {
69 None
70 };
71
72 SsoCredentialsProvider {
73 fs,
74 env,
75 sso_provider_config,
76 sdk_config: provider_config.client_config(),
77 token_provider,
78 time_source: provider_config.time_source(),
79 }
80 }
81
82 async fn credentials(&self) -> provider::Result {
83 load_sso_credentials(
84 &self.sso_provider_config,
85 &self.sdk_config,
86 self.token_provider.as_ref(),
87 &self.env,
88 &self.fs,
89 self.time_source.clone(),
90 )
91 .await
92 .map(|mut creds| {
93 creds
94 .get_property_mut_or_default::<Vec<AwsCredentialFeature>>()
95 .push(AwsCredentialFeature::CredentialsSso);
96 creds
97 })
98 }
99}
100
101impl ProvideCredentials for SsoCredentialsProvider {
102 fn provide_credentials<'a>(&'a self) -> future::ProvideCredentials<'a>
103 where
104 Self: 'a,
105 {
106 future::ProvideCredentials::new(self.credentials())
107 }
108}
109
110#[derive(Default, Debug, Clone)]
112pub struct Builder {
113 provider_config: Option<ProviderConfig>,
114 account_id: Option<String>,
115 region: Option<Region>,
116 role_name: Option<String>,
117 start_url: Option<String>,
118 session_name: Option<String>,
119}
120
121impl Builder {
122 pub fn new() -> Self {
124 Self::default()
125 }
126
127 pub fn configure(mut self, provider_config: &ProviderConfig) -> Self {
129 self.provider_config = Some(provider_config.clone());
130 self
131 }
132
133 pub fn account_id(mut self, account_id: impl Into<String>) -> Self {
137 self.account_id = Some(account_id.into());
138 self
139 }
140
141 pub fn set_account_id(&mut self, account_id: Option<String>) -> &mut Self {
145 self.account_id = account_id;
146 self
147 }
148
149 pub fn region(mut self, region: Region) -> Self {
153 self.region = Some(region);
154 self
155 }
156
157 pub fn set_region(&mut self, region: Option<Region>) -> &mut Self {
161 self.region = region;
162 self
163 }
164
165 pub fn role_name(mut self, role_name: impl Into<String>) -> Self {
169 self.role_name = Some(role_name.into());
170 self
171 }
172
173 pub fn set_role_name(&mut self, role_name: Option<String>) -> &mut Self {
177 self.role_name = role_name;
178 self
179 }
180
181 pub fn start_url(mut self, start_url: impl Into<String>) -> Self {
185 self.start_url = Some(start_url.into());
186 self
187 }
188
189 pub fn set_start_url(&mut self, start_url: Option<String>) -> &mut Self {
193 self.start_url = start_url;
194 self
195 }
196
197 pub fn session_name(mut self, session_name: impl Into<String>) -> Self {
199 self.session_name = Some(session_name.into());
200 self
201 }
202
203 pub fn set_session_name(&mut self, session_name: Option<String>) -> &mut Self {
205 self.session_name = session_name;
206 self
207 }
208
209 pub fn build(self) -> SsoCredentialsProvider {
218 let provider_config = self.provider_config.unwrap_or_default();
219 let sso_config = SsoProviderConfig {
220 account_id: self.account_id.expect("account_id must be set"),
221 region: self.region.expect("region must be set"),
222 role_name: self.role_name.expect("role_name must be set"),
223 start_url: self.start_url.expect("start_url must be set"),
224 session_name: self.session_name,
225 };
226 SsoCredentialsProvider::new(&provider_config, sso_config)
227 }
228}
229
230#[derive(Debug)]
231pub(crate) struct SsoProviderConfig {
232 pub(crate) account_id: String,
233 pub(crate) role_name: String,
234 pub(crate) start_url: String,
235 pub(crate) region: Region,
236 pub(crate) session_name: Option<String>,
237}
238
239async fn load_sso_credentials(
240 sso_provider_config: &SsoProviderConfig,
241 sdk_config: &SdkConfig,
242 token_provider: Option<&SsoTokenProvider>,
243 env: &Env,
244 fs: &Fs,
245 time_source: SharedTimeSource,
246) -> provider::Result {
247 let token = if let Some(token_provider) = token_provider {
248 token_provider
249 .resolve_token(time_source)
250 .await
251 .map_err(CredentialsError::provider_error)?
252 } else {
253 load_cached_token(env, fs, &sso_provider_config.start_url)
255 .await
256 .map_err(CredentialsError::provider_error)?
257 };
258
259 let config = sdk_config
260 .to_builder()
261 .region(sso_provider_config.region.clone())
262 .identity_cache(IdentityCache::no_cache())
263 .build();
264 let client = SsoClient::new(&config);
266 let resp = client
267 .get_role_credentials()
268 .role_name(&sso_provider_config.role_name)
269 .access_token(&*token.access_token)
270 .account_id(&sso_provider_config.account_id)
271 .send()
272 .await
273 .map_err(CredentialsError::provider_error)?;
274 let credentials: RoleCredentials = resp
275 .role_credentials
276 .ok_or_else(|| CredentialsError::unhandled("SSO did not return credentials"))?;
277 let akid = credentials
278 .access_key_id
279 .ok_or_else(|| CredentialsError::unhandled("no access key id in response"))?;
280 let secret_key = credentials
281 .secret_access_key
282 .ok_or_else(|| CredentialsError::unhandled("no secret key in response"))?;
283 let expiration = DateTime::from_millis(credentials.expiration)
284 .try_into()
285 .map_err(|err| {
286 CredentialsError::unhandled(format!(
287 "expiration could not be converted into a system time: {}",
288 err
289 ))
290 })?;
291 let mut builder = Credentials::builder()
292 .access_key_id(akid)
293 .secret_access_key(secret_key)
294 .account_id(&sso_provider_config.account_id)
295 .expiry(expiration)
296 .provider_name("SSO");
297 builder.set_session_token(credentials.session_token);
298 Ok(builder.build())
299}