1 + | /*
|
2 + | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3 + | * SPDX-License-Identifier: Apache-2.0
|
4 + | */
|
5 + |
|
6 + | use crate::provider_config::ProviderConfig;
|
7 + | use aws_runtime::env_config::EnvConfigValue;
|
8 + | use aws_smithy_runtime_api::client::auth::{AuthSchemeId, AuthSchemePreference};
|
9 + | use aws_smithy_types::error::display::DisplayErrorContext;
|
10 + | use std::borrow::Cow;
|
11 + | use std::fmt;
|
12 + |
|
13 + | mod env {
|
14 + | pub(super) const AUTH_SCHEME_PREFERENCE: &str = "AWS_AUTH_SCHEME_PREFERENCE";
|
15 + | }
|
16 + |
|
17 + | mod profile_key {
|
18 + | pub(super) const AUTH_SCHEME_PREFERENCE: &str = "auth_scheme_preference";
|
19 + | }
|
20 + |
|
21 + | /// Load the value for the auth scheme preference
|
22 + | ///
|
23 + | /// This checks the following sources:
|
24 + | /// 1. The environment variable `AWS_AUTH_SCHEME_PREFERENCE=scheme1,scheme2,scheme3`
|
25 + | /// 2. The profile key `auth_scheme_preference=scheme1,scheme2,scheme3`
|
26 + | ///
|
27 + | /// A scheme name can be either a fully qualified name or a shorthand with the namespace prefix trimmed.
|
28 + | /// For example, valid scheme names include "aws.auth#sigv4", "smithy.api#httpBasicAuth", "sigv4", and "httpBasicAuth".
|
29 + | /// Whitespace (spaces or tabs), including leading, trailing, and between names, is ignored.
|
30 + | ///
|
31 + | /// Returns `None` if a parsed string component is empty when creating an `AuthSchemeId`.
|
32 + | pub(crate) async fn auth_scheme_preference_provider(
|
33 + | provider_config: &ProviderConfig,
|
34 + | ) -> Option<AuthSchemePreference> {
|
35 + | let env = provider_config.env();
|
36 + | let profiles = provider_config.profile().await;
|
37 + |
|
38 + | EnvConfigValue::new()
|
39 + | .env(env::AUTH_SCHEME_PREFERENCE)
|
40 + | .profile(profile_key::AUTH_SCHEME_PREFERENCE)
|
41 + | .validate(&env, profiles, parse_auth_scheme_names)
|
42 + | .map_err(|err| tracing::warn!(err = %DisplayErrorContext(&err), "invalid value for `AuthSchemePreference`"))
|
43 + | .unwrap_or(None)
|
44 + | }
|
45 + |
|
46 + | fn parse_auth_scheme_names(csv: &str) -> Result<AuthSchemePreference, InvalidAuthSchemeNamesCsv> {
|
47 + | csv.split(',')
|
48 + | .map(|s| {
|
49 + | let trimmed = s.trim().replace([' ', '\t'], "");
|
50 + | if trimmed.is_empty() {
|
51 + | return Err(InvalidAuthSchemeNamesCsv {
|
52 + | value: format!("Empty name found in `{csv}`."),
|
53 + | });
|
54 + | }
|
55 + | let scheme_name = trimmed.split('#').next_back().unwrap_or(&trimmed);
|
56 + | Ok(runtime_api_auth_scheme_id(scheme_name))
|
57 + | })
|
58 + | .collect::<Result<Vec<_>, _>>()
|
59 + | .map(AuthSchemePreference::from)
|
60 + | }
|
61 + |
|
62 + | // This function is not automatically extensible. When a new scheme is introduced,
|
63 + | // it must be manually updated for the scheme to be recognized in the environment configuration.
|
64 + | //
|
65 + | // Furthermore, this function does not return a predefined `AuthSchemeId` like `HTTP_BASIC_AUTH_SCHEME_ID`.
|
66 + | // The predefined `SchemeId`s are gated behind the `http-auth` feature, but this function returns an `AuthSchemeId`
|
67 + | // that wraps the parsed string regardless of feature flags. If the feature is disabled, the returned
|
68 + | // auth scheme preference is simply ignored during auth scheme resolution.
|
69 + | fn runtime_api_auth_scheme_id(auth_scheme_name: &str) -> AuthSchemeId {
|
70 + | let runtime_api_auth_scheme_str = match auth_scheme_name {
|
71 + | "httpBasicAuth" => "http-basic-auth",
|
72 + | "httpDigestAuth" => "http-digest-auth",
|
73 + | "httpBearerAuth" => "http-bearer-auth",
|
74 + | "httpApiKeyAuth" => "http-api-key-auth",
|
75 + | "noAuth" => "no_auth",
|
76 + | otherwise => otherwise,
|
77 + | };
|
78 + | AuthSchemeId::from(Cow::Owned(runtime_api_auth_scheme_str.to_owned()))
|
79 + | }
|
80 + |
|
81 + | #[derive(Debug)]
|
82 + | pub(crate) struct InvalidAuthSchemeNamesCsv {
|
83 + | value: String,
|
84 + | }
|
85 + |
|
86 + | impl fmt::Display for InvalidAuthSchemeNamesCsv {
|
87 + | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
88 + | write!(
|
89 + | f,
|
90 + | "Not a valid comma-separated auth scheme names: {}",
|
91 + | self.value
|
92 + | )
|
93 + | }
|
94 + | }
|
95 + |
|
96 + | impl std::error::Error for InvalidAuthSchemeNamesCsv {}
|
97 + |
|
98 + | #[cfg(test)]
|
99 + | mod test {
|
100 + | use super::env;
|
101 + | use crate::{
|
102 + | default_provider::auth_scheme_preference::auth_scheme_preference_provider,
|
103 + | provider_config::ProviderConfig,
|
104 + | };
|
105 + | use aws_types::os_shim_internal::Env;
|
106 + | use tracing_test::traced_test;
|
107 + |
|
108 + | #[tokio::test]
|
109 + | #[traced_test]
|
110 + | async fn log_error_on_invalid_value() {
|
111 + | let conf = ProviderConfig::empty().with_env(Env::from_slice(&[(
|
112 + | env::AUTH_SCHEME_PREFERENCE,
|
113 + | "scheme1, , \tscheme2",
|
114 + | )]));
|
115 + | assert_eq!(None, auth_scheme_preference_provider(&conf).await);
|
116 + | assert!(logs_contain(
|
117 + | "Not a valid comma-separated auth scheme names: Empty name found"
|
118 + | ));
|
119 + | }
|
120 + |
|
121 + | #[cfg(feature = "sso")] // for aws-smithy-runtime-api/http-auth
|
122 + | mod http_auth_tests {
|
123 + | use super::env;
|
124 + | #[allow(deprecated)]
|
125 + | use crate::profile::profile_file::{ProfileFileKind, ProfileFiles};
|
126 + | use crate::{
|
127 + | default_provider::auth_scheme_preference::auth_scheme_preference_provider,
|
128 + | provider_config::ProviderConfig,
|
129 + | };
|
130 + | use aws_smithy_runtime_api::client::auth::AuthSchemePreference;
|
131 + | use aws_types::os_shim_internal::{Env, Fs};
|
132 + |
|
133 + | #[tokio::test]
|
134 + | async fn environment_priority() {
|
135 + | let conf = ProviderConfig::empty()
|
136 + | .with_env(Env::from_slice(&[(
|
137 + | env::AUTH_SCHEME_PREFERENCE,
|
138 + | "aws.auth#sigv4, smithy.api#httpBasicAuth, smithy.api#httpDigestAuth, smithy.api#httpBearerAuth, smithy.api#httpApiKeyAuth",
|
139 + | )]))
|
140 + | .with_profile_config(
|
141 + | Some(
|
142 + | #[allow(deprecated)]
|
143 + | ProfileFiles::builder()
|
144 + | .with_file(
|
145 + | #[allow(deprecated)]
|
146 + | ProfileFileKind::Config,
|
147 + | "conf",
|
148 + | )
|
149 + | .build(),
|
150 + | ),
|
151 + | None,
|
152 + | )
|
153 + | .with_fs(Fs::from_slice(&[(
|
154 + | "conf",
|
155 + | "[default]\nauth_scheme_preference = scheme1, scheme2 , \tscheme3 \t",
|
156 + | )]));
|
157 + | assert_eq!(
|
158 + | AuthSchemePreference::from([
|
159 + | aws_runtime::auth::sigv4::SCHEME_ID,
|
160 + | aws_smithy_runtime_api::client::auth::http::HTTP_BASIC_AUTH_SCHEME_ID,
|
161 + | aws_smithy_runtime_api::client::auth::http::HTTP_DIGEST_AUTH_SCHEME_ID,
|
162 + | aws_smithy_runtime_api::client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID,
|
163 + | aws_smithy_runtime_api::client::auth::http::HTTP_API_KEY_AUTH_SCHEME_ID,
|
164 + | ]),
|
165 + | auth_scheme_preference_provider(&conf).await.unwrap()
|
166 + | );
|
167 + | }
|
168 + |
|
169 + | #[tokio::test]
|
170 + | async fn load_from_profile() {
|
171 + | let conf = ProviderConfig::empty()
|
172 + | .with_profile_config(
|
173 + | Some(
|
174 + | #[allow(deprecated)]
|
175 + | ProfileFiles::builder()
|
176 + | .with_file(
|
177 + | #[allow(deprecated)]
|
178 + | ProfileFileKind::Config,
|
179 + | "conf",
|
180 + | )
|
181 + | .build(),
|
182 + | ),
|
183 + | None,
|
184 + | )
|
185 + | .with_fs(Fs::from_slice(&[(
|
186 + | "conf",
|
187 + | "[default]\nauth_scheme_preference = sigv4, httpBasicAuth, httpDigestAuth, \thttpBearerAuth \t, httpApiKeyAuth ",
|
188 + | )]));
|
189 + | assert_eq!(
|
190 + | AuthSchemePreference::from([
|
191 + | aws_runtime::auth::sigv4::SCHEME_ID,
|
192 + | aws_smithy_runtime_api::client::auth::http::HTTP_BASIC_AUTH_SCHEME_ID,
|
193 + | aws_smithy_runtime_api::client::auth::http::HTTP_DIGEST_AUTH_SCHEME_ID,
|
194 + | aws_smithy_runtime_api::client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID,
|
195 + | aws_smithy_runtime_api::client::auth::http::HTTP_API_KEY_AUTH_SCHEME_ID,
|
196 + | ]),
|
197 + | auth_scheme_preference_provider(&conf).await.unwrap()
|
198 + | );
|
199 + | }
|
200 + | }
|
201 + | }
|