rusty_s3/credentials/
serde.rs

1use std::fmt::{self, Debug, Formatter};
2use std::mem;
3
4use jiff::Timestamp;
5use serde::Deserialize;
6use zeroize::Zeroize as _;
7
8use super::{Credentials, RotatingCredentials};
9
10/// Parser for responses received from the EC2 security-credentials metadata service.
11#[derive(Clone, Deserialize)]
12pub struct Ec2SecurityCredentialsMetadataResponse {
13    #[serde(rename = "AccessKeyId")]
14    key: String,
15    #[serde(rename = "SecretAccessKey")]
16    secret: String,
17    #[serde(rename = "Token")]
18    token: String,
19    #[serde(rename = "Expiration")]
20    expiration: Timestamp,
21}
22
23impl Ec2SecurityCredentialsMetadataResponse {
24    /// Deserialize a JSON response received from the EC2 metadata service.
25    ///
26    /// Parses the credentials from a response received from
27    /// `http://169.254.169.254/latest/meta-data/iam/security-credentials/{name-of-IAM-role}`.
28    ///
29    /// # Errors
30    ///
31    /// Returns an error if the JSON is invalid.
32    pub fn deserialize(s: &str) -> Result<Self, serde_json::Error> {
33        serde_json::from_str(s)
34    }
35
36    /// Get the key of this `Ec2SecurityCredentialsMetadataResponse`
37    #[inline]
38    #[must_use]
39    pub fn key(&self) -> &str {
40        &self.key
41    }
42
43    /// Get the secret of this `Ec2SecurityCredentialsMetadataResponse`
44    #[inline]
45    #[must_use]
46    pub fn secret(&self) -> &str {
47        &self.secret
48    }
49
50    /// Get the token of this `Ec2SecurityCredentialsMetadataResponse`
51    #[inline]
52    #[must_use]
53    pub fn token(&self) -> &str {
54        &self.token
55    }
56
57    /// Get the expiration of the credentials of this `Ec2SecurityCredentialsMetadataResponse`
58    #[inline]
59    #[must_use]
60    pub const fn expiration(&self) -> Timestamp {
61        self.expiration
62    }
63
64    /// Convert this `Ec2SecurityCredentialsMetadataResponse` into [`Credentials`]
65    #[inline]
66    #[must_use]
67    pub fn into_credentials(mut self) -> Credentials {
68        let key = mem::take(&mut self.key);
69        let secret = mem::take(&mut self.secret);
70        let token = mem::take(&mut self.token);
71        Credentials::new_with_token(key, secret, token)
72    }
73
74    /// Update a [`RotatingCredentials`] with the credentials of this `Ec2SecurityCredentialsMetadataResponse`
75    #[inline]
76    pub fn rotate_credentials(mut self, rotating: &RotatingCredentials) {
77        let key = mem::take(&mut self.key);
78        let secret = mem::take(&mut self.secret);
79        let token = mem::take(&mut self.token);
80        rotating.update(key, secret, Some(token));
81    }
82}
83
84impl Debug for Ec2SecurityCredentialsMetadataResponse {
85    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
86        f.debug_struct("Ec2SecurityCredentialsMetadataResponse")
87            .field("key", &self.key)
88            .finish_non_exhaustive()
89    }
90}
91
92impl Drop for Ec2SecurityCredentialsMetadataResponse {
93    fn drop(&mut self) {
94        self.secret.zeroize();
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use pretty_assertions::assert_eq;
101
102    use super::*;
103
104    #[test]
105    fn deserialize() {
106        let json = r#"{
107    "Code" : "Success",
108    "LastUpdated" : "2020-12-28T16:47:50Z",
109    "Type" : "AWS-HMAC",
110    "AccessKeyId" : "some_access_key",
111    "SecretAccessKey" : "some_secret_key",
112    "Token" : "some_token",
113    "Expiration" : "2020-12-28T23:10:09Z"
114}"#;
115
116        let deserialized = Ec2SecurityCredentialsMetadataResponse::deserialize(json).unwrap();
117        assert_eq!(deserialized.key(), "some_access_key");
118        assert_eq!(deserialized.secret(), "some_secret_key");
119        assert_eq!(deserialized.token(), "some_token");
120        //                                                                  2020-12-28T23:10:09Z
121        assert_eq!(
122            deserialized
123                .expiration()
124                .duration_since(Timestamp::UNIX_EPOCH)
125                .as_secs(),
126            1609197009
127        );
128
129        let debug_output = format!("{deserialized:?}");
130        assert_eq!(
131            debug_output,
132            "Ec2SecurityCredentialsMetadataResponse { key: \"some_access_key\", .. }"
133        );
134    }
135}