aws_config/sso/
credentials.rs
1use super::cache::load_cached_token;
14use crate::identity::IdentityCache;
15use crate::provider_config::ProviderConfig;
16use crate::sso::SsoTokenProvider;
17use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials};
18use aws_credential_types::Credentials;
19use aws_sdk_sso::types::RoleCredentials;
20use aws_sdk_sso::Client as SsoClient;
21use aws_smithy_async::time::SharedTimeSource;
22use aws_smithy_types::DateTime;
23use aws_types::os_shim_internal::{Env, Fs};
24use aws_types::region::Region;
25use aws_types::SdkConfig;
26use std::convert::TryInto;
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 }
93}
94
95impl ProvideCredentials for SsoCredentialsProvider {
96 fn provide_credentials<'a>(&'a self) -> future::ProvideCredentials<'a>
97 where
98 Self: 'a,
99 {
100 future::ProvideCredentials::new(self.credentials())
101 }
102}
103
104#[derive(Default, Debug, Clone)]
106pub struct Builder {
107 provider_config: Option<ProviderConfig>,
108 account_id: Option<String>,
109 region: Option<Region>,
110 role_name: Option<String>,
111 start_url: Option<String>,
112 session_name: Option<String>,
113}
114
115impl Builder {
116 pub fn new() -> Self {
118 Self::default()
119 }
120
121 pub fn configure(mut self, provider_config: &ProviderConfig) -> Self {
123 self.provider_config = Some(provider_config.clone());
124 self
125 }
126
127 pub fn account_id(mut self, account_id: impl Into<String>) -> Self {
131 self.account_id = Some(account_id.into());
132 self
133 }
134
135 pub fn set_account_id(&mut self, account_id: Option<String>) -> &mut Self {
139 self.account_id = account_id;
140 self
141 }
142
143 pub fn region(mut self, region: Region) -> Self {
147 self.region = Some(region);
148 self
149 }
150
151 pub fn set_region(&mut self, region: Option<Region>) -> &mut Self {
155 self.region = region;
156 self
157 }
158
159 pub fn role_name(mut self, role_name: impl Into<String>) -> Self {
163 self.role_name = Some(role_name.into());
164 self
165 }
166
167 pub fn set_role_name(&mut self, role_name: Option<String>) -> &mut Self {
171 self.role_name = role_name;
172 self
173 }
174
175 pub fn start_url(mut self, start_url: impl Into<String>) -> Self {
179 self.start_url = Some(start_url.into());
180 self
181 }
182
183 pub fn set_start_url(&mut self, start_url: Option<String>) -> &mut Self {
187 self.start_url = start_url;
188 self
189 }
190
191 pub fn session_name(mut self, session_name: impl Into<String>) -> Self {
193 self.session_name = Some(session_name.into());
194 self
195 }
196
197 pub fn set_session_name(&mut self, session_name: Option<String>) -> &mut Self {
199 self.session_name = session_name;
200 self
201 }
202
203 pub fn build(self) -> SsoCredentialsProvider {
212 let provider_config = self.provider_config.unwrap_or_default();
213 let sso_config = SsoProviderConfig {
214 account_id: self.account_id.expect("account_id must be set"),
215 region: self.region.expect("region must be set"),
216 role_name: self.role_name.expect("role_name must be set"),
217 start_url: self.start_url.expect("start_url must be set"),
218 session_name: self.session_name,
219 };
220 SsoCredentialsProvider::new(&provider_config, sso_config)
221 }
222}
223
224#[derive(Debug)]
225pub(crate) struct SsoProviderConfig {
226 pub(crate) account_id: String,
227 pub(crate) role_name: String,
228 pub(crate) start_url: String,
229 pub(crate) region: Region,
230 pub(crate) session_name: Option<String>,
231}
232
233async fn load_sso_credentials(
234 sso_provider_config: &SsoProviderConfig,
235 sdk_config: &SdkConfig,
236 token_provider: Option<&SsoTokenProvider>,
237 env: &Env,
238 fs: &Fs,
239 time_source: SharedTimeSource,
240) -> provider::Result {
241 let token = if let Some(token_provider) = token_provider {
242 token_provider
243 .resolve_token(time_source)
244 .await
245 .map_err(CredentialsError::provider_error)?
246 } else {
247 load_cached_token(env, fs, &sso_provider_config.start_url)
249 .await
250 .map_err(CredentialsError::provider_error)?
251 };
252
253 let config = sdk_config
254 .to_builder()
255 .region(sso_provider_config.region.clone())
256 .identity_cache(IdentityCache::no_cache())
257 .build();
258 let client = SsoClient::new(&config);
260 let resp = client
261 .get_role_credentials()
262 .role_name(&sso_provider_config.role_name)
263 .access_token(&*token.access_token)
264 .account_id(&sso_provider_config.account_id)
265 .send()
266 .await
267 .map_err(CredentialsError::provider_error)?;
268 let credentials: RoleCredentials = resp
269 .role_credentials
270 .ok_or_else(|| CredentialsError::unhandled("SSO did not return credentials"))?;
271 let akid = credentials
272 .access_key_id
273 .ok_or_else(|| CredentialsError::unhandled("no access key id in response"))?;
274 let secret_key = credentials
275 .secret_access_key
276 .ok_or_else(|| CredentialsError::unhandled("no secret key in response"))?;
277 let expiration = DateTime::from_millis(credentials.expiration)
278 .try_into()
279 .map_err(|err| {
280 CredentialsError::unhandled(format!(
281 "expiration could not be converted into a system time: {}",
282 err
283 ))
284 })?;
285 Ok(Credentials::new(
286 akid,
287 secret_key,
288 credentials.session_token,
289 Some(expiration),
290 "SSO",
291 ))
292}