rusty_oss/credentials/
serde.rs

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