-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Credentials Provider Initial Implementation (#179)
- Loading branch information
Showing
8 changed files
with
268 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
target/ | ||
Cargo.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
[package] | ||
name = "auth" | ||
version = "0.1.0" | ||
authors = ["Russell Cohen <rcoh@amazon.com>"] | ||
edition = "2018" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
pub mod provider; | ||
|
||
use std::time::SystemTime; | ||
use std::error::Error; | ||
use std::fmt::{Display, Formatter, Debug}; | ||
use std::fmt; | ||
|
||
/// AWS SDK Credentials | ||
/// | ||
/// An opaque struct representing credentials that may be used in an AWS SDK, modeled on | ||
/// the [CRT credentials implementation](https://github.com/awslabs/aws-c-auth/blob/main/source/credentials.c). | ||
/// | ||
/// Future design note: It may be desirable to make Credentials cheap to clone because they are cloned frequently. | ||
#[derive(Clone)] | ||
pub struct Credentials { | ||
access_key_id: String, | ||
secret_access_key: String, | ||
session_token: Option<String>, | ||
|
||
/// Credential Expiry | ||
/// | ||
/// A timepoint at which the credentials should no longer | ||
/// be used because they have expired. The primary purpose of this value is to allow | ||
/// credentials to communicate to the caching provider when they need to be refreshed. | ||
/// | ||
/// If these credentials never expire, this value will be set to `None` | ||
expires_after: Option<SystemTime>, | ||
|
||
provider_name: &'static str, | ||
} | ||
|
||
impl Debug for Credentials { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||
let mut creds = f.debug_struct("Credentials"); | ||
creds.field("provider_name", &self.provider_name); | ||
creds.finish() | ||
} | ||
} | ||
|
||
const STATIC_CREDENTIALS: &'static str = "static"; | ||
impl Credentials { | ||
pub fn from_keys( | ||
access_key_id: impl Into<String>, | ||
secret_access_key: impl Into<String>, | ||
session_token: Option<String>, | ||
) -> Self { | ||
Credentials { | ||
access_key_id: access_key_id.into(), | ||
secret_access_key: secret_access_key.into(), | ||
session_token, | ||
expires_after: None, | ||
|
||
provider_name: STATIC_CREDENTIALS | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
#[non_exhaustive] | ||
pub enum CredentialsError { | ||
CredentialsNotLoaded, | ||
Unhandled(Box<dyn Error + Send + Sync + 'static>) | ||
} | ||
|
||
impl Display for CredentialsError { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||
match self { | ||
CredentialsError::CredentialsNotLoaded => write!(f, "CredentialsNotLoaded"), | ||
CredentialsError::Unhandled(err) => write!(f, "{}", err) | ||
} | ||
} | ||
} | ||
|
||
impl Error for CredentialsError { | ||
fn source(&self) -> Option<&(dyn Error + 'static)> { | ||
match self { | ||
CredentialsError::Unhandled(e) => Some(e.as_ref() as _), | ||
_ => None | ||
} | ||
} | ||
} | ||
|
||
/// A Credentials Provider | ||
/// | ||
/// This interface is intentionally NOT async. Credential providers should provide a separate | ||
/// async method to drive refresh (eg. in a background task). | ||
/// | ||
/// Pending future design iteration, an async credentials provider may be introduced. | ||
pub trait ProvideCredentials: Send + Sync { | ||
fn credentials(&self) -> Result<Credentials, CredentialsError>; | ||
} | ||
|
||
impl ProvideCredentials for Credentials { | ||
fn credentials(&self) -> Result<Credentials, CredentialsError> { | ||
Ok(self.clone()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0. | ||
*/ | ||
|
||
use crate::{ProvideCredentials, Credentials, CredentialsError}; | ||
use std::env::VarError; | ||
use std::collections::HashMap; | ||
|
||
/// Load Credentials from Environment Variables | ||
pub struct EnvironmentVariableCredentialsProvider { | ||
env: Box<dyn Fn(&str) -> Result<String, VarError> + Send + Sync> | ||
} | ||
|
||
impl EnvironmentVariableCredentialsProvider { | ||
pub fn new() -> Self { | ||
EnvironmentVariableCredentialsProvider { env: Box::new(var) } | ||
} | ||
|
||
/// Create a EnvironmentVariable provider from a HashMap for testing | ||
fn for_map(env: HashMap<String, String>) -> Self { | ||
EnvironmentVariableCredentialsProvider { | ||
env: Box::new(move |key: &str| { | ||
env.get(key).ok_or(VarError::NotPresent).map(|k|k.to_string()) | ||
}) | ||
} | ||
} | ||
} | ||
|
||
fn var(key: &str) -> Result<String, VarError> { | ||
std::env::var(key) | ||
} | ||
|
||
const ENV_PROVIDER: &'static str = "EnvironmentVariable"; | ||
|
||
impl ProvideCredentials for EnvironmentVariableCredentialsProvider { | ||
fn credentials(&self) -> Result<Credentials, CredentialsError> { | ||
let access_key = (self.env)("AWS_ACCESS_KEY_ID").map_err(to_cred_error)?; | ||
let secret_key = | ||
(self.env)("AWS_SECRET_ACCESS_KEY").or_else(|_|(self.env)("SECRET_ACCESS_KEY")).map_err(to_cred_error)?; | ||
let session_token = (self.env)("AWS_SESSION_TOKEN").ok(); | ||
Ok(Credentials { | ||
access_key_id: access_key, | ||
secret_access_key: secret_key, | ||
session_token, | ||
expires_after: None, | ||
provider_name: ENV_PROVIDER | ||
}) | ||
} | ||
} | ||
|
||
fn to_cred_error(err: VarError) -> CredentialsError { | ||
match err { | ||
VarError::NotPresent => CredentialsError::CredentialsNotLoaded, | ||
e @ VarError::NotUnicode(_) => CredentialsError::Unhandled(Box::new(e)) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use crate::provider::EnvironmentVariableCredentialsProvider; | ||
use std::collections::HashMap; | ||
use crate::{ProvideCredentials, CredentialsError}; | ||
|
||
#[test] | ||
fn valid_no_token() { | ||
let mut env = HashMap::new(); | ||
env.insert("AWS_ACCESS_KEY_ID".to_owned(), "access".to_owned()); | ||
env.insert("AWS_SECRET_ACCESS_KEY".to_owned(), "secret".to_owned()); | ||
|
||
let provider = EnvironmentVariableCredentialsProvider::for_map(env); | ||
let creds = provider.credentials().expect("valid credentials"); | ||
assert_eq!(creds.session_token, None); | ||
assert_eq!(creds.access_key_id, "access"); | ||
assert_eq!(creds.secret_access_key, "secret"); | ||
} | ||
|
||
#[test] | ||
fn valid_with_token() { | ||
let mut env = HashMap::new(); | ||
env.insert("AWS_ACCESS_KEY_ID".to_owned(), "access".to_owned()); | ||
env.insert("AWS_SECRET_ACCESS_KEY".to_owned(), "secret".to_owned()); | ||
env.insert("AWS_SESSION_TOKEN".to_owned(), "token".to_owned()); | ||
|
||
let provider = EnvironmentVariableCredentialsProvider::for_map(env); | ||
let creds = provider.credentials().expect("valid credentials"); | ||
assert_eq!(creds.session_token.unwrap(), "token"); | ||
assert_eq!(creds.access_key_id, "access"); | ||
assert_eq!(creds.secret_access_key, "secret"); | ||
} | ||
|
||
#[test] | ||
fn secret_key_fallback() { | ||
let mut env = HashMap::new(); | ||
env.insert("AWS_ACCESS_KEY_ID".to_owned(), "access".to_owned()); | ||
env.insert("SECRET_ACCESS_KEY".to_owned(), "secret".to_owned()); | ||
env.insert("AWS_SESSION_TOKEN".to_owned(), "token".to_owned()); | ||
|
||
let provider = EnvironmentVariableCredentialsProvider::for_map(env); | ||
let creds = provider.credentials().expect("valid credentials"); | ||
assert_eq!(creds.session_token.unwrap(), "token"); | ||
assert_eq!(creds.access_key_id, "access"); | ||
assert_eq!(creds.secret_access_key, "secret"); | ||
|
||
} | ||
|
||
#[test] | ||
fn missing() { | ||
let env = HashMap::new(); | ||
let provider = EnvironmentVariableCredentialsProvider::for_map(env); | ||
let err = provider.credentials().expect_err("no credentials defined"); | ||
match err { | ||
CredentialsError::Unhandled(_ ) => panic!("wrong error type"), | ||
_ => () | ||
}; | ||
} | ||
|
||
#[test] | ||
fn real_environment() { | ||
let provider = EnvironmentVariableCredentialsProvider::new(); | ||
// we don't know what's in the env, just make sure it doesn't crash. | ||
let _ = provider.credentials(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
#!/bin/bash | ||
|
||
# | ||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
# SPDX-License-Identifier: Apache-2.0. | ||
# | ||
|
||
set -e | ||
for crate in "$(dirname "$0")"/*/ | ||
do | ||
if [ -d "$crate" ] && [ -f "$crate/Cargo.toml" ]; then | ||
echo "Testing $crate" | ||
(cd "$crate" && cargo fmt) | ||
(cd "$crate" && cargo fmt -- --check) | ||
(cd "$crate" && cargo clippy -- -D warnings) | ||
(cd "$crate" && cargo test) | ||
(cd "$crate" && cargo doc --no-deps) | ||
fi | ||
done |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters