Repository: rusoto/rusoto (language: rust)

Issue:
Add support for Web Identity providers for IAM credentials
Add support for Web Identity providers for IAM credentials to the default provider chain. This feature is especially important to support the new projected token based IAM role support for EKS (Kubernetes) service accounts.

More infos here:

- https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html
- https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-minimum-sdk.html

The official AWS SDKs already have gained support for that new feature in the following releases and it would be great if rusoto will add support soon:

- Java — 1.11.623
- Java (Version 2) — 2.7.36
- Go — 1.23.13
- Python (Boto3) — 1.9.220
- Python (botocore) — 1.12.200
- AWS CLI — 1.16.232
- Node — 2.521.0
- Ruby — 2.11.345
- C++ — 1.7.174
- .NET — 3.3.580.0
- PHP — 3.110.7


Discussion / hints:
Maybe I am wrong, but I guess we first need to fix https://github.com/rusoto/rusoto/issues/1478, then we might need to consider moving the following methods from `rusoto_sts` to `rusoto_credentials` in order to avoid circular dependencies:

- `assume_role_with_web_identity`
- `assume_role_with_saml`

Those methods are special because:

1. They do not need to be signed with credentials (see AWS documentation for more details)
2. They are used to obtain AWS credentials with non AWS credentials (OAuth or OpenID Connect bearer tokens resp. SAML assertions).

Note, for this issue only `assume_role_with_web_identity` is relevant. Nevertheless, `assume_role_with_saml` has the same characteristics. 

Fixed in https://github.com/rusoto/rusoto/pull/1500 , thanks @alex-berger !
This issue is still not resolved, PR #1500 just provides the foundation and I am still working on adding support for "IAM roles for service accounts".
I intend to first add a corresponding credential provider to the STS service. Once this is in place we have to decide how we can add that provider to the default provider chain without introducing cyclic dependencies.
Gotcha. Reopening until this is fixed. 😄 

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be82b8e573a..11de5e402c3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@
 
 (Please put changes here)
 - Fixed SNS API's attributes and value keyword https://github.com/rusoto/rusoto/pull/1591
+- Adding support for web identity provider, which enables IAM roles for Kubernetes service accounts.
+- Add object-safe AwsCredentialsProvider trait as alternative to the existing generic `ProvideAwsCredentials
+  trait.
+- Introduce `Secret` type to automatically zero-out memory use to stored secret credentials. So far, 
+  only used in the new web identity provider.
+- Introduce `Variable` to abstract over certain credential provider input parameters.
 
 ## [0.42.0] - 2019-11-18
 
diff --git a/rusoto/credential/Cargo.toml b/rusoto/credential/Cargo.toml
index cf9dda8bc0a..2291bec5e18 100644
--- a/rusoto/credential/Cargo.toml
+++ b/rusoto/credential/Cargo.toml
@@ -32,6 +32,8 @@ shlex = "0.1.1"
 tokio-process = "0.2.3"
 tokio-timer = "0.2.6"
 lazy_static = "1.4.0"
+tempfile = "^3.1.0"
+zeroize = "1.0.0"
 
 [dev-dependencies]
 quickcheck = "0.6"
diff --git a/rusoto/credential/src/lib.rs b/rusoto/credential/src/lib.rs
index 87c4af092a1..19a164fe2a1 100644
--- a/rusoto/credential/src/lib.rs
+++ b/rusoto/credential/src/lib.rs
@@ -23,25 +23,32 @@ extern crate tokio_timer;
 pub use crate::container::{ContainerProvider, ContainerProviderFuture};
 pub use crate::environment::{EnvironmentProvider, EnvironmentProviderFuture};
 pub use crate::instance_metadata::{InstanceMetadataProvider, InstanceMetadataProviderFuture};
+pub use crate::object_safe::{AwsCredentialProviderChain, AwsCredentialsProvider, WithFallback};
 pub use crate::profile::{ProfileProvider, ProfileProviderFuture};
+pub use crate::secrets::Secret;
 pub use crate::static_provider::StaticProvider;
+pub use crate::variable::Variable;
 
 pub mod claims;
 mod container;
 mod environment;
 mod instance_metadata;
+mod object_safe;
 mod profile;
 mod request;
+mod secrets;
 mod static_provider;
 pub(crate) mod test_utils;
+mod variable;
 
 use std::collections::BTreeMap;
-use std::env::var as env_var;
+use std::env::{var as env_var, VarError};
 use std::error::Error;
 use std::fmt;
 use std::io::Error as IoError;
 use std::ops::Deref;
 use std::rc::Rc;
+use std::string::FromUtf8Error;
 use std::sync::{Arc, Mutex};
 use std::time::Duration;
 
@@ -182,7 +189,7 @@ impl fmt::Debug for AwsCredentials {
 ///
 /// This generally is an error message from one of our underlying libraries, however
 /// we wrap it up with this type so we can export one single error type.
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
 pub struct CredentialsError {
     /// The underlying error message for the credentials error.
     pub message: String,
@@ -238,6 +245,18 @@ impl From<serde_json::Error> for CredentialsError {
     }
 }
 
+impl From<VarError> for CredentialsError {
+    fn from(err: VarError) -> CredentialsError {
+        CredentialsError::new(err.description())
+    }
+}
+
+impl From<FromUtf8Error> for CredentialsError {
+    fn from(err: FromUtf8Error) -> CredentialsError {
+        CredentialsError::new(err.description())
+    }
+}
+
 /// A trait for types that produce `AwsCredentials`.
 pub trait ProvideAwsCredentials {
     /// The future response value.
@@ -247,6 +266,13 @@ pub trait ProvideAwsCredentials {
     fn credentials(&self) -> Self::Future;
 }
 
+impl<P: ProvideAwsCredentials> ProvideAwsCredentials for Box<P> {
+    type Future = P::Future;
+    fn credentials(&self) -> Self::Future {
+        P::credentials(&*self)
+    }
+}
+
 impl<P: ProvideAwsCredentials> ProvideAwsCredentials for Rc<P> {
     type Future = P::Future;
     fn credentials(&self) -> Self::Future {
@@ -275,12 +301,18 @@ pub struct AutoRefreshingProvider<P: ProvideAwsCredentials + 'static> {
 
 impl<P: ProvideAwsCredentials + 'static> AutoRefreshingProvider<P> {
     /// Create a new `AutoRefreshingProvider` around the provided base provider.
+    #[deprecated(note = "Please use the from function instead")]
     pub fn new(provider: P) -> Result<AutoRefreshingProvider<P>, CredentialsError> {
+        Ok(Self::from(provider))
+    }
+
+    /// Create a new `AutoRefreshingProvider` around the provided base provider.
+    pub fn from(provider: P) -> Self {
         let future = provider.credentials();
-        Ok(AutoRefreshingProvider {
+        AutoRefreshingProvider {
             credentials_provider: provider,
             shared_future: Mutex::new(future.shared()),
-        })
+        }
     }
 
     /// Get a shared reference to the wrapped provider.
@@ -297,6 +329,12 @@ impl<P: ProvideAwsCredentials + 'static> AutoRefreshingProvider<P> {
     }
 }
 
+impl<P: Clone + ProvideAwsCredentials + 'static> Clone for AutoRefreshingProvider<P> {
+    fn clone(&self) -> Self {
+        Self::from(self.credentials_provider.clone())
+    }
+}
+
 enum AutoRefreshingFutureInner<P: ProvideAwsCredentials + 'static> {
     Cached(SharedItem<AwsCredentials>),
     NotCached(Shared<P::Future>),
@@ -375,6 +413,10 @@ impl<P: ProvideAwsCredentials + 'static> ProvideAwsCredentials for AutoRefreshin
 
 /// Wraps a `ChainProvider` in an `AutoRefreshingProvider`.
 ///
+/// **Note**: Consider using `rusoto_sts::DefaultCredentialsProvider` instead as it supports
+/// additional credentials sources that depend on STS and uses a security first approach by
+/// disabling [`credential_process`][credential_process] by default.
+///
 /// The underlying `ChainProvider` checks multiple sources for credentials, and the `AutoRefreshingProvider`
 /// refreshes the credentials automatically when they expire.
 ///
@@ -392,7 +434,7 @@ pub struct DefaultCredentialsProvider(AutoRefreshingProvider<ChainProvider>);
 impl DefaultCredentialsProvider {
     /// Creates a new thread-safe `DefaultCredentialsProvider`.
     pub fn new() -> Result<DefaultCredentialsProvider, CredentialsError> {
-        let inner = AutoRefreshingProvider::new(ChainProvider::new())?;
+        let inner = AutoRefreshingProvider::from(ChainProvider::new());
         Ok(DefaultCredentialsProvider(inner))
     }
 }
@@ -420,6 +462,9 @@ impl Future for DefaultCredentialsProviderFuture {
 
 /// Provides AWS credentials from multiple possible sources using a priority order.
 ///
+/// **Note**: Consider using `rusoto_sts::DefaultCredentialsProvider` instead as it supports
+/// additional credentials sources and uses a security first approach.
+///
 /// The following sources are checked in order for credentials when calling `credentials`:
 ///
 /// 1. Environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`
@@ -438,7 +483,6 @@ impl Future for DefaultCredentialsProviderFuture {
 /// extern crate rusoto_credential;
 ///
 /// use std::time::Duration;
-///
 /// use rusoto_credential::ChainProvider;
 ///
 /// fn main() {
@@ -574,6 +618,7 @@ extern crate quickcheck;
 
 #[cfg(test)]
 mod tests {
+
     use std::fs::{self, File};
     use std::io::Read;
     use std::path::Path;
diff --git a/rusoto/credential/src/object_safe.rs b/rusoto/credential/src/object_safe.rs
new file mode 100644
index 00000000000..93dae0b56da
--- /dev/null
+++ b/rusoto/credential/src/object_safe.rs
@@ -0,0 +1,297 @@
+use super::{AwsCredentials, CredentialsError, ProvideAwsCredentials};
+use futures::Future;
+use std::rc::Rc;
+use std::sync::Arc;
+
+/// Object safe trait pendant to `ProvideAwsCredentials` trait allowing better composition
+/// (abstraction) e.g. for chained providers.
+///
+/// # Examples
+///
+/// One can use the object safe representation to easily create credentials provider chains by
+/// combining multiple `AwsCredentialsProvider`s using the `or` method.
+///
+/// ```rust
+/// # use rusoto_credential::*;  
+/// # use std::rc::Rc;
+/// # use futures::future::Future;
+/// let provider = (Box::new(EnvironmentProvider::default()) as Box<dyn AwsCredentialsProvider>).or(Box::new(ContainerProvider::default()));
+/// # let _ = provider.credentials().wait();
+/// # let _ = provider.fetch_credentials().wait();
+/// ```
+pub trait AwsCredentialsProvider {
+    /// Produce a new `AwsCredentials` future.
+    fn fetch_credentials(&self)
+        -> Box<dyn Future<Item = AwsCredentials, Error = CredentialsError>>;
+}
+
+/// API to chain providers
+pub trait WithFallback {
+    /// Add fallback provider
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// # use rusoto_credential::*;  
+    /// # use std::rc::Rc;
+    /// # use futures::future::Future;
+    /// let env: Box<dyn AwsCredentialsProvider> = Box::new(EnvironmentProvider::default());
+    /// let env_or_container = env.or(Box::new(ContainerProvider::default()));
+    /// let env_or_container_or_ec2 = env_or_container.or(Box::new(InstanceMetadataProvider::new()));
+    /// # let _ = env_or_container_or_ec2.credentials().wait();
+    /// # let _ = env_or_container_or_ec2.fetch_credentials().wait();
+    /// ```
+    fn or(self, fallback: Self) -> Self;
+}
+
+impl WithFallback for Rc<dyn AwsCredentialsProvider> {
+    fn or(self, fallback: Self) -> Self {
+        Rc::new(AwsCredentialProviderChain::new(self, fallback))
+    }
+}
+
+impl WithFallback for Arc<dyn AwsCredentialsProvider> {
+    fn or(self, fallback: Self) -> Self {
+        Arc::new(AwsCredentialProviderChain::new(
+            Rc::new(self),
+            Rc::new(fallback),
+        ))
+    }
+}
+
+impl WithFallback for Box<dyn AwsCredentialsProvider> {
+    fn or(self, fallback: Self) -> Self {
+        Box::new(AwsCredentialProviderChain::new(
+            self.into(),
+            fallback.into(),
+        ))
+    }
+}
+
+impl<P, F> AwsCredentialsProvider for P
+where
+    P: ProvideAwsCredentials<Future = F> + 'static,
+    F: Future<Item = AwsCredentials, Error = CredentialsError> + 'static,
+{
+    fn fetch_credentials(
+        &self,
+    ) -> Box<dyn Future<Item = AwsCredentials, Error = CredentialsError>> {
+        Box::new(P::credentials(&self))
+    }
+}
+
+/// Provider chain
+#[derive(Clone)]
+pub struct AwsCredentialProviderChain {
+    primary: Rc<dyn AwsCredentialsProvider>,
+    fallback: Rc<dyn AwsCredentialsProvider>,
+}
+
+impl AwsCredentialProviderChain {
+    /// Create new provider chain
+    pub fn new(
+        primary: Rc<dyn AwsCredentialsProvider>,
+        fallback: Rc<dyn AwsCredentialsProvider>,
+    ) -> Self {
+        Self { primary, fallback }
+    }
+
+    /// Add provider to the end of the provider chain.
+    pub fn push<P, F>(self, fallback: P) -> Self
+    where
+        P: ProvideAwsCredentials<Future = F> + Clone + 'static,
+        F: Future<Item = AwsCredentials, Error = CredentialsError> + 'static,
+    {
+        Self {
+            primary: Rc::new(self),
+            fallback: Rc::new(fallback),
+        }
+    }
+}
+
+impl AwsCredentialsProvider for AwsCredentialProviderChain {
+    fn fetch_credentials(
+        &self,
+    ) -> Box<dyn Future<Item = AwsCredentials, Error = CredentialsError>> {
+        let fb = self.fallback.clone();
+        Box::new(
+            self.primary
+                .fetch_credentials()
+                .or_else(move |_| fb.fetch_credentials()),
+        )
+    }
+}
+
+/// Allow going from trait object to trait.
+impl ProvideAwsCredentials for Rc<dyn AwsCredentialsProvider> {
+    type Future = Box<dyn Future<Item = AwsCredentials, Error = CredentialsError>>;
+
+    fn credentials(&self) -> Self::Future {
+        // as_ref() is important as otherwise this would lead to a stack overflow, calling
+        self.as_ref().fetch_credentials()
+    }
+}
+
+impl ProvideAwsCredentials for Arc<dyn AwsCredentialsProvider> {
+    type Future = Box<dyn Future<Item = AwsCredentials, Error = CredentialsError>>;
+
+    fn credentials(&self) -> Self::Future {
+        // as_ref() is important as otherwise this would lead to a stack overflow, calling
+        self.as_ref().fetch_credentials()
+    }
+}
+
+impl ProvideAwsCredentials for Box<dyn AwsCredentialsProvider> {
+    type Future = Box<dyn Future<Item = AwsCredentials, Error = CredentialsError>>;
+
+    fn credentials(&self) -> Self::Future {
+        // as_ref() is important as otherwise this would lead to a stack overflow, calling
+        self.as_ref().fetch_credentials()
+    }
+}
+
+impl ProvideAwsCredentials for &dyn AwsCredentialsProvider {
+    type Future = Box<dyn Future<Item = AwsCredentials, Error = CredentialsError>>;
+
+    fn credentials(&self) -> Self::Future {
+        // as_ref() is important as otherwise this would lead to a stack overflow, calling
+        (*self).fetch_credentials()
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::super::*;
+    use crate::EnvironmentProvider;
+    use futures::future::Future;
+    use std::rc::Rc;
+    use std::sync::Arc;
+
+    fn by_obj(c: &dyn AwsCredentialsProvider) {
+        let _ = c.fetch_credentials().wait();
+    }
+
+    fn by_obj_trait_ref<P>(c: &P)
+    where
+        P: AwsCredentialsProvider + ?Sized,
+    {
+        let _ = c.fetch_credentials().wait();
+    }
+
+    fn by_obj_trait<P>(c: &P)
+    where
+        P: AwsCredentialsProvider,
+    {
+        let _ = c.fetch_credentials().wait();
+    }
+
+    fn by_static_trait_ref<P, F>(c: &P)
+    where
+        P: ProvideAwsCredentials<Future = F>,
+        F: Future<Item = AwsCredentials, Error = CredentialsError> + 'static,
+    {
+        let _ = c.credentials().wait();
+    }
+
+    fn by_static_trait<P, F>(c: P)
+    where
+        P: ProvideAwsCredentials<Future = F>,
+        F: Future<Item = AwsCredentials, Error = CredentialsError> + 'static,
+    {
+        let _ = c.credentials().wait();
+    }
+
+    #[test]
+    fn can_use_object_ref() {
+        let provider: Box<dyn AwsCredentialsProvider> = Box::new(EnvironmentProvider::default());
+        let obj_ref: &dyn AwsCredentialsProvider = &provider;
+        by_obj(obj_ref);
+        by_obj_trait_ref(obj_ref);
+        by_static_trait_ref(&obj_ref);
+        by_static_trait(obj_ref);
+    }
+
+    #[test]
+    fn can_use_boxed() {
+        let provider: Box<dyn AwsCredentialsProvider> = Box::new(EnvironmentProvider::default());
+        by_obj(&provider);
+        by_obj_trait_ref(&provider);
+        by_obj_trait(&provider);
+        by_static_trait_ref(&provider);
+        by_static_trait(provider);
+
+        let provider: Box<EnvironmentProvider> = Box::new(EnvironmentProvider::default());
+        by_obj(&provider);
+        by_obj_trait_ref(&provider);
+        by_obj_trait(&provider);
+        by_static_trait_ref(&provider);
+        by_static_trait(provider);
+    }
+
+    #[test]
+    fn can_use_rc() {
+        let provider: Rc<dyn AwsCredentialsProvider> = Rc::new(EnvironmentProvider::default());
+        by_obj(&provider);
+        by_obj_trait_ref(&provider);
+        by_obj_trait(&provider);
+        by_static_trait_ref(&provider);
+        by_static_trait(provider);
+
+        let provider: Rc<EnvironmentProvider> = Rc::new(EnvironmentProvider::default());
+        by_obj(&provider);
+        by_obj_trait_ref(&provider);
+        by_obj_trait(&provider);
+        by_static_trait_ref(&provider);
+        by_static_trait(provider);
+    }
+
+    #[test]
+    fn can_use_arc() {
+        let provider: Arc<dyn AwsCredentialsProvider> = Arc::new(EnvironmentProvider::default());
+        by_obj(&provider);
+        by_obj_trait_ref(&provider);
+        by_obj_trait(&provider);
+        by_static_trait_ref(&provider);
+        by_static_trait(provider);
+
+        let provider: Arc<EnvironmentProvider> = Arc::new(EnvironmentProvider::default());
+        by_obj(&provider);
+        by_obj_trait_ref(&provider);
+        by_obj_trait(&provider);
+        by_static_trait_ref(&provider);
+        by_static_trait(provider);
+    }
+
+    #[test]
+    fn can_chain_box() {
+        let provider: Box<dyn AwsCredentialsProvider> = Box::new(EnvironmentProvider::default());
+        let provider = provider.or(Box::new(ContainerProvider::new()));
+        by_obj(&provider);
+        by_obj_trait_ref(&provider);
+        by_obj_trait(&provider);
+        by_static_trait_ref(&provider);
+        by_static_trait(provider);
+    }
+
+    #[test]
+    fn can_chain_rc() {
+        let provider: Rc<dyn AwsCredentialsProvider> = Rc::new(EnvironmentProvider::default());
+        let provider = provider.or(Rc::new(ContainerProvider::new()));
+        by_obj(&provider);
+        by_obj_trait_ref(&provider);
+        by_obj_trait(&provider);
+        by_static_trait_ref(&provider);
+        by_static_trait(provider);
+    }
+
+    #[test]
+    fn can_chain_arc() {
+        let provider: Arc<dyn AwsCredentialsProvider> = Arc::new(EnvironmentProvider::default());
+        let provider = provider.or(Arc::new(ContainerProvider::new()));
+        by_obj(&provider);
+        by_obj_trait_ref(&provider);
+        by_obj_trait(&provider);
+        by_static_trait_ref(&provider);
+        by_static_trait(provider);
+    }
+}
diff --git a/rusoto/credential/src/profile.rs b/rusoto/credential/src/profile.rs
index 0a2f2726aef..b8c040e7180 100644
--- a/rusoto/credential/src/profile.rs
+++ b/rusoto/credential/src/profile.rs
@@ -43,6 +43,11 @@ pub struct ProfileProvider {
     file_path: PathBuf,
     /// The Profile Path to parse out of the Credentials File.
     profile: String,
+    /// If `true` disable [`credential_process`][credential_process] option, making sure not to
+    /// call any external process.
+    ///
+    /// [credential_process]: https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes
+    secure: bool,
 }
 
 impl ProfileProvider {
@@ -64,6 +69,7 @@ impl ProfileProvider {
         ProfileProvider {
             file_path: file_path.into(),
             profile: profile.into(),
+            secure: false,
         }
     }
 
@@ -171,6 +177,17 @@ impl ProfileProvider {
     {
         self.profile = profile.into();
     }
+
+    /// Returns `true` if this provider instance will not execute any external processes to obtain
+    /// credentials.
+    pub fn is_secure(&self) -> bool {
+        self.secure
+    }
+
+    /// Ensure this provider instance does not call any external processes to obtain credentials.
+    pub fn secure(&mut self) {
+        self.secure = true;
+    }
 }
 
 /// Provides AWS credentials from a profile in a credentials file as a Future.
@@ -217,14 +234,14 @@ impl ProvideAwsCredentials for ProfileProvider {
                     .map(std::borrow::ToOwned::to_owned)
             })
         }) {
-            Ok(Some(command)) => {
+            Ok(Some(ref command)) if !self.is_secure() => {
                 // credential_process is set, create the future
                 match parse_command_str(&command) {
                     Ok(mut command) => ProfileProviderFutureInner::Future(command.output_async()),
                     Err(err) => ProfileProviderFutureInner::Result(result(Err(err))),
                 }
             }
-            Ok(None) => {
+            Ok(Some(_)) | Ok(None) => {
                 // credential_process is not set, parse the credentials file
                 ProfileProviderFutureInner::Result(result(
                     parse_credentials_file(self.file_path()).and_then(|mut profiles| {
diff --git a/rusoto/credential/src/secrets.rs b/rusoto/credential/src/secrets.rs
new file mode 100644
index 00000000000..2aa0bb39d9c
--- /dev/null
+++ b/rusoto/credential/src/secrets.rs
@@ -0,0 +1,54 @@
+use zeroize::Zeroize;
+
+/// Newtype (pattern) to protect secret credentials stored as Strings.
+#[derive(Clone)]
+pub struct Secret(String);
+
+impl From<String> for Secret {
+    fn from(s: String) -> Self {
+        Secret(s)
+    }
+}
+
+/// Allow dereferencing Secrets as &str.
+///
+/// ```rust
+/// # use rusoto_credential::Secret;
+/// assert_eq!(Secret::from("hello".to_string()).as_ref(), "hello");
+/// ```
+impl AsRef<str> for Secret {
+    fn as_ref(&self) -> &str {
+        return self.0.as_str();
+    }
+}
+
+impl Drop for Secret {
+    fn drop(&mut self) {
+        let s = &mut self.0;
+        s.zeroize();
+    }
+}
+
+/// Secrets must not leak, so make sure they are not printed.
+///
+/// ```rust
+/// # use rusoto_credential::Secret;
+/// assert_eq!(format!("{:?}",Secret::from("hello".to_string())), "\"*******\"");
+/// ```
+impl std::fmt::Debug for Secret {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "\"*******\"")
+    }
+}
+
+/// Secrets must not leak, so make sure they are not displayed.
+///
+/// ```rust
+/// # use rusoto_credential::Secret;
+/// assert_eq!(format!("{}",Secret::from("hello".to_string())), "*******");
+/// ```
+impl std::fmt::Display for Secret {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "*******")
+    }
+}
diff --git a/rusoto/credential/src/static_provider.rs b/rusoto/credential/src/static_provider.rs
index fd88a704548..c2be373ac4f 100644
--- a/rusoto/credential/src/static_provider.rs
+++ b/rusoto/credential/src/static_provider.rs
@@ -79,7 +79,10 @@ impl ProvideAwsCredentials for StaticProvider {
 
 impl From<AwsCredentials> for StaticProvider {
     fn from(credentials: AwsCredentials) -> Self {
-        StaticProvider{ credentials, valid_for: None }
+        StaticProvider {
+            credentials,
+            valid_for: None,
+        }
     }
 }
 
diff --git a/rusoto/credential/src/variable.rs b/rusoto/credential/src/variable.rs
new file mode 100644
index 00000000000..17a32fdf0df
--- /dev/null
+++ b/rusoto/credential/src/variable.rs
@@ -0,0 +1,277 @@
+use std::convert::From;
+use std::env::{var, VarError};
+use std::fmt;
+use std::sync::Arc;
+
+/// Variable is an abstraction over parameters to credential providers, allowing to abstract on
+/// how (source) and when (time) parameter values are resolved. A lot of credentials providers
+/// use external information sources such as environment variables or files to obtain parameter
+/// values needed to produce AWS credentials.
+///
+/// # Information Sources
+///
+/// - In memory values (static)
+/// - Environment variables (dynamic)
+/// - Files (dynamic)
+/// - ...
+///
+/// # Resolving Behaviour
+///
+/// - Static variables always resolve to the same value.
+/// - Dynamic variables can resolve to different values over time.
+///
+/// Most prominent examples for dynamic variables are parameters which read their value from
+/// environment variables or files.
+pub enum Variable<T, E = super::CredentialsError> {
+    /// Static variable always resolving to the same given value.
+    Static(T),
+    /// Dynamic variable can resolve to different values over time.
+    Dynamic(Arc<dyn Fn() -> Result<T, E> + Send + Sync>),
+    /// Fallback try variables in given order returning the value of the first variable that
+    /// does resolve.
+    Fallback(Box<Variable<T, E>>, Box<Variable<T, E>>),
+}
+
+impl<T, E> From<T> for Variable<T, E>
+where
+    T: Clone,
+{
+    fn from(value: T) -> Self {
+        Variable::with_value(value)
+    }
+}
+
+impl<E> From<&str> for Variable<String, E> {
+    fn from(value: &str) -> Self {
+        Variable::with_value(value.to_owned())
+    }
+}
+
+/*
+impl<T, E, V> From<&V> for Variable<T, E>
+where
+    T: Clone + std::borrow::Borrow<V>,
+    V: ToOwned<Owned = T> + ?Sized,
+{
+    fn from(value: &V) -> Self {
+        Variable::with_value(value.to_owned())
+    }
+}*/
+
+impl<T: fmt::Debug, E> fmt::Debug for Variable<T, E> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::Static(t) => write!(f, "Variable::Static({:?})", t),
+            Self::Dynamic(_) => write!(f, "Variable::Dynamic(...)"),
+            Self::Fallback(a, b) => write!(f, "Variable::Fallback({:?}, {:?})", a, b),
+        }
+    }
+}
+
+/// Custom Clone implementation as type parameter E doesn't have to be cloneable.
+impl<T: Clone, E> Clone for Variable<T, E> {
+    fn clone(&self) -> Self {
+        match self {
+            Self::Static(t) => Self::Static(t.clone()),
+            Self::Dynamic(f) => Self::Dynamic(f.clone()),
+            Self::Fallback(a, b) => Self::Fallback(a.clone(), b.clone()),
+        }
+    }
+}
+
+impl<T: Clone, E> Variable<T, E> {
+    /// Variable which statically resolves to a provided (in-memory) value.
+    pub fn with_value<V: Into<T>>(value: V) -> Self {
+        Self::Static(value.into())
+    }
+
+    /// Resolve the variable's value.
+    pub fn resolve(&self) -> Result<T, E> {
+        match self {
+            Self::Static(t) => Ok(t.clone()),
+            Self::Dynamic(f) => f(),
+            Self::Fallback(a, b) => a.resolve().or_else(|_| b.resolve()),
+        }
+    }
+
+    /// Combine this Variable with a fallback Variable. Resolving the variable's value will be
+    /// done lazily, stopping on the first Variable that successfuly resolves.
+    ///
+    /// # Example Usage
+    ///
+    /// ```rust
+    /// # use rusoto_credential::Variable;
+    /// let primary: Variable<String> = Variable::from_env_var("AWS_SECRET_ACCESS_KEY");
+    /// let fallback = Variable::from_env_var("AWS_SECRET_KEY");
+    /// let aws_secret_access_key = primary.or(fallback);
+    /// ```
+    pub fn or(self, other: Variable<T, E>) -> Self {
+        Self::Fallback(Box::new(self), Box::new(other))
+    }
+}
+
+impl<T: 'static, E: 'static> Variable<T, E> {
+    /// Variable which dynamically resolves to the value returned from the provided closure. Use
+    /// this constructor function to create dynamically resolving Variables with custom logic.
+    pub fn dynamic(f: impl Fn() -> Result<T, E> + Send + Sync + 'static) -> Self {
+        Self::Dynamic(Arc::new(f))
+    }
+}
+
+impl<T, E> Variable<T, E>
+where
+    T: From<String> + 'static,
+    E: From<VarError> + 'static,
+{
+    /// Variable which dynamically resolves to the value of a given environment variable.
+    pub fn from_env_var<K: AsRef<std::ffi::OsStr>>(key: K) -> Self {
+        let tmp = key.as_ref().to_os_string();
+        Self::dynamic(move || match var(&tmp) {
+            Ok(ref v) if !v.trim().is_empty() => Ok(T::from(v.trim().to_string())),
+            Ok(_) => Err(E::from(VarError::NotPresent)),
+            Err(e) => Err(E::from(e)),
+        })
+    }
+}
+
+impl<T, E> Variable<T, E>
+where
+    T: From<String> + 'static,
+    E: From<std::io::Error> + From<std::string::FromUtf8Error> + 'static,
+{
+    /// Variable which dynamically resolves to the value of an UTF-8 encoded text file
+    /// (removing all leading and trailing whitespaces.
+    pub fn from_text_file<K: AsRef<std::path::Path>>(file: K) -> Self {
+        use std::fs::read;
+        let tmp = file.as_ref().to_path_buf();
+        Self::dynamic(move || {
+            Ok(T::from(
+                String::from_utf8(read(&tmp)?)?.as_str().trim().to_string(),
+            ))
+        })
+    }
+}
+
+impl<T, E> Variable<T, E>
+where
+    T: From<Vec<u8>> + 'static,
+    E: From<std::io::Error> + 'static,
+{
+    /// Variable which dynamically resolves to the value of a binary file.
+    pub fn from_binary_file<K: AsRef<std::path::Path>>(file: K) -> Self {
+        use std::fs::read;
+        let tmp = file.as_ref().to_path_buf();
+        Self::dynamic(move || Ok(T::from(read(&tmp)?)))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::super::CredentialsError;
+    use super::*;
+    use crate::test_utils::lock_env;
+    use std::io::Write;
+    use tempfile::NamedTempFile;
+
+    #[test]
+    fn api_ergonomics() {
+        let _tmp: Variable<String> = "".to_string().into();
+        let _tmp: Variable<String> = "".into();
+        let _tmp: Variable<u32> = 1.into();
+    }
+
+    #[test]
+    fn is_send() {
+        fn _send<T: Send>(_t: T) {}
+        let var = Variable::<i32, ()>::with_value(1);
+        _send(var);
+    }
+
+    #[test]
+    fn is_sync() {
+        fn _sync<T: Sync>(_t: T) {}
+        let var = Variable::<i32, ()>::with_value(1);
+        _sync(var);
+    }
+
+    #[test]
+    fn from_value() {
+        let var = Variable::<i32, ()>::with_value(1);
+        assert_eq!(var.resolve(), Ok(1));
+        assert_eq!(var.resolve(), Ok(1));
+    }
+
+    #[test]
+    fn dynamic() {
+        fn xx() -> Result<i32, ()> {
+            Ok(1)
+        }
+        let var = Variable::<i32, ()>::dynamic(|| Ok(1));
+        assert_eq!(var.resolve(), Ok(1));
+        assert_eq!(var.resolve(), Ok(1));
+        let var = Variable::<i32, ()>::dynamic(xx);
+        assert_eq!(var.resolve(), Ok(1));
+        assert_eq!(var.resolve(), Ok(1));
+    }
+
+    #[test]
+    fn from_env_var() {
+        let _guard = lock_env();
+        const VALUE: &str = "E6591691_C658_4C63_A7CF_C822D8FFC15B";
+        std::env::set_var(VALUE, VALUE);
+        let var = Variable::<String, CredentialsError>::from_env_var(VALUE);
+        assert_eq!(var.resolve(), Ok(VALUE.to_string()));
+        assert_eq!(var.resolve(), Ok(VALUE.to_string()));
+        std::env::remove_var(VALUE);
+        assert_eq!(var.resolve().is_ok(), false);
+    }
+
+    #[test]
+    fn from_empty_env_var() {
+        let _guard = lock_env();
+        const VALUE: &str = "90E839DA_2254_4416_8295_AB82BD44D822";
+        std::env::set_var(VALUE, "");
+        let var = Variable::<String>::from_env_var(VALUE);
+        assert_eq!(var.resolve().is_ok(), false);
+        std::env::remove_var(VALUE);
+    }
+
+    #[test]
+    fn from_text_file() -> Result<(), CredentialsError> {
+        const VALUE: &str = "value";
+        let mut file = NamedTempFile::new()?;
+        writeln!(file, "{}", VALUE)?;
+        let var = Variable::<String>::from_text_file(file.path());
+        assert_eq!(var.resolve(), Ok(VALUE.to_string()));
+        assert_eq!(var.resolve(), Ok(VALUE.to_string()));
+        Ok(())
+    }
+
+    #[test]
+    fn from_binary_file() -> Result<(), CredentialsError> {
+        const VALUE: &[u8] = b"value";
+        let mut file = NamedTempFile::new()?;
+        file.write(VALUE)?;
+        let var = Variable::<Vec<u8>>::from_binary_file(file.path());
+        assert_eq!(var.resolve().as_ref().map(|v| v.as_slice()), Ok(VALUE));
+        assert_eq!(var.resolve().as_ref().map(|v| v.as_slice()), Ok(VALUE));
+        Ok(())
+    }
+
+    #[test]
+    fn or() {
+        let a = Variable::<i32, ()>::with_value(1);
+        let b = Variable::<i32, ()>::with_value(2);
+        assert_eq!(a.or(b).resolve(), Ok(1));
+        let a = Variable::<i32, VarError>::dynamic(|| Err(VarError::NotPresent));
+        let b = Variable::<i32, VarError>::with_value(2);
+        assert_eq!(a.or(b).resolve(), Ok(2));
+        let a = Variable::<i32, VarError>::dynamic(|| Err(VarError::NotPresent));
+        let b = Variable::<i32, VarError>::dynamic(|| Err(VarError::NotPresent));
+        let c = Variable::<i32, VarError>::with_value(3);
+        assert_eq!(a.or(b).or(c).resolve(), Ok(3));
+        let a = Variable::<i32, VarError>::dynamic(|| Err(VarError::NotPresent));
+        let b = Variable::<i32, VarError>::dynamic(|| Err(VarError::NotPresent));
+        assert_eq!(a.or(b).resolve(), Err(VarError::NotPresent));
+    }
+}
diff --git a/rusoto/services/sts/Cargo.toml b/rusoto/services/sts/Cargo.toml
index 531a08d2fc3..6163e1f4014 100644
--- a/rusoto/services/sts/Cargo.toml
+++ b/rusoto/services/sts/Cargo.toml
@@ -19,6 +19,7 @@ bytes = "0.4.12"
 chrono = "0.4.0"
 futures = "0.1.16"
 serde_urlencoded = "0.6"
+tempfile = "^3.1.0"
 xml-rs = "0.8"
 
 [dependencies.rusoto_core]
diff --git a/rusoto/services/sts/src/custom/default_provider.rs b/rusoto/services/sts/src/custom/default_provider.rs
new file mode 100644
index 00000000000..bcb041a381f
--- /dev/null
+++ b/rusoto/services/sts/src/custom/default_provider.rs
@@ -0,0 +1,134 @@
+use crate::WebIdentityProvider;
+use futures::{Future, Poll};
+use rusoto_core::credential::{
+    AutoRefreshingProvider, AutoRefreshingProviderFuture, AwsCredentialProviderChain,
+    AwsCredentials, AwsCredentialsProvider, ContainerProvider, CredentialsError,
+    EnvironmentProvider, InstanceMetadataProvider, ProfileProvider, ProvideAwsCredentials,
+};
+use std::rc::Rc;
+use std::time::Duration;
+
+/// Default credential provider.
+///
+/// Checks multiple sources for credentials, and uses `AutoRefreshingProvider` to refreshes the
+/// credentials automatically when they expire. Currently the following providers are probed in
+/// order, taking the credentials from the first provider that returns without an error:
+///
+/// - EnvironmentProvider
+/// - WebIdentityProvider (Kubernets)
+/// - ProfileProvider (optional)
+/// - ContainerProvider (ECS/Fargate)
+/// - InstanceMetadataProvider
+///
+/// [credential_process]: https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes
+#[derive(Clone)]
+pub struct DefaultCredentialsProvider(AutoRefreshingProvider<Rc<dyn AwsCredentialsProvider>>);
+
+impl DefaultCredentialsProvider {
+    pub fn new(profile_provider: Option<ProfileProvider>, timeout: Option<Duration>) -> Self {
+        let provider = AwsCredentialProviderChain::new(
+            Rc::new(EnvironmentProvider::default()),
+            Rc::new(WebIdentityProvider::from_k8s_env()),
+        );
+        let provider = match profile_provider {
+            Some(p) => provider.push(p),
+            _ => provider,
+        };
+        let mut container_provider = ContainerProvider::new();
+        let mut instance_provider = InstanceMetadataProvider::new();
+        timeout.map(|t| {
+            container_provider.set_timeout(t.clone());
+            instance_provider.set_timeout(t);
+        });
+        let provider = provider.push(container_provider).push(instance_provider);
+        Self(AutoRefreshingProvider::from(Rc::new(provider)))
+    }
+
+    /// Creates a potentially insecure default credential provider chain.
+    ///
+    /// Checks multiple sources for credentials, and uses `AutoRefreshingProvider` to refreshes the
+    /// credentials automatically when they expire. Currently the following providers are probed in
+    /// order, taking the credentials from the first provider that returns without an error:
+    ///
+    /// - EnvironmentProvider
+    /// - WebIdentityProvider (Kubernets)
+    /// - ProfileProvider (optional)
+    /// - ContainerProvider (ECS/Fargate)
+    /// - InstanceMetadataProvider
+    ///
+    /// # Warning
+    ///
+    /// This provider allows the [`credential_process`][credential_process] option, a method of
+    /// sourcing credentials from an external process. This can potentially be dangerous, so proceed
+    /// with caution. Other credential providers should be preferred if at all possible. If using this
+    /// option, you should make sure that the config file is as locked down as possible using security
+    /// best practices for your operating system.
+    pub fn insecure() -> Self {
+        Self::new(ProfileProvider::new().ok(), None)
+    }
+
+    /// Create secure default credential provider chain.
+    ///
+    /// Checks multiple sources for credentials, and uses `AutoRefreshingProvider` to refreshes the
+    /// credentials automatically when they expire. Currently the following providers are probed in
+    /// order, taking the credentials from the first provider that returns without an error:
+    ///
+    /// - EnvironmentProvider
+    /// - WebIdentityProvider (Kubernets)
+    /// - ProfileProvider (optional)
+    /// - ContainerProvider (ECS/Fargate)
+    /// - InstanceMetadataProvider
+    ///
+    /// This provider **does not** allow the [`credential_process`][credential_process] option
+    /// in the AWS config file (`~/.aws/config`) and therefore the returned provider can be
+    /// considered secure.
+    pub fn secure() -> Self {
+        let mut profile = ProfileProvider::new().ok();
+        if let Some(p) = &mut profile {
+            p.secure();
+        }
+        Self::new(profile, None)
+    }
+}
+
+impl Default for DefaultCredentialsProvider {
+    /// Create secure default credential provider chain.
+    fn default() -> Self {
+        Self::secure()
+    }
+}
+
+impl ProvideAwsCredentials for DefaultCredentialsProvider {
+    type Future = DefaultCredentialsProviderFuture;
+
+    fn credentials(&self) -> Self::Future {
+        let inner = self.0.credentials();
+        DefaultCredentialsProviderFuture(inner)
+    }
+}
+
+/// Future returned from `DefaultCredentialsProvider`.
+pub struct DefaultCredentialsProviderFuture(
+    AutoRefreshingProviderFuture<Rc<dyn AwsCredentialsProvider>>,
+);
+
+impl Future for DefaultCredentialsProviderFuture {
+    type Item = AwsCredentials;
+    type Error = CredentialsError;
+
+    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+        self.0.poll()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn api() {
+        let provider = DefaultCredentialsProvider::default();
+        let future = provider.credentials();
+        let _result = future.wait();
+    }
+}
diff --git a/rusoto/services/sts/src/custom/mod.rs b/rusoto/services/sts/src/custom/mod.rs
index 508dcf309de..f9e091866d1 100644
--- a/rusoto/services/sts/src/custom/mod.rs
+++ b/rusoto/services/sts/src/custom/mod.rs
@@ -4,3 +4,8 @@ pub use self::credential::{
     NewAwsCredsForStsCreds, StsAssumeRoleSessionCredentialsProvider, StsSessionCredentialsProvider,
     StsWebIdentityFederationSessionCredentialsProvider,
 };
+
+mod default_provider;
+mod web_identity;
+pub use self::default_provider::*;
+pub use self::web_identity::*;
diff --git a/rusoto/services/sts/src/custom/web_identity.rs b/rusoto/services/sts/src/custom/web_identity.rs
new file mode 100644
index 00000000000..12a84643e59
--- /dev/null
+++ b/rusoto/services/sts/src/custom/web_identity.rs
@@ -0,0 +1,196 @@
+use crate::{
+    AssumeRoleWithWebIdentityError, AssumeRoleWithWebIdentityRequest,
+    AssumeRoleWithWebIdentityResponse, Sts, StsClient,
+};
+use futures::{Async, Future, Poll};
+use rusoto_core::credential::{
+    AwsCredentials, CredentialsError, ProvideAwsCredentials, Secret, Variable,
+};
+use rusoto_core::request::HttpClient;
+use rusoto_core::{Client, Region, RusotoFuture};
+use std::error::Error;
+
+const AWS_WEB_IDENTITY_TOKEN_FILE: &str = "AWS_WEB_IDENTITY_TOKEN_FILE";
+
+const AWS_ROLE_ARN: &str = "AWS_ROLE_ARN";
+
+const AWS_ROLE_SESSION_NAME: &str = "AWS_ROLE_SESSION_NAME";
+
+/// WebIdentityProvider using OpenID Connect bearer token to retrieve AWS IAM credentials.
+///
+/// See https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html for
+/// more details.
+#[derive(Debug, Clone)]
+pub struct WebIdentityProvider {
+    /// The OAuth 2.0 access token or OpenID Connect ID token that is provided by the identity provider.
+    /// Your application must get this token by authenticating the user who is using your application
+    /// with a web identity provider before the application makes an AssumeRoleWithWebIdentity call.
+    pub web_identity_token: Variable<Secret, CredentialsError>,
+    /// The Amazon Resource Name (ARN) of the role that the caller is assuming.
+    pub role_arn: Variable<String, CredentialsError>,
+    /// An identifier for the assumed role session. Typically, you pass the name or identifier that is
+    /// associated with the user who is using your application. That way, the temporary security credentials
+    /// that your application will use are associated with that user. This session name is included as part
+    /// of the ARN and assumed role ID in the AssumedRoleUser response element.
+    pub role_session_name: Variable<String, CredentialsError>,
+}
+
+impl WebIdentityProvider {
+    /// Create new WebIdentityProvider by explicitly passing its configuration.
+    pub fn new<A, B, C>(web_identity_token: A, role_arn: B, role_session_name: Option<C>) -> Self
+    where
+        A: Into<Variable<Secret, CredentialsError>>,
+        B: Into<Variable<String, CredentialsError>>,
+        C: Into<Variable<String, CredentialsError>>,
+    {
+        Self {
+            web_identity_token: web_identity_token.into(),
+            role_arn: role_arn.into(),
+            role_session_name: role_session_name
+                .map(|v| v.into())
+                .unwrap_or_else(|| Variable::with_value(Self::create_session_name())),
+        }
+    }
+
+    /// Creat a WebIdentityProvider from the following environment variables:
+    ///
+    /// - `AWS_WEB_IDENTITY_TOKEN_FILE` path to the web identity token file.
+    /// - `AWS_ROLE_ARN` ARN of the role to assume.
+    /// - `AWS_ROLE_SESSION_NAME` (optional) name applied to the assume-role session.
+    ///
+    /// See https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-technical-overview.html
+    /// for more information about how IAM Roles for Kubernetes Service Accounts works.
+    pub fn from_k8s_env() -> Self {
+        Self::_from_k8s_env(
+            Variable::from_env_var(AWS_WEB_IDENTITY_TOKEN_FILE),
+            Variable::from_env_var(AWS_ROLE_ARN),
+            Some(Variable::from_env_var(AWS_ROLE_SESSION_NAME)),
+        )
+    }
+
+    /// Used by unit testing
+    pub(crate) fn _from_k8s_env(
+        token_file: Variable<String, CredentialsError>,
+        role: Variable<String, CredentialsError>,
+        session_name: Option<Variable<String, CredentialsError>>,
+    ) -> Self {
+        Self::new(
+            Variable::dynamic(move || Variable::from_text_file(token_file.resolve()?).resolve()),
+            role,
+            session_name,
+        )
+    }
+
+    pub(crate) fn load_token(&self) -> Result<Secret, CredentialsError> {
+        self.web_identity_token.resolve()
+    }
+
+    fn create_session_name() -> String {
+        // TODO can we do better here?
+        // - Pod service account, Pod name and Pod namespace
+        // - EC2 Instance ID if available
+        // - IP address if available
+        // - ...
+        // Having some information in the session name that identifies the client would enable
+        // better correlation analysis in CloudTrail.
+        "WebIdentitySession".to_string()
+    }
+}
+
+impl ProvideAwsCredentials for WebIdentityProvider {
+    type Future = WebIdentityProviderFuture;
+
+    fn credentials(&self) -> Self::Future {
+        WebIdentityProviderFuture {
+            state: WebIdentityProviderFutureState::LoadBearerToken(
+                self.load_token(),
+                self.role_arn.resolve(),
+                self.role_session_name.resolve(),
+            ),
+        }
+    }
+}
+
+enum WebIdentityProviderFutureState {
+    LoadBearerToken(
+        Result<Secret, CredentialsError>,
+        Result<String, CredentialsError>,
+        Result<String, CredentialsError>,
+    ),
+    ExchangeToken(RusotoFuture<AssumeRoleWithWebIdentityResponse, AssumeRoleWithWebIdentityError>),
+}
+
+/// Provides AWS credentials from environment variables as a Future.
+pub struct WebIdentityProviderFuture {
+    state: WebIdentityProviderFutureState,
+}
+
+impl Future for WebIdentityProviderFuture {
+    type Item = AwsCredentials;
+    type Error = CredentialsError;
+
+    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+        use crate::custom::credential::NewAwsCredsForStsCreds;
+        use WebIdentityProviderFutureState::*;
+        match &mut self.state {
+            LoadBearerToken(Err(e), _, _) => Err(e.clone()),
+            LoadBearerToken(_, Err(e), _) => Err(e.clone()),
+            LoadBearerToken(_, _, Err(e)) => Err(e.clone()),
+            LoadBearerToken(Ok(token), Ok(role), Ok(session)) => match HttpClient::new() {
+                Err(e) => Err(CredentialsError::new(e.description().to_string())),
+                Ok(c) => {
+                    let client = Client::new_not_signing(c);
+                    let sts = StsClient::new_with_client(client, Region::default());
+                    let mut req = AssumeRoleWithWebIdentityRequest::default();
+                    req.role_arn = role.clone();
+                    req.web_identity_token = token.as_ref().to_string();
+                    req.role_session_name = session.clone();
+                    self.state = ExchangeToken(sts.assume_role_with_web_identity(req));
+                    self.poll()
+                }
+            },
+            ExchangeToken(ref mut future) => match future.poll() {
+                Ok(Async::Ready(r)) => match r.credentials {
+                    Some(c) => AwsCredentials::new_for_credentials(c).map(|c| Async::Ready(c)),
+                    None => Err(CredentialsError::new(format!(
+                        "No credentials found in AssumeRoleWithWebIdentityResponse: {:?}",
+                        r
+                    ))),
+                },
+                Ok(Async::NotReady) => Ok(Async::NotReady),
+                Err(e) => Err(CredentialsError::new(e.to_string())),
+            },
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::io::Write;
+    use tempfile::NamedTempFile;
+
+    #[test]
+    fn api_ergonomy() {
+        WebIdentityProvider::new(Secret::from("".to_string()), "", Some("".to_string()));
+    }
+
+    #[test]
+    fn from_k8s_env() -> Result<(), CredentialsError> {
+        const TOKEN_VALUE: &str = "secret";
+        const ROLE_ARN: &str = "role";
+        const SESSION_NAME: &str = "session";
+        let mut file = NamedTempFile::new()?;
+        // We use writeln to add an extra newline at the end of the token, which should be
+        // removed by Variable::from_text_file.
+        writeln!(file, "{}", TOKEN_VALUE)?;
+        let p = WebIdentityProvider::_from_k8s_env(
+            Variable::with_value(file.path().to_string_lossy().to_string()),
+            Variable::with_value(ROLE_ARN.to_string()),
+            Some(Variable::with_value(SESSION_NAME.to_string())),
+        );
+        let token = p.load_token()?;
+        assert_eq!(token.as_ref(), TOKEN_VALUE);
+        Ok(())
+    }
+}
diff --git a/rusoto/services/sts/src/lib.rs b/rusoto/services/sts/src/lib.rs
index 35c1aa64a43..644dfdcf1b2 100644
--- a/rusoto/services/sts/src/lib.rs
+++ b/rusoto/services/sts/src/lib.rs
@@ -21,6 +21,7 @@ extern crate chrono;
 extern crate futures;
 extern crate rusoto_core;
 extern crate serde_urlencoded;
+extern crate tempfile;
 extern crate xml;
 
 mod generated;
diff --git a/service_crategen/services.json b/service_crategen/services.json
index f8f1145bd21..06dd3aaf21d 100644
--- a/service_crategen/services.json
+++ b/service_crategen/services.json
@@ -877,7 +877,8 @@
     "coreVersion": "0.42.0",
     "protocolVersion": "2011-06-15",
     "customDependencies": {
-      "chrono": "0.4.0"
+      "chrono": "0.4.0",
+      "tempfile": "^3.1.0"
     },
     "baseTypeName": "Sts"
   },


Test patch: