rusty_s3/credentials/
mod.rs

1//! Credentials management types
2//!
3//! [`RotatingCredentials`] wraps [`Credentials`] and gives the ability to
4//! rotate them at any point in the program, keeping all copies of the same
5//! [`RotatingCredentials`] in sync with the latest version.
6//!
7//! [`Ec2SecurityCredentialsMetadataResponse`] parses the response from the
8//! [EC2 metadata service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html),
9//! which provides an endpoint for retrieving credentials using the permissions
10//! for the [attached IAM roles](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html).
11
12use std::env;
13use std::fmt::{self, Debug, Formatter};
14
15#[allow(clippy::module_name_repetitions)]
16pub use self::rotating::RotatingCredentials;
17#[cfg(feature = "full")]
18pub use self::serde::Ec2SecurityCredentialsMetadataResponse;
19use zeroize::Zeroizing;
20
21mod rotating;
22#[cfg(feature = "full")]
23mod serde;
24
25/// S3 credentials
26#[derive(Clone, PartialEq, Eq)]
27pub struct Credentials {
28    key: String,
29    secret: Zeroizing<String>,
30    token: Option<String>,
31}
32
33impl Credentials {
34    /// Construct a new `Credentials` using the provided key and secret
35    #[inline]
36    pub fn new(key: impl Into<String>, secret: impl Into<String>) -> Self {
37        Self::new_with_maybe_token(key.into(), secret.into(), None)
38    }
39
40    /// Construct a new `Credentials` using the provided key, secret and token
41    #[inline]
42    pub fn new_with_token(
43        key: impl Into<String>,
44        secret: impl Into<String>,
45        token: impl Into<String>,
46    ) -> Self {
47        Self::new_with_maybe_token(key.into(), secret.into(), Some(token.into()))
48    }
49
50    #[inline]
51    pub(super) fn new_with_maybe_token(key: String, secret: String, token: Option<String>) -> Self {
52        Self {
53            key,
54            secret: Zeroizing::new(secret),
55            token,
56        }
57    }
58
59    /// Construct a new `Credentials` using AWS's default environment variables
60    ///
61    /// Reads the key from the `AWS_ACCESS_KEY_ID` environment variable and the secret
62    /// from the `AWS_SECRET_ACCESS_KEY` environment variable.
63    /// If `AWS_SESSION_TOKEN` is set a token is also read.
64    /// Returns `None` if either environment variables aren't set or they aren't valid utf-8.
65    #[must_use]
66    pub fn from_env() -> Option<Self> {
67        let key = env::var("AWS_ACCESS_KEY_ID").ok()?;
68        let secret = env::var("AWS_SECRET_ACCESS_KEY").ok()?;
69        let token = env::var("AWS_SESSION_TOKEN").ok();
70        Some(Self::new_with_maybe_token(key, secret, token))
71    }
72
73    /// Get the key of this `Credentials`
74    #[inline]
75    #[must_use]
76    pub fn key(&self) -> &str {
77        &self.key
78    }
79
80    /// Get the secret of this `Credentials`
81    #[inline]
82    #[must_use]
83    pub fn secret(&self) -> &str {
84        &self.secret
85    }
86
87    /// Get the token of this `Credentials`, if present
88    #[inline]
89    #[must_use]
90    pub fn token(&self) -> Option<&str> {
91        self.token.as_deref()
92    }
93}
94
95impl Debug for Credentials {
96    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
97        f.debug_struct("Credentials")
98            .field("key", &self.key)
99            .finish_non_exhaustive()
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use pretty_assertions::assert_eq;
106
107    use super::*;
108
109    #[test]
110    fn key_secret() {
111        let credentials = Credentials::new("abcd", "1234");
112        assert_eq!(credentials.key(), "abcd");
113        assert_eq!(credentials.secret(), "1234");
114        assert!(credentials.token().is_none());
115    }
116
117    #[test]
118    fn key_secret_token() {
119        let credentials = Credentials::new_with_token("abcd", "1234", "xyz");
120        assert_eq!(credentials.key(), "abcd");
121        assert_eq!(credentials.secret(), "1234");
122        assert_eq!(credentials.token(), Some("xyz"));
123    }
124
125    #[test]
126    fn debug() {
127        let credentials = Credentials::new("abcd", "1234");
128        let debug_output = format!("{credentials:?}");
129        assert_eq!(debug_output, "Credentials { key: \"abcd\", .. }");
130    }
131
132    #[test]
133    fn debug_token() {
134        let credentials = Credentials::new_with_token("abcd", "1234", "xyz");
135        let debug_output = format!("{credentials:?}");
136        assert_eq!(debug_output, "Credentials { key: \"abcd\", .. }");
137    }
138
139    #[test]
140    fn from_env() {
141        env::set_var("AWS_ACCESS_KEY_ID", "key");
142        env::set_var("AWS_SECRET_ACCESS_KEY", "secret");
143
144        let credentials = Credentials::from_env().unwrap();
145        assert_eq!(credentials.key(), "key");
146        assert_eq!(credentials.secret(), "secret");
147        assert!(credentials.token().is_none());
148
149        env::remove_var("AWS_ACCESS_KEY_ID");
150        env::remove_var("AWS_SECRET_ACCESS_KEY");
151
152        assert!(Credentials::from_env().is_none());
153    }
154}