aws_mfa/
lib.rs

1use std::time::SystemTime;
2
3use anyhow::{anyhow, Result};
4use async_trait::async_trait;
5use time::{Duration, OffsetDateTime};
6
7use crate::config::{get_env_config, get_env_provider, get_file_config, get_file_provider};
8use crate::env::get_env_credentials;
9use crate::error::Error;
10use crate::error::Error::{ConvertSessionTimestampError, Other};
11use crate::io::{find_auth_credentials, save_auth_credentials};
12use crate::sts::{get_auth_credentials, get_client, get_mfa_device_arn};
13
14mod config;
15mod env;
16pub mod error;
17mod io;
18mod sts;
19
20/// Credentials received after authenticating to AWS with MFA
21pub struct Credentials {
22    access_key_id: String,
23    secret_access_key: String,
24    session_token: String,
25    session_expiration_timestamp: i64,
26}
27
28impl Credentials {
29    pub fn new(
30        access_key_id: &str,
31        secret_access_key: &str,
32        session_token: &str,
33        session_expiration_timestamp: i64,
34    ) -> Self {
35        Self {
36            access_key_id: String::from(access_key_id),
37            secret_access_key: String::from(secret_access_key),
38            session_token: String::from(session_token),
39            session_expiration_timestamp,
40        }
41    }
42
43    pub fn to_aws_credentials(&self) -> aws_credential_types::Credentials {
44        aws_credential_types::Credentials::new(
45            self.access_key_id(),
46            self.secret_access_key(),
47            Some(String::from(self.session_token())),
48            Some(SystemTime::UNIX_EPOCH + Duration::seconds(self.session_expiration_timestamp())),
49            "aws-mfa",
50        )
51    }
52
53    pub fn access_key_id(&self) -> &str {
54        &self.access_key_id
55    }
56
57    pub fn secret_access_key(&self) -> &str {
58        &self.secret_access_key
59    }
60
61    pub fn session_token(&self) -> &str {
62        &self.session_token
63    }
64
65    pub fn session_expiration_timestamp(&self) -> i64 {
66        self.session_expiration_timestamp
67    }
68
69    pub fn session_duration(&self) -> Result<Duration, Error> {
70        let session_duration =
71            OffsetDateTime::from_unix_timestamp(self.session_expiration_timestamp)
72                .map_err(ConvertSessionTimestampError)?
73                - OffsetDateTime::now_utc();
74
75        Ok(Duration::seconds(session_duration.whole_seconds()))
76    }
77
78    pub fn expired(&self) -> bool {
79        OffsetDateTime::now_utc().unix_timestamp() > self.session_expiration_timestamp
80    }
81}
82
83#[async_trait]
84pub trait CredentialsProvider {
85    async fn validate(&self) -> Result<Option<Credentials>, Error>;
86    async fn authenticate(&self) -> Result<Credentials, Error>;
87}
88
89/// Provider for authenticating to AWS with MFA using config and credentials files
90pub struct FileCredentialsProvider {
91    code: String,
92    home: String,
93    region: Option<String>,
94    profile: String,
95    suffix: String,
96    identifier: Option<String>,
97    duration: i32,
98}
99
100impl FileCredentialsProvider {
101    pub fn new(
102        code: &str,
103        home: &str,
104        region: Option<String>,
105        profile: &str,
106        suffix: &str,
107        identifier: Option<String>,
108        duration: i32,
109    ) -> Self {
110        Self {
111            code: String::from(code),
112            home: String::from(home),
113            region,
114            profile: String::from(profile),
115            suffix: String::from(suffix),
116            identifier,
117            duration,
118        }
119    }
120}
121
122#[async_trait]
123impl CredentialsProvider for FileCredentialsProvider {
124    /// Validate and return current [`Credentials`] from credentials file unless expired
125    async fn validate(&self) -> Result<Option<Credentials>, Error> {
126        if let Some(credentials) = find_auth_credentials(&self.home, &self.profile)? {
127            if !credentials.expired() {
128                return Ok(Some(credentials));
129            }
130        }
131
132        Ok(None)
133    }
134
135    /// Authenticate using [`aws_config::profile::ProfileFileCredentialsProvider`] and return new [`Credentials`]
136    async fn authenticate(&self) -> Result<Credentials, Error> {
137        let config =
138            get_file_config(&self.home, self.region.clone(), &self.profile, &self.suffix).await;
139        let provider = get_file_provider(&self.profile, &self.suffix);
140        let client = get_client(&config, provider);
141        let arn = get_mfa_device_arn(&client, self.identifier.clone()).await?;
142        let credentials = get_auth_credentials(&client, &arn, &self.code, self.duration).await?;
143
144        save_auth_credentials(&self.home, &self.profile, &credentials)?;
145
146        Ok(credentials)
147    }
148}
149
150/// Provider for authenticating to AWS with MFA using environment variables
151pub struct EnvCredentialsProvider {
152    code: String,
153    identifier: Option<String>,
154    duration: i32,
155}
156
157impl EnvCredentialsProvider {
158    pub fn new(code: &str, identifier: Option<String>, duration: i32) -> Self {
159        Self {
160            code: String::from(code),
161            identifier,
162            duration,
163        }
164    }
165}
166
167#[async_trait]
168impl CredentialsProvider for EnvCredentialsProvider {
169    /// Validate and return current [`Credentials`] from environment variables unless expired
170    async fn validate(&self) -> Result<Option<Credentials>, Error> {
171        let provider = get_env_provider();
172        if let Some(credentials) = get_env_credentials(provider).await? {
173            if credentials.expired() {
174                return Err(Other(anyhow!("expired credentials")));
175            }
176
177            return Ok(Some(credentials));
178        }
179
180        Ok(None)
181    }
182
183    /// Authenticate using [`aws_config::environment::EnvironmentVariableCredentialsProvider`]) and return new [`Credentials`]
184    async fn authenticate(&self) -> Result<Credentials, Error> {
185        let config = get_env_config().await;
186        let provider = get_env_provider();
187        let client = get_client(&config, provider);
188        let arn = get_mfa_device_arn(&client, self.identifier.clone()).await?;
189        let credentials = get_auth_credentials(&client, &arn, &self.code, self.duration).await?;
190
191        Ok(credentials)
192    }
193}