aws_credential_types/
credentials_impl.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use aws_smithy_types::date_time::Format;
7use std::fmt;
8use std::fmt::{Debug, Formatter};
9use std::sync::Arc;
10use std::time::{SystemTime, UNIX_EPOCH};
11use zeroize::Zeroizing;
12
13use aws_smithy_runtime_api::client::identity::Identity;
14
15use crate::attributes::AccountId;
16
17/// AWS SDK Credentials
18///
19/// An opaque struct representing credentials that may be used in an AWS SDK, modeled on
20/// the [CRT credentials implementation](https://github.com/awslabs/aws-c-auth/blob/main/source/credentials.c).
21///
22/// When `Credentials` is dropped, its contents are zeroed in memory. Credentials uses an interior Arc to ensure
23/// that even when cloned, credentials don't exist in multiple memory locations.
24#[derive(Clone, Eq, PartialEq)]
25pub struct Credentials(Arc<Inner>);
26
27#[derive(Clone, Eq, PartialEq)]
28struct Inner {
29    access_key_id: Zeroizing<String>,
30    secret_access_key: Zeroizing<String>,
31    session_token: Zeroizing<Option<String>>,
32
33    /// Credential Expiry
34    ///
35    /// A SystemTime at which the credentials should no longer be used because they have expired.
36    /// The primary purpose of this value is to allow credentials to communicate to the caching
37    /// provider when they need to be refreshed.
38    ///
39    /// If these credentials never expire, this value will be set to `None`
40    expires_after: Option<SystemTime>,
41
42    // Optional piece of data to support account-based endpoints.
43    // https://docs.aws.amazon.com/sdkref/latest/guide/feature-account-endpoints.html
44    account_id: Option<AccountId>,
45
46    provider_name: &'static str,
47}
48
49impl Debug for Credentials {
50    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
51        let mut creds = f.debug_struct("Credentials");
52        creds
53            .field("provider_name", &self.0.provider_name)
54            .field("access_key_id", &self.0.access_key_id.as_str())
55            .field("secret_access_key", &"** redacted **");
56        if let Some(expiry) = self.expiry() {
57            if let Some(formatted) = expiry.duration_since(UNIX_EPOCH).ok().and_then(|dur| {
58                aws_smithy_types::DateTime::from_secs(dur.as_secs() as _)
59                    .fmt(Format::DateTime)
60                    .ok()
61            }) {
62                creds.field("expires_after", &formatted);
63            } else {
64                creds.field("expires_after", &expiry);
65            }
66        } else {
67            creds.field("expires_after", &"never");
68        }
69        if let Some(account_id) = &self.0.account_id {
70            creds.field("account_id", &account_id.as_str());
71        }
72        creds.finish()
73    }
74}
75
76#[cfg(feature = "hardcoded-credentials")]
77const STATIC_CREDENTIALS: &str = "Static";
78
79impl Credentials {
80    /// Returns builder for `Credentials`.
81    pub fn builder() -> CredentialsBuilder {
82        CredentialsBuilder::default()
83    }
84
85    /// Creates `Credentials`.
86    ///
87    /// This is intended to be used from a custom credentials provider implementation.
88    /// It is __NOT__ secure to hardcode credentials into your application.
89    pub fn new(
90        access_key_id: impl Into<String>,
91        secret_access_key: impl Into<String>,
92        session_token: Option<String>,
93        expires_after: Option<SystemTime>,
94        provider_name: &'static str,
95    ) -> Self {
96        Credentials(Arc::new(Inner {
97            access_key_id: Zeroizing::new(access_key_id.into()),
98            secret_access_key: Zeroizing::new(secret_access_key.into()),
99            session_token: Zeroizing::new(session_token),
100            expires_after,
101            account_id: None,
102            provider_name,
103        }))
104    }
105
106    /// Creates `Credentials` from hardcoded access key, secret key, and session token.
107    ///
108    /// _Note: In general, you should prefer to use the credential providers that come
109    /// with the AWS SDK to get credentials. It is __NOT__ secure to hardcode credentials
110    /// into your application. If you're writing a custom credentials provider, then
111    /// use [`Credentials::new`] instead of this._
112    ///
113    /// This function requires the `hardcoded-credentials` feature to be enabled.
114    ///
115    /// [`Credentials`] implement
116    /// [`ProvideCredentials`](crate::provider::ProvideCredentials) directly, so no custom provider
117    /// implementation is required when wiring these up to a client:
118    /// ```rust
119    /// use aws_credential_types::Credentials;
120    /// # mod service {
121    /// #     use aws_credential_types::provider::ProvideCredentials;
122    /// #     pub struct Config;
123    /// #     impl Config {
124    /// #        pub fn builder() -> Self {
125    /// #            Config
126    /// #        }
127    /// #        pub fn credentials_provider(self, provider: impl ProvideCredentials + 'static) -> Self {
128    /// #            self
129    /// #        }
130    /// #        pub fn build(self) -> Config { Config }
131    /// #     }
132    /// #     pub struct Client;
133    /// #     impl Client {
134    /// #        pub fn from_conf(config: Config) -> Self {
135    /// #            Client
136    /// #        }
137    /// #     }
138    /// # }
139    /// # use service::{Config, Client};
140    ///
141    /// let creds = Credentials::from_keys("akid", "secret_key", None);
142    /// let config = Config::builder()
143    ///     .credentials_provider(creds)
144    ///     .build();
145    /// let client = Client::from_conf(config);
146    /// ```
147    #[cfg(feature = "hardcoded-credentials")]
148    pub fn from_keys(
149        access_key_id: impl Into<String>,
150        secret_access_key: impl Into<String>,
151        session_token: Option<String>,
152    ) -> Self {
153        Self::new(
154            access_key_id,
155            secret_access_key,
156            session_token,
157            None,
158            STATIC_CREDENTIALS,
159        )
160    }
161
162    /// Returns the access key ID.
163    pub fn access_key_id(&self) -> &str {
164        &self.0.access_key_id
165    }
166
167    /// Returns the secret access key.
168    pub fn secret_access_key(&self) -> &str {
169        &self.0.secret_access_key
170    }
171
172    /// Returns the time when the credentials will expire.
173    pub fn expiry(&self) -> Option<SystemTime> {
174        self.0.expires_after
175    }
176
177    /// Returns a mutable reference to the time when the credentials will expire.
178    pub fn expiry_mut(&mut self) -> &mut Option<SystemTime> {
179        &mut Arc::make_mut(&mut self.0).expires_after
180    }
181
182    /// Returns the account ID.
183    pub fn account_id(&self) -> Option<&AccountId> {
184        self.0.account_id.as_ref()
185    }
186
187    /// Returns the session token.
188    pub fn session_token(&self) -> Option<&str> {
189        self.0.session_token.as_deref()
190    }
191}
192
193/// Builder for [`Credentials`]
194///
195/// Similar to [`Credentials::new`], the use of the builder is intended for a custom credentials provider implementation.
196/// It is __NOT__ secure to hardcode credentials into your application.
197#[derive(Default, Clone)]
198#[allow(missing_debug_implementations)] // for security reasons, and we can add manual `impl Debug` just like `Credentials`, if needed.
199pub struct CredentialsBuilder {
200    access_key_id: Option<Zeroizing<String>>,
201    secret_access_key: Option<Zeroizing<String>>,
202    session_token: Zeroizing<Option<String>>,
203    expires_after: Option<SystemTime>,
204    account_id: Option<AccountId>,
205    provider_name: Option<&'static str>,
206}
207
208impl CredentialsBuilder {
209    /// Set access key id for the builder.
210    pub fn access_key_id(mut self, access_key_id: impl Into<String>) -> Self {
211        self.access_key_id = Some(Zeroizing::new(access_key_id.into()));
212        self
213    }
214
215    /// Set secret access key for the builder.
216    pub fn secret_access_key(mut self, secret_access_key: impl Into<String>) -> Self {
217        self.secret_access_key = Some(Zeroizing::new(secret_access_key.into()));
218        self
219    }
220
221    /// Set session token for the builder.
222    pub fn session_token(mut self, session_token: impl Into<String>) -> Self {
223        self.set_session_token(Some(session_token.into()));
224        self
225    }
226
227    /// Set session token for the builder.
228    pub fn set_session_token(&mut self, session_token: Option<String>) {
229        self.session_token = Zeroizing::new(session_token);
230    }
231
232    /// Set expiry for the builder.
233    pub fn expiry(mut self, expiry: SystemTime) -> Self {
234        self.set_expiry(Some(expiry));
235        self
236    }
237
238    /// Set expiry for the builder.
239    pub fn set_expiry(&mut self, expiry: Option<SystemTime>) {
240        self.expires_after = expiry;
241    }
242
243    /// Set account ID for the builder.
244    pub fn account_id(mut self, account_id: impl Into<AccountId>) -> Self {
245        self.set_account_id(Some(account_id.into()));
246        self
247    }
248
249    /// Set account ID for the builder.
250    pub fn set_account_id(&mut self, account_id: Option<AccountId>) {
251        self.account_id = account_id;
252    }
253
254    /// Set provicer name for the builder.
255    pub fn provider_name(mut self, provider_name: &'static str) -> Self {
256        self.provider_name = Some(provider_name);
257        self
258    }
259
260    /// Build [`Credentials`] from the builder.
261    pub fn build(self) -> Credentials {
262        Credentials(Arc::new(Inner {
263            access_key_id: self
264                .access_key_id
265                .expect("required field `access_key_id` missing"),
266            secret_access_key: self
267                .secret_access_key
268                .expect("required field `secret_access_key` missing"),
269            session_token: self.session_token,
270            expires_after: self.expires_after,
271            account_id: self.account_id,
272            provider_name: self
273                .provider_name
274                .expect("required field `provider_name` missing"),
275        }))
276    }
277}
278
279#[cfg(feature = "test-util")]
280impl Credentials {
281    /// Creates a test `Credentials` with no session token.
282    pub fn for_tests() -> Self {
283        Self::new(
284            "ANOTREAL",
285            "notrealrnrELgWzOk3IfjzDKtFBhDby",
286            None,
287            None,
288            "test",
289        )
290    }
291
292    /// Creates a test `Credentials` that include a session token.
293    pub fn for_tests_with_session_token() -> Self {
294        Self::new(
295            "ANOTREAL",
296            "notrealrnrELgWzOk3IfjzDKtFBhDby",
297            Some("notarealsessiontoken".to_string()),
298            None,
299            "test",
300        )
301    }
302}
303
304#[cfg(feature = "test-util")]
305impl CredentialsBuilder {
306    /// Creates a test `CredentialsBuilder` with the required fields:
307    /// `access_key_id`, `secret_access_key`, and `provider_name`.
308    pub fn for_tests() -> Self {
309        CredentialsBuilder::default()
310            .access_key_id("ANOTREAL")
311            .secret_access_key("notrealrnrELgWzOk3IfjzDKtFBhDby")
312            .provider_name("test")
313    }
314}
315
316impl From<Credentials> for Identity {
317    fn from(val: Credentials) -> Self {
318        let expiry = val.expiry();
319        let mut builder = if let Some(account_id) = val.account_id() {
320            Identity::builder().property(account_id.clone()).data(val)
321        } else {
322            Identity::builder().data(val)
323        };
324        builder.set_expiration(expiry);
325        builder.build().expect("set required fields")
326    }
327}
328
329#[cfg(test)]
330mod test {
331    use crate::Credentials;
332    use std::time::{Duration, UNIX_EPOCH};
333
334    #[test]
335    fn debug_impl() {
336        let creds = Credentials::new(
337            "akid",
338            "secret",
339            Some("token".into()),
340            Some(UNIX_EPOCH + Duration::from_secs(1234567890)),
341            "debug tester",
342        );
343        assert_eq!(
344            format!("{:?}", creds),
345            r#"Credentials { provider_name: "debug tester", access_key_id: "akid", secret_access_key: "** redacted **", expires_after: "2009-02-13T23:31:30Z" }"#
346        );
347
348        // with account ID
349        let creds = Credentials::builder()
350            .access_key_id("akid")
351            .secret_access_key("secret")
352            .session_token("token")
353            .expiry(UNIX_EPOCH + Duration::from_secs(1234567890))
354            .account_id("012345678901")
355            .provider_name("debug tester")
356            .build();
357        assert_eq!(
358            format!("{:?}", creds),
359            r#"Credentials { provider_name: "debug tester", access_key_id: "akid", secret_access_key: "** redacted **", expires_after: "2009-02-13T23:31:30Z", account_id: "012345678901" }"#
360        );
361    }
362}