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::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;
26
27#[derive(Debug)]
36pub struct SsoCredentialsProvider {
37 fs: Fs,
38 env: Env,
39 sso_provider_config: SsoProviderConfig,
40 sdk_config: SdkConfig,
41 token_provider: Option<SsoTokenProvider>,
42 time_source: SharedTimeSource,
43}
44
45impl SsoCredentialsProvider {
46 pub fn builder() -> Builder {
48 Builder::new()
49 }
50
51 pub(crate) fn new(
52 provider_config: &ProviderConfig,
53 sso_provider_config: SsoProviderConfig,
54 ) -> Self {
55 let fs = provider_config.fs();
56 let env = provider_config.env();
57
58 let token_provider = if let Some(session_name) = &sso_provider_config.session_name {
59 Some(
60 SsoTokenProvider::builder()
61 .configure(&provider_config.client_config())
62 .start_url(&sso_provider_config.start_url)
63 .session_name(session_name)
64 .region(sso_provider_config.region.clone())
65 .build_with(env.clone(), fs.clone()),
66 )
67 } else {
68 None
69 };
70
71 SsoCredentialsProvider {
72 fs,
73 env,
74 sso_provider_config,
75 sdk_config: provider_config.client_config(),
76 token_provider,
77 time_source: provider_config.time_source(),
78 }
79 }
80
81 async fn credentials(&self) -> provider::Result {
82 load_sso_credentials(
83 &self.sso_provider_config,
84 &self.sdk_config,
85 self.token_provider.as_ref(),
86 &self.env,
87 &self.fs,
88 self.time_source.clone(),
89 )
90 .await
91 }
92}
93
94impl ProvideCredentials for SsoCredentialsProvider {
95 fn provide_credentials<'a>(&'a self) -> future::ProvideCredentials<'a>
96 where
97 Self: 'a,
98 {
99 future::ProvideCredentials::new(self.credentials())
100 }
101}
102
103#[derive(Default, Debug, Clone)]
105pub struct Builder {
106 provider_config: Option<ProviderConfig>,
107 account_id: Option<String>,
108 region: Option<Region>,
109 role_name: Option<String>,
110 start_url: Option<String>,
111 session_name: Option<String>,
112}
113
114impl Builder {
115 pub fn new() -> Self {
117 Self::default()
118 }
119
120 pub fn configure(mut self, provider_config: &ProviderConfig) -> Self {
122 self.provider_config = Some(provider_config.clone());
123 self
124 }
125
126 pub fn account_id(mut self, account_id: impl Into<String>) -> Self {
130 self.account_id = Some(account_id.into());
131 self
132 }
133
134 pub fn set_account_id(&mut self, account_id: Option<String>) -> &mut Self {
138 self.account_id = account_id;
139 self
140 }
141
142 pub fn region(mut self, region: Region) -> Self {
146 self.region = Some(region);
147 self
148 }
149
150 pub fn set_region(&mut self, region: Option<Region>) -> &mut Self {
154 self.region = region;
155 self
156 }
157
158 pub fn role_name(mut self, role_name: impl Into<String>) -> Self {
162 self.role_name = Some(role_name.into());
163 self
164 }
165
166 pub fn set_role_name(&mut self, role_name: Option<String>) -> &mut Self {
170 self.role_name = role_name;
171 self
172 }
173
174 pub fn start_url(mut self, start_url: impl Into<String>) -> Self {
178 self.start_url = Some(start_url.into());
179 self
180 }
181
182 pub fn set_start_url(&mut self, start_url: Option<String>) -> &mut Self {
186 self.start_url = start_url;
187 self
188 }
189
190 pub fn session_name(mut self, session_name: impl Into<String>) -> Self {
192 self.session_name = Some(session_name.into());
193 self
194 }
195
196 pub fn set_session_name(&mut self, session_name: Option<String>) -> &mut Self {
198 self.session_name = session_name;
199 self
200 }
201
202 pub fn build(self) -> SsoCredentialsProvider {
211 let provider_config = self.provider_config.unwrap_or_default();
212 let sso_config = SsoProviderConfig {
213 account_id: self.account_id.expect("account_id must be set"),
214 region: self.region.expect("region must be set"),
215 role_name: self.role_name.expect("role_name must be set"),
216 start_url: self.start_url.expect("start_url must be set"),
217 session_name: self.session_name,
218 };
219 SsoCredentialsProvider::new(&provider_config, sso_config)
220 }
221}
222
223#[derive(Debug)]
224pub(crate) struct SsoProviderConfig {
225 pub(crate) account_id: String,
226 pub(crate) role_name: String,
227 pub(crate) start_url: String,
228 pub(crate) region: Region,
229 pub(crate) session_name: Option<String>,
230}
231
232async fn load_sso_credentials(
233 sso_provider_config: &SsoProviderConfig,
234 sdk_config: &SdkConfig,
235 token_provider: Option<&SsoTokenProvider>,
236 env: &Env,
237 fs: &Fs,
238 time_source: SharedTimeSource,
239) -> provider::Result {
240 let token = if let Some(token_provider) = token_provider {
241 token_provider
242 .resolve_token(time_source)
243 .await
244 .map_err(CredentialsError::provider_error)?
245 } else {
246 load_cached_token(env, fs, &sso_provider_config.start_url)
248 .await
249 .map_err(CredentialsError::provider_error)?
250 };
251
252 let config = sdk_config
253 .to_builder()
254 .region(sso_provider_config.region.clone())
255 .identity_cache(IdentityCache::no_cache())
256 .build();
257 let client = SsoClient::new(&config);
259 let resp = client
260 .get_role_credentials()
261 .role_name(&sso_provider_config.role_name)
262 .access_token(&*token.access_token)
263 .account_id(&sso_provider_config.account_id)
264 .send()
265 .await
266 .map_err(CredentialsError::provider_error)?;
267 let credentials: RoleCredentials = resp
268 .role_credentials
269 .ok_or_else(|| CredentialsError::unhandled("SSO did not return credentials"))?;
270 let akid = credentials
271 .access_key_id
272 .ok_or_else(|| CredentialsError::unhandled("no access key id in response"))?;
273 let secret_key = credentials
274 .secret_access_key
275 .ok_or_else(|| CredentialsError::unhandled("no secret key in response"))?;
276 let expiration = DateTime::from_millis(credentials.expiration)
277 .try_into()
278 .map_err(|err| {
279 CredentialsError::unhandled(format!(
280 "expiration could not be converted into a system time: {}",
281 err
282 ))
283 })?;
284 Ok(Credentials::new(
285 akid,
286 secret_key,
287 credentials.session_token,
288 Some(expiration),
289 "SSO",
290 ))
291}