Unverified Commit 36bc1bd5 authored by Russell Cohen's avatar Russell Cohen Committed by GitHub
Browse files

Zero credentials when dropped (#295)

* Zero credentials when dropped

* Use the Zeroize wrapper
parent ece5e147
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -9,3 +9,4 @@ edition = "2018"

[dependencies]
smithy-http = { path = "../../../rust-runtime/smithy-http" }
zeroize = "1.2.0"
+51 −16
Original line number Diff line number Diff line
@@ -11,18 +11,22 @@ use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use std::sync::Arc;
use std::time::SystemTime;
use zeroize::Zeroizing;

/// 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.
/// When `Credentials` is dropped, its contents are zeroed in memory. Credentials uses an interior Arc to ensure
/// that even when cloned, credentials don't exist in multiple memory locations.
#[derive(Clone)]
pub struct Credentials {
    access_key_id: String,
    secret_access_key: String,
    session_token: Option<String>,
pub struct Credentials(Arc<Inner>);

struct Inner {
    access_key_id: Zeroizing<String>,
    secret_access_key: Zeroizing<String>,
    session_token: Zeroizing<Option<String>>,

    /// Credential Expiry
    ///
@@ -39,38 +43,57 @@ pub struct Credentials {
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.field("provider_name", &self.0.provider_name);
        creds.finish()
    }
}

const STATIC_CREDENTIALS: &str = "Static";
impl Credentials {
    pub fn new(
        access_key_id: impl Into<String>,
        secret_access_key: impl Into<String>,
        session_token: Option<String>,
        expires_after: Option<SystemTime>,
        provider_name: &'static str,
    ) -> Self {
        Credentials(Arc::new(Inner {
            access_key_id: Zeroizing::new(access_key_id.into()),
            secret_access_key: Zeroizing::new(secret_access_key.into()),
            session_token: Zeroizing::new(session_token),
            expires_after,
            provider_name,
        }))
    }

    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(),
        Self::new(
            access_key_id,
            secret_access_key,
            session_token,
            expires_after: None,

            provider_name: STATIC_CREDENTIALS,
        }
            None,
            STATIC_CREDENTIALS,
        )
    }

    pub fn access_key_id(&self) -> &str {
        &self.access_key_id
        &self.0.access_key_id
    }

    pub fn secret_access_key(&self) -> &str {
        &self.secret_access_key
        &self.0.secret_access_key
    }

    pub fn expiry(&self) -> Option<SystemTime> {
        self.0.expires_after
    }

    pub fn session_token(&self) -> Option<&str> {
        self.session_token.as_deref()
        self.0.session_token.as_deref()
    }
}

@@ -125,3 +148,15 @@ impl ProvideCredentials for Credentials {
pub fn set_provider(config: &mut PropertyBag, provider: Arc<dyn ProvideCredentials>) {
    config.insert(provider);
}

#[cfg(test)]
mod test {
    use crate::Credentials;

    fn assert_send_sync<T: Send + Sync>() {}

    #[test]
    fn creds_are_send_sync() {
        assert_send_sync::<Credentials>()
    }
}
+15 −15
Original line number Diff line number Diff line
@@ -48,13 +48,13 @@ impl ProvideCredentials for EnvironmentVariableCredentialsProvider {
            .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,
        Ok(Credentials::new(
            access_key,
            secret_key,
            session_token,
            expires_after: None,
            provider_name: ENV_PROVIDER,
        })
            None,
            ENV_PROVIDER,
        ))
    }
}

@@ -79,9 +79,9 @@ mod test {

        let provider = EnvironmentVariableCredentialsProvider::for_map(env);
        let creds = provider.provide_credentials().expect("valid credentials");
        assert_eq!(creds.session_token, None);
        assert_eq!(creds.access_key_id, "access");
        assert_eq!(creds.secret_access_key, "secret");
        assert_eq!(creds.session_token(), None);
        assert_eq!(creds.access_key_id(), "access");
        assert_eq!(creds.secret_access_key(), "secret");
    }

    #[test]
@@ -93,9 +93,9 @@ mod test {

        let provider = EnvironmentVariableCredentialsProvider::for_map(env);
        let creds = provider.provide_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");
        assert_eq!(creds.session_token().unwrap(), "token");
        assert_eq!(creds.access_key_id(), "access");
        assert_eq!(creds.secret_access_key(), "secret");
    }

    #[test]
@@ -107,9 +107,9 @@ mod test {

        let provider = EnvironmentVariableCredentialsProvider::for_map(env);
        let creds = provider.provide_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");
        assert_eq!(creds.session_token().unwrap(), "token");
        assert_eq!(creds.access_key_id(), "access");
        assert_eq!(creds.secret_access_key(), "secret");
    }

    #[test]