Unverified Commit 6f442794 authored by ysaito1001's avatar ysaito1001 Committed by GitHub
Browse files

Merge branch 'main' into merge-smithy-rs-release-1.x.y-to-main

parents 3124d1ac 85d2621b
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
@@ -32,3 +32,32 @@ result of the compilation."""
references = ["aws-sdk-rust#975", "smithy-rs#3269"]
meta = { "breaking" = false, "tada" = true, "bug" = false }
author = "jdisanti"

[[aws-sdk-rust]]
message = """Add `test_credentials` to `ConfigLoader` in `aws_config`. This allows the following pattern during tests:

```rust
async fn main() {
    let conf = aws_config::defaults(BehaviorVersion::latest())
        .test_credentials()
        .await;
}
```

This is designed for unit tests and using local mocks like DynamoDB Local and LocalStack with the SDK.
"""
meta = { "breaking" = false, "tada" = true, "bug" = false }
author = "rcoh"
references = ["smithy-rs#3279", "aws-sdk-rust#971"]

[[aws-sdk-rust]]
message = "Improve the error messages for when auth fails to select an auth scheme for a request."
references = ["aws-sdk-rust#979", "smithy-rs#3277"]
meta = { "breaking" = false, "tada" = false, "bug" = false }
author = "jdisanti"

[[smithy-rs]]
message = "Improve the error messages for when auth fails to select an auth scheme for a request."
references = ["smithy-rs#3277"]
meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client" }
author = "jdisanti"
+4 −3
Original line number Diff line number Diff line
@@ -12,15 +12,17 @@ repository = "https://github.com/smithy-lang/smithy-rs"
behavior-version-latest = []
client-hyper = ["aws-smithy-runtime/connector-hyper-0-14-x"]
rustls = ["aws-smithy-runtime/tls-rustls", "client-hyper"]
allow-compilation = [] # our tests use `cargo test --all-features` and native-tls breaks CI
rt-tokio = ["aws-smithy-async/rt-tokio", "aws-smithy-runtime/rt-tokio", "tokio/rt"]
sso = ["dep:aws-sdk-sso", "dep:aws-sdk-ssooidc", "dep:ring", "dep:hex", "dep:zeroize", "aws-smithy-runtime-api/http-auth"]
credentials-process = ["tokio/process"]

default = ["client-hyper", "rustls", "rt-tokio", "credentials-process", "sso"]

# deprecated: this feature does nothing
allow-compilation = []

[dependencies]
aws-credential-types = { path = "../../sdk/build/aws-sdk/sdk/aws-credential-types" }
aws-credential-types = { path = "../../sdk/build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] }
aws-http = { path = "../../sdk/build/aws-sdk/sdk/aws-http" }
aws-sdk-sts = { path = "../../sdk/build/aws-sdk/sdk/sts", default-features = false }
aws-smithy-async = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-async" }
@@ -52,7 +54,6 @@ zeroize = { version = "1", optional = true }
aws-sdk-ssooidc = { path = "../../sdk/build/aws-sdk/sdk/ssooidc", default-features = false, optional = true }

[dev-dependencies]
aws-credential-types = { path = "../../sdk/build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] }
aws-smithy-runtime = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "connector-hyper-0-14-x", "test-util"] }
aws-smithy-runtime-api = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-runtime-api", features = ["test-util"] }
futures-util = { version = "0.3.16", default-features = false }
+10 −0
Original line number Diff line number Diff line
@@ -212,6 +212,7 @@ mod loader {
    use crate::profile::profile_file::ProfileFiles;
    use crate::provider_config::ProviderConfig;
    use aws_credential_types::provider::{ProvideCredentials, SharedCredentialsProvider};
    use aws_credential_types::Credentials;
    use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep};
    use aws_smithy_async::time::{SharedTimeSource, TimeSource};
    use aws_smithy_runtime_api::client::behavior_version::BehaviorVersion;
@@ -458,6 +459,10 @@ mod loader {
        /// anonymous auth for S3, calling operations in STS that don't require a signature,
        /// or using token-based auth.
        ///
        /// **Note**: For tests, e.g. with a service like DynamoDB Local, this is **not** what you
        /// want. If credentials are disabled, requests cannot be signed. For these use cases, use
        /// [`test_credentials`](Self::test_credentials).
        ///
        /// # Examples
        ///
        /// Turn off credentials in order to call a service without signing:
@@ -474,6 +479,11 @@ mod loader {
            self
        }

        /// Set test credentials for use when signing requests
        pub fn test_credentials(self) -> Self {
            self.credentials_provider(Credentials::for_tests())
        }

        /// Override the name of the app used to build [`SdkConfig`](aws_types::SdkConfig).
        ///
        /// This _optional_ name is used to identify the application in the user agent that
+32 −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 aws_sdk_dynamodb::config::Region;
use aws_sdk_dynamodb::error::DisplayErrorContext;
use aws_sdk_dynamodb::{Client, Config};
use aws_smithy_runtime::assert_str_contains;
use aws_smithy_runtime::client::http::test_util::capture_request;

#[tokio::test]
async fn auth_scheme_error() {
    let (http_client, _) = capture_request(None);
    let config = Config::builder()
        .behavior_version_latest()
        .http_client(http_client)
        .region(Region::new("us-west-2"))
        // intentionally omitting credentials_provider
        .build();
    let client = Client::from_conf(config);

    let err = client
        .list_tables()
        .send()
        .await
        .expect_err("there is no credential provider, so this must fail");
    assert_str_contains!(
        DisplayErrorContext(&err).to_string(),
        "\"sigv4\" wasn't a valid option because there was no identity resolver for it. Be sure to set an identity"
    );
}
+220 −7
Original line number Diff line number Diff line
@@ -20,18 +20,86 @@ use std::error::Error as StdError;
use std::fmt;
use tracing::trace;

#[derive(Debug)]
struct NoMatchingAuthSchemeError(ExploredList);

impl fmt::Display for NoMatchingAuthSchemeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let explored = &self.0;

        // Use the information we have about the auth options that were explored to construct
        // as helpful of an error message as possible.
        if explored.items().count() == 0 {
            return f.write_str(
                "no auth options are available. This can happen if there's \
                    a problem with the service model, or if there is a codegen bug.",
            );
        }
        if explored
            .items()
            .all(|explored| matches!(explored.result, ExploreResult::NoAuthScheme))
        {
            return f.write_str(
                "no auth schemes are registered. This can happen if there's \
                    a problem with the service model, or if there is a codegen bug.",
            );
        }

        let mut try_add_identity = false;
        let mut likely_bug = false;
        f.write_str("failed to select an auth scheme to sign the request with.")?;
        for item in explored.items() {
            write!(
                f,
                " \"{}\" wasn't a valid option because ",
                item.scheme_id.as_str()
            )?;
            f.write_str(match item.result {
                ExploreResult::NoAuthScheme => {
                    likely_bug = true;
                    "no auth scheme was registered for it."
                }
                ExploreResult::NoIdentityResolver => {
                    try_add_identity = true;
                    "there was no identity resolver for it."
                }
                ExploreResult::MissingEndpointConfig => {
                    likely_bug = true;
                    "there is auth config in the endpoint config, but this scheme wasn't listed in it \
                    (see https://github.com/smithy-lang/smithy-rs/discussions/3281 for more details)."
                }
                ExploreResult::NotExplored => {
                    debug_assert!(false, "this should be unreachable");
                    "<unknown>"
                }
            })?;
        }
        if try_add_identity {
            f.write_str(" Be sure to set an identity, such as credentials, auth token, or other identity type that is required for this service.")?;
        } else if likely_bug {
            f.write_str(" This is likely a bug.")?;
        }
        if explored.truncated {
            f.write_str(" Note: there were other auth schemes that were evaluated that weren't listed here.")?;
        }

        Ok(())
    }
}

impl StdError for NoMatchingAuthSchemeError {}

#[derive(Debug)]
enum AuthOrchestrationError {
    NoMatchingAuthScheme,
    MissingEndpointConfig,
    BadAuthSchemeEndpointConfig(Cow<'static, str>),
}

impl fmt::Display for AuthOrchestrationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::NoMatchingAuthScheme => f.write_str(
                "no auth scheme matched auth scheme options. This is a bug. Please file an issue.",
            ),
            // This error is never bubbled up
            Self::MissingEndpointConfig => f.write_str("missing endpoint config"),
            Self::BadAuthSchemeEndpointConfig(message) => f.write_str(message),
        }
    }
@@ -59,6 +127,8 @@ pub(super) async fn orchestrate_auth(
        "orchestrating auth",
    );

    let mut explored = ExploredList::default();

    // Iterate over IDs of possibly-supported auth schemes
    for &scheme_id in options.as_ref() {
        // For each ID, try to resolve the corresponding auth scheme.
@@ -95,16 +165,21 @@ pub(super) async fn orchestrate_auth(
                        )?;
                        return Ok(());
                    }
                    Err(AuthOrchestrationError::NoMatchingAuthScheme) => {
                    Err(AuthOrchestrationError::MissingEndpointConfig) => {
                        explored.push(scheme_id, ExploreResult::MissingEndpointConfig);
                        continue;
                    }
                    Err(other_err) => return Err(other_err.into()),
                }
            } else {
                explored.push(scheme_id, ExploreResult::NoIdentityResolver);
            }
        } else {
            explored.push(scheme_id, ExploreResult::NoAuthScheme);
        }
    }

    Err(AuthOrchestrationError::NoMatchingAuthScheme.into())
    Err(NoMatchingAuthSchemeError(explored).into())
}

fn extract_endpoint_auth_scheme_config(
@@ -135,10 +210,66 @@ fn extract_endpoint_auth_scheme_config(
                .and_then(Document::as_string);
            config_scheme_id == Some(scheme_id.as_str())
        })
        .ok_or(AuthOrchestrationError::NoMatchingAuthScheme)?;
        .ok_or(AuthOrchestrationError::MissingEndpointConfig)?;
    Ok(AuthSchemeEndpointConfig::from(Some(auth_scheme_config)))
}

#[derive(Debug)]
enum ExploreResult {
    NotExplored,
    NoAuthScheme,
    NoIdentityResolver,
    MissingEndpointConfig,
}

/// Information about an evaluated auth option.
/// This should be kept small so it can fit in an array on the stack.
#[derive(Debug)]
struct ExploredAuthOption {
    scheme_id: AuthSchemeId,
    result: ExploreResult,
}
impl Default for ExploredAuthOption {
    fn default() -> Self {
        Self {
            scheme_id: AuthSchemeId::new(""),
            result: ExploreResult::NotExplored,
        }
    }
}

const MAX_EXPLORED_LIST_LEN: usize = 8;

/// Stack allocated list of explored auth options for error messaging
#[derive(Default)]
struct ExploredList {
    items: [ExploredAuthOption; MAX_EXPLORED_LIST_LEN],
    len: usize,
    truncated: bool,
}
impl ExploredList {
    fn items(&self) -> impl Iterator<Item = &ExploredAuthOption> {
        self.items.iter().take(self.len)
    }

    fn push(&mut self, scheme_id: AuthSchemeId, result: ExploreResult) {
        if self.len + 1 >= self.items.len() {
            self.truncated = true;
        } else {
            self.items[self.len] = ExploredAuthOption { scheme_id, result };
            self.len += 1;
        }
    }
}
impl fmt::Debug for ExploredList {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("ExploredList")
            .field("items", &&self.items[0..self.len])
            .field("truncated", &self.truncated)
            .finish()
    }
}

#[cfg(all(test, feature = "test-util"))]
mod tests {
    use super::*;
@@ -481,4 +612,86 @@ mod tests {
                .unwrap()
        );
    }

    #[test]
    fn friendly_error_messages() {
        let err = NoMatchingAuthSchemeError(ExploredList::default());
        assert_eq!(
            "no auth options are available. This can happen if there's a problem with \
            the service model, or if there is a codegen bug.",
            err.to_string()
        );

        let mut list = ExploredList::default();
        list.push(
            AuthSchemeId::new("SigV4"),
            ExploreResult::NoIdentityResolver,
        );
        list.push(
            AuthSchemeId::new("SigV4a"),
            ExploreResult::NoIdentityResolver,
        );
        let err = NoMatchingAuthSchemeError(list);
        assert_eq!(
            "failed to select an auth scheme to sign the request with. \
            \"SigV4\" wasn't a valid option because there was no identity resolver for it. \
            \"SigV4a\" wasn't a valid option because there was no identity resolver for it. \
            Be sure to set an identity, such as credentials, auth token, or other identity \
            type that is required for this service.",
            err.to_string()
        );

        // It should prioritize the suggestion to try an identity before saying it's a bug
        let mut list = ExploredList::default();
        list.push(
            AuthSchemeId::new("SigV4"),
            ExploreResult::NoIdentityResolver,
        );
        list.push(
            AuthSchemeId::new("SigV4a"),
            ExploreResult::MissingEndpointConfig,
        );
        let err = NoMatchingAuthSchemeError(list);
        assert_eq!(
            "failed to select an auth scheme to sign the request with. \
            \"SigV4\" wasn't a valid option because there was no identity resolver for it. \
            \"SigV4a\" wasn't a valid option because there is auth config in the endpoint \
            config, but this scheme wasn't listed in it (see \
            https://github.com/smithy-lang/smithy-rs/discussions/3281 for more details). \
            Be sure to set an identity, such as credentials, auth token, or other identity \
            type that is required for this service.",
            err.to_string()
        );

        // Otherwise, it should suggest it's a bug
        let mut list = ExploredList::default();
        list.push(
            AuthSchemeId::new("SigV4a"),
            ExploreResult::MissingEndpointConfig,
        );
        let err = NoMatchingAuthSchemeError(list);
        assert_eq!(
            "failed to select an auth scheme to sign the request with. \
            \"SigV4a\" wasn't a valid option because there is auth config in the endpoint \
            config, but this scheme wasn't listed in it (see \
            https://github.com/smithy-lang/smithy-rs/discussions/3281 for more details). \
            This is likely a bug.",
            err.to_string()
        );

        // Truncation should be indicated
        let mut list = ExploredList::default();
        for _ in 0..=MAX_EXPLORED_LIST_LEN {
            list.push(
                AuthSchemeId::new("dontcare"),
                ExploreResult::MissingEndpointConfig,
            );
        }
        let err = NoMatchingAuthSchemeError(list).to_string();
        if !err.contains(
            "Note: there were other auth schemes that were evaluated that weren't listed here",
        ) {
            panic!("The error should indicate that the explored list was truncated.");
        }
    }
}
Loading