Skip to main content

reqsign_aws_core/provide_credential/
env.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::{Credential, constants::*};
19use reqsign_core::{Context, ProvideCredential, Result};
20
21/// EnvCredentialProvider loads AWS credentials from environment variables.
22///
23/// This provider looks for the following environment variables:
24/// - `AWS_ACCESS_KEY_ID`: The AWS access key ID
25/// - `AWS_SECRET_ACCESS_KEY`: The AWS secret access key
26/// - `AWS_SESSION_TOKEN`: The AWS session token (optional)
27#[derive(Debug, Default, Clone)]
28pub struct EnvCredentialProvider;
29
30impl EnvCredentialProvider {
31    /// Create a new EnvCredentialProvider.
32    pub fn new() -> Self {
33        Self
34    }
35}
36impl ProvideCredential for EnvCredentialProvider {
37    type Credential = Credential;
38
39    async fn provide_credential(&self, ctx: &Context) -> Result<Option<Self::Credential>> {
40        let envs = ctx.env_vars();
41
42        let access_key_id = envs.get(AWS_ACCESS_KEY_ID);
43        let secret_access_key = envs.get(AWS_SECRET_ACCESS_KEY);
44
45        match (access_key_id, secret_access_key) {
46            (Some(ak), Some(sk)) => Ok(Some(Credential {
47                access_key_id: ak.clone(),
48                secret_access_key: sk.clone(),
49                session_token: envs.get(AWS_SESSION_TOKEN).cloned(),
50                expires_in: None,
51            })),
52            _ => Ok(None),
53        }
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    use reqsign_core::{OsEnv, StaticEnv};
61    use reqsign_file_read_tokio::TokioFileRead;
62    use reqsign_http_send_reqwest::ReqwestHttpSend;
63    use std::collections::HashMap;
64
65    #[tokio::test]
66    async fn test_env_credential_provider() -> anyhow::Result<()> {
67        // Test with valid credentials
68        let envs = HashMap::from([
69            (AWS_ACCESS_KEY_ID.to_string(), "test_access_key".to_string()),
70            (
71                AWS_SECRET_ACCESS_KEY.to_string(),
72                "test_secret_key".to_string(),
73            ),
74        ]);
75
76        let ctx = Context::new()
77            .with_file_read(TokioFileRead)
78            .with_http_send(ReqwestHttpSend::default())
79            .with_env(OsEnv)
80            .with_env(StaticEnv {
81                home_dir: None,
82                envs,
83            });
84
85        let provider = EnvCredentialProvider::new();
86        let cred = provider.provide_credential(&ctx).await?;
87        assert!(cred.is_some());
88        let cred = cred.unwrap();
89        assert_eq!(cred.access_key_id, "test_access_key");
90        assert_eq!(cred.secret_access_key, "test_secret_key");
91        assert!(cred.session_token.is_none());
92
93        Ok(())
94    }
95
96    #[tokio::test]
97    async fn test_env_credential_provider_with_session_token() -> anyhow::Result<()> {
98        let envs = HashMap::from([
99            (AWS_ACCESS_KEY_ID.to_string(), "test_access_key".to_string()),
100            (
101                AWS_SECRET_ACCESS_KEY.to_string(),
102                "test_secret_key".to_string(),
103            ),
104            (
105                AWS_SESSION_TOKEN.to_string(),
106                "test_session_token".to_string(),
107            ),
108        ]);
109
110        let ctx = Context::new()
111            .with_file_read(TokioFileRead)
112            .with_http_send(ReqwestHttpSend::default())
113            .with_env(OsEnv)
114            .with_env(StaticEnv {
115                home_dir: None,
116                envs,
117            });
118
119        let provider = EnvCredentialProvider::new();
120        let cred = provider.provide_credential(&ctx).await?;
121        assert!(cred.is_some());
122        let cred = cred.unwrap();
123        assert_eq!(cred.access_key_id, "test_access_key");
124        assert_eq!(cred.secret_access_key, "test_secret_key");
125        assert_eq!(cred.session_token, Some("test_session_token".to_string()));
126
127        Ok(())
128    }
129
130    #[tokio::test]
131    async fn test_env_credential_provider_missing_credentials() -> anyhow::Result<()> {
132        let ctx = Context::new()
133            .with_file_read(TokioFileRead)
134            .with_http_send(ReqwestHttpSend::default())
135            .with_env(OsEnv)
136            .with_env(StaticEnv::default());
137
138        let provider = EnvCredentialProvider::new();
139        let cred = provider.provide_credential(&ctx).await?;
140        assert!(cred.is_none());
141
142        Ok(())
143    }
144
145    #[tokio::test]
146    async fn test_env_credential_provider_partial_credentials() -> anyhow::Result<()> {
147        // Only access key ID
148        let envs = HashMap::from([(AWS_ACCESS_KEY_ID.to_string(), "test_access_key".to_string())]);
149
150        let ctx = Context::new()
151            .with_file_read(TokioFileRead)
152            .with_http_send(ReqwestHttpSend::default())
153            .with_env(OsEnv)
154            .with_env(StaticEnv {
155                home_dir: None,
156                envs,
157            });
158
159        let provider = EnvCredentialProvider::new();
160        let cred = provider.provide_credential(&ctx).await?;
161        assert!(cred.is_none());
162
163        Ok(())
164    }
165}