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

Enable FIPS and dual-stack from SDK config (#2168)

* Enable FIPS and dual-stack from SDK config

* Update changelog

* Appease clippy

* refactor to use docs_for macro
parent e311ebf4
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -148,3 +148,17 @@ references = ["smithy-rs#2170", "aws-sdk-rust#706"]
meta = { "breaking" = false, "tada" = false, "bug" = true }
message = "Remove the webpki-roots feature from `hyper-rustls`"
author = "rcoh"

[[aws-sdk-rust]]
references = ["smithy-rs#2168"]
meta = { "breaking" = false, "tada" = true, "bug" = false }
message = """Add support for resolving FIPS and dual-stack endpoints.

FIPS and dual-stack endpoints can each be configured in multiple ways:
1. Automatically from the environment and AWS profile
2. Across all clients loaded from the same `SdkConfig` via `from_env().use_dual_stack(true).load().await`
3. At a client level when constructing the configuration for an individual client.

Note: Not all services support FIPS and dual-stack.
"""
author = "rcoh"
+6 −0
Original line number Diff line number Diff line
@@ -41,3 +41,9 @@ pub mod timeout_config;
/// Typically, this module is used via [`load_from_env`](crate::load_from_env) or [`from_env`](crate::from_env). It should only be used directly
/// if you need to set custom configuration options like [`region`](credentials::Builder::region) or [`profile_name`](credentials::Builder::profile_name).
pub mod credentials;

/// Default FIPS provider chain
pub mod use_fips;

/// Default dual-stack provider chain
pub mod use_dual_stack;
+2 −120
Original line number Diff line number Diff line
@@ -266,12 +266,9 @@ mod test {
    use tracing_test::traced_test;

    use aws_credential_types::provider::ProvideCredentials;
    use aws_smithy_types::retry::{RetryConfig, RetryMode};
    use aws_types::os_shim_internal::{Env, Fs};

    use crate::default_provider::credentials::DefaultCredentialsChain;
    use crate::default_provider::retry_config;
    use crate::provider_config::ProviderConfig;

    use crate::test_case::TestEnvironment;

    /// Test generation macro
@@ -373,6 +370,7 @@ mod test {
    #[traced_test]
    #[cfg(feature = "client-hyper")]
    async fn no_providers_configured_err() {
        use crate::provider_config::ProviderConfig;
        use aws_credential_types::provider::error::CredentialsError;
        use aws_credential_types::time_source::TimeSource;
        use aws_smithy_async::rt::sleep::TokioSleep;
@@ -398,120 +396,4 @@ mod test {
            creds
        )
    }

    #[tokio::test]
    async fn test_returns_default_retry_config_from_empty_profile() {
        let env = Env::from_slice(&[("AWS_CONFIG_FILE", "config")]);
        let fs = Fs::from_slice(&[("config", "[default]\n")]);

        let provider_config = ProviderConfig::no_configuration().with_env(env).with_fs(fs);

        let actual_retry_config = retry_config::default_provider()
            .configure(&provider_config)
            .retry_config()
            .await;

        let expected_retry_config = RetryConfig::standard();

        assert_eq!(actual_retry_config, expected_retry_config);
        // This is redundant but it's really important to make sure that
        // we're setting these exact values by default so we check twice
        assert_eq!(actual_retry_config.max_attempts(), 3);
        assert_eq!(actual_retry_config.mode(), RetryMode::Standard);
    }

    #[tokio::test]
    async fn test_no_retry_config_in_empty_profile() {
        let env = Env::from_slice(&[("AWS_CONFIG_FILE", "config")]);
        let fs = Fs::from_slice(&[("config", "[default]\n")]);

        let provider_config = ProviderConfig::no_configuration().with_env(env).with_fs(fs);

        let actual_retry_config = retry_config::default_provider()
            .configure(&provider_config)
            .retry_config()
            .await;

        let expected_retry_config = RetryConfig::standard();

        assert_eq!(actual_retry_config, expected_retry_config)
    }

    #[tokio::test]
    async fn test_creation_of_retry_config_from_profile() {
        let env = Env::from_slice(&[("AWS_CONFIG_FILE", "config")]);
        // TODO(https://github.com/awslabs/aws-sdk-rust/issues/247): standard is the default mode;
        // this test would be better if it was setting it to adaptive mode
        // adaptive mode is currently unsupported so that would panic
        let fs = Fs::from_slice(&[(
            "config",
            // If the lines with the vars have preceding spaces, they don't get read
            r#"[default]
max_attempts = 1
retry_mode = standard
            "#,
        )]);

        let provider_config = ProviderConfig::no_configuration().with_env(env).with_fs(fs);

        let actual_retry_config = retry_config::default_provider()
            .configure(&provider_config)
            .retry_config()
            .await;

        let expected_retry_config = RetryConfig::standard().with_max_attempts(1);

        assert_eq!(actual_retry_config, expected_retry_config)
    }

    #[tokio::test]
    async fn test_env_retry_config_takes_precedence_over_profile_retry_config() {
        let env = Env::from_slice(&[
            ("AWS_CONFIG_FILE", "config"),
            ("AWS_MAX_ATTEMPTS", "42"),
            ("AWS_RETRY_MODE", "standard"),
        ]);
        // TODO(https://github.com/awslabs/aws-sdk-rust/issues/247) standard is the default mode;
        // this test would be better if it was setting it to adaptive mode
        // adaptive mode is currently unsupported so that would panic
        let fs = Fs::from_slice(&[(
            "config",
            // If the lines with the vars have preceding spaces, they don't get read
            r#"[default]
max_attempts = 88
retry_mode = standard
            "#,
        )]);

        let provider_config = ProviderConfig::no_configuration().with_env(env).with_fs(fs);

        let actual_retry_config = retry_config::default_provider()
            .configure(&provider_config)
            .retry_config()
            .await;

        let expected_retry_config = RetryConfig::standard().with_max_attempts(42);

        assert_eq!(actual_retry_config, expected_retry_config)
    }

    #[tokio::test]
    #[should_panic = "failed to parse max attempts. source: profile `default`, key: `max_attempts`: invalid digit found in string"]
    async fn test_invalid_profile_retry_config_panics() {
        let env = Env::from_slice(&[("AWS_CONFIG_FILE", "config")]);
        let fs = Fs::from_slice(&[(
            "config",
            // If the lines with the vars have preceding spaces, they don't get read
            r#"[default]
max_attempts = potato
            "#,
        )]);

        let provider_config = ProviderConfig::no_configuration().with_env(env).with_fs(fs);

        let _ = retry_config::default_provider()
            .configure(&provider_config)
            .retry_config()
            .await;
    }
}
+117 −1
Original line number Diff line number Diff line
@@ -149,7 +149,7 @@ mod test {
        error::RetryConfigError, error::RetryConfigErrorKind, RetryConfig, RetryMode,
    };
    use crate::standard_property::PropertyResolutionError;
    use aws_types::os_shim_internal::Env;
    use aws_types::os_shim_internal::{Env, Fs};

    async fn test_provider(
        vars: &[(&str, &str)],
@@ -160,6 +160,122 @@ mod test {
            .await
    }

    #[tokio::test]
    async fn test_returns_default_retry_config_from_empty_profile() {
        let env = Env::from_slice(&[("AWS_CONFIG_FILE", "config")]);
        let fs = Fs::from_slice(&[("config", "[default]\n")]);

        let provider_config = ProviderConfig::no_configuration().with_env(env).with_fs(fs);

        let actual_retry_config = super::default_provider()
            .configure(&provider_config)
            .retry_config()
            .await;

        let expected_retry_config = RetryConfig::standard();

        assert_eq!(actual_retry_config, expected_retry_config);
        // This is redundant but it's really important to make sure that
        // we're setting these exact values by default so we check twice
        assert_eq!(actual_retry_config.max_attempts(), 3);
        assert_eq!(actual_retry_config.mode(), RetryMode::Standard);
    }

    #[tokio::test]
    async fn test_no_retry_config_in_empty_profile() {
        let env = Env::from_slice(&[("AWS_CONFIG_FILE", "config")]);
        let fs = Fs::from_slice(&[("config", "[default]\n")]);

        let provider_config = ProviderConfig::no_configuration().with_env(env).with_fs(fs);

        let actual_retry_config = super::default_provider()
            .configure(&provider_config)
            .retry_config()
            .await;

        let expected_retry_config = RetryConfig::standard();

        assert_eq!(actual_retry_config, expected_retry_config)
    }

    #[tokio::test]
    async fn test_creation_of_retry_config_from_profile() {
        let env = Env::from_slice(&[("AWS_CONFIG_FILE", "config")]);
        // TODO(https://github.com/awslabs/aws-sdk-rust/issues/247): standard is the default mode;
        // this test would be better if it was setting it to adaptive mode
        // adaptive mode is currently unsupported so that would panic
        let fs = Fs::from_slice(&[(
            "config",
            // If the lines with the vars have preceding spaces, they don't get read
            r#"[default]
max_attempts = 1
retry_mode = standard
            "#,
        )]);

        let provider_config = ProviderConfig::no_configuration().with_env(env).with_fs(fs);

        let actual_retry_config = super::default_provider()
            .configure(&provider_config)
            .retry_config()
            .await;

        let expected_retry_config = RetryConfig::standard().with_max_attempts(1);

        assert_eq!(actual_retry_config, expected_retry_config)
    }

    #[tokio::test]
    async fn test_env_retry_config_takes_precedence_over_profile_retry_config() {
        let env = Env::from_slice(&[
            ("AWS_CONFIG_FILE", "config"),
            ("AWS_MAX_ATTEMPTS", "42"),
            ("AWS_RETRY_MODE", "standard"),
        ]);
        // TODO(https://github.com/awslabs/aws-sdk-rust/issues/247) standard is the default mode;
        // this test would be better if it was setting it to adaptive mode
        // adaptive mode is currently unsupported so that would panic
        let fs = Fs::from_slice(&[(
            "config",
            // If the lines with the vars have preceding spaces, they don't get read
            r#"[default]
max_attempts = 88
retry_mode = standard
            "#,
        )]);

        let provider_config = ProviderConfig::no_configuration().with_env(env).with_fs(fs);

        let actual_retry_config = super::default_provider()
            .configure(&provider_config)
            .retry_config()
            .await;

        let expected_retry_config = RetryConfig::standard().with_max_attempts(42);

        assert_eq!(actual_retry_config, expected_retry_config)
    }

    #[tokio::test]
    #[should_panic = "failed to parse max attempts. source: profile `default`, key: `max_attempts`: invalid digit found in string"]
    async fn test_invalid_profile_retry_config_panics() {
        let env = Env::from_slice(&[("AWS_CONFIG_FILE", "config")]);
        let fs = Fs::from_slice(&[(
            "config",
            // If the lines with the vars have preceding spaces, they don't get read
            r#"[default]
max_attempts = potato
            "#,
        )]);

        let provider_config = ProviderConfig::no_configuration().with_env(env).with_fs(fs);

        let _ = super::default_provider()
            .configure(&provider_config)
            .retry_config()
            .await;
    }

    #[tokio::test]
    async fn defaults() {
        let built = test_provider(&[]).await.unwrap();
+89 −0
Original line number Diff line number Diff line
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

use crate::environment::parse_bool;
use crate::provider_config::ProviderConfig;
use crate::standard_property::StandardProperty;
use aws_smithy_types::error::display::DisplayErrorContext;

mod env {
    pub(super) const USE_DUAL_STACK: &str = "AWS_USE_DUALSTACK_ENDPOINT";
}

mod profile_key {
    pub(super) const USE_DUAL_STACK: &str = "use_dualstack_endpoint";
}

pub(crate) async fn use_dual_stack_provider(provider_config: &ProviderConfig) -> Option<bool> {
    StandardProperty::new()
        .env(env::USE_DUAL_STACK)
        .profile(profile_key::USE_DUAL_STACK)
        .validate(provider_config, parse_bool)
        .await
        .map_err(
            |err| tracing::warn!(err = %DisplayErrorContext(&err), "invalid value for dual-stack setting"),
        )
        .unwrap_or(None)
}

#[cfg(test)]
mod test {
    use crate::default_provider::use_dual_stack::use_dual_stack_provider;
    use crate::profile::profile_file::{ProfileFileKind, ProfileFiles};
    use crate::provider_config::ProviderConfig;
    use aws_types::os_shim_internal::{Env, Fs};
    use tracing_test::traced_test;

    #[tokio::test]
    #[traced_test]
    async fn log_error_on_invalid_value() {
        let conf = ProviderConfig::empty().with_env(Env::from_slice(&[(
            "AWS_USE_DUALSTACK_ENDPOINT",
            "not-a-boolean",
        )]));
        assert_eq!(use_dual_stack_provider(&conf).await, None);
        assert!(logs_contain("invalid value for dual-stack setting"));
        assert!(logs_contain("AWS_USE_DUALSTACK_ENDPOINT"));
    }

    #[tokio::test]
    #[traced_test]
    async fn environment_priority() {
        let conf = ProviderConfig::empty()
            .with_env(Env::from_slice(&[("AWS_USE_DUALSTACK_ENDPOINT", "TRUE")]))
            .with_profile_config(
                Some(
                    ProfileFiles::builder()
                        .with_file(ProfileFileKind::Config, "conf")
                        .build(),
                ),
                None,
            )
            .with_fs(Fs::from_slice(&[(
                "conf",
                "[default]\nuse_dualstack_endpoint = false",
            )]));
        assert_eq!(use_dual_stack_provider(&conf).await, Some(true));
    }

    #[tokio::test]
    #[traced_test]
    async fn profile_works() {
        let conf = ProviderConfig::empty()
            .with_profile_config(
                Some(
                    ProfileFiles::builder()
                        .with_file(ProfileFileKind::Config, "conf")
                        .build(),
                ),
                None,
            )
            .with_fs(Fs::from_slice(&[(
                "conf",
                "[default]\nuse_dualstack_endpoint = false",
            )]));
        assert_eq!(use_dual_stack_provider(&conf).await, Some(false));
    }
}
Loading