From 4d96cc2f572fae0c92a97a6a87a715436851131c Mon Sep 17 00:00:00 2001 From: Nigel Brittain <108375408+nbaws@users.noreply.github.com> Date: Thu, 2 Jan 2025 11:42:29 +1100 Subject: [PATCH] aws: update credential provider proto and support credential file reload (#37834) Commit Message: aws: update credential provider proto and support credential file reload Additional Description: Allows for customisation of the credential provider settings, using the credential provider proto added in #36217 Risk Level: Low Testing: Unit Docs Changes: Updated Release Notes: Updated Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] Addresses #36769 and partially addresses #37432 [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Nigel Brittain --- api/envoy/extensions/common/aws/v3/BUILD | 5 +- .../common/aws/v3/credential_provider.proto | 49 +- changelogs/current.yaml | 3 +- ...ing-filter-credential-provider-config.yaml | 70 +++ .../http_filters/_include/aws_credentials.rst | 22 +- .../aws_request_signing_filter.rst | 12 + source/extensions/common/aws/BUILD | 3 + .../common/aws/credentials_provider_impl.cc | 227 +++++--- .../common/aws/credentials_provider_impl.h | 195 ++++--- .../common/aws/region_provider_impl.cc | 45 +- .../common/aws/region_provider_impl.h | 23 +- source/extensions/common/aws/utility.cc | 27 +- source/extensions/common/aws/utility.h | 15 +- .../filters/http/aws_lambda/config.cc | 4 +- .../http/aws_request_signing/config.cc | 181 +++---- .../filters/http/aws_request_signing/config.h | 5 + .../aws/credentials_provider_impl_test.cc | 489 ++++++++++++++---- .../common/aws/region_provider_impl_test.cc | 55 +- test/extensions/common/aws/utility_test.cc | 4 +- .../http/aws_request_signing/config_test.cc | 92 +++- 20 files changed, 1124 insertions(+), 402 deletions(-) create mode 100644 docs/root/configuration/http/http_filters/_include/aws-request-signing-filter-credential-provider-config.yaml diff --git a/api/envoy/extensions/common/aws/v3/BUILD b/api/envoy/extensions/common/aws/v3/BUILD index 29ebf0741406..09a37ad16b83 100644 --- a/api/envoy/extensions/common/aws/v3/BUILD +++ b/api/envoy/extensions/common/aws/v3/BUILD @@ -5,5 +5,8 @@ load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") licenses(["notice"]) # Apache 2 api_proto_package( - deps = ["@com_github_cncf_xds//udpa/annotations:pkg"], + deps = [ + "//envoy/config/core/v3:pkg", + "@com_github_cncf_xds//udpa/annotations:pkg", + ], ) diff --git a/api/envoy/extensions/common/aws/v3/credential_provider.proto b/api/envoy/extensions/common/aws/v3/credential_provider.proto index b623a40a437d..722e9b32867d 100644 --- a/api/envoy/extensions/common/aws/v3/credential_provider.proto +++ b/api/envoy/extensions/common/aws/v3/credential_provider.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package envoy.extensions.common.aws.v3; +import "envoy/config/core/v3/base.proto"; + import "udpa/annotations/sensitive.proto"; import "udpa/annotations/status.proto"; import "validate/validate.proto"; @@ -14,18 +16,26 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: AWS common configuration] -// Configuration for AWS credential provider. Normally, this is optional and the credentials are +// Configuration for AWS credential provider. This is optional and the credentials are normally // retrieved from the environment or AWS configuration files by following the default credential -// provider chain. This is to support cases where the credentials need to be explicitly provided -// by the control plane. +// provider chain. However, this configuration can be used to override the default behavior. message AwsCredentialProvider { // The option to use `AssumeRoleWithWebIdentity `_. - // If inline_credential is set, this is ignored. - AssumeRoleWithWebIdentityCredentialProvider assume_role_with_web_identity = 1; + AssumeRoleWithWebIdentityCredentialProvider assume_role_with_web_identity_provider = 1; - // The option to use an inline credential. - // If this is set, it takes precedence over assume_role_with_web_identity. + // The option to use an inline credential. If inline credential is provided, no chain will be created and only the inline credential will be used. InlineCredentialProvider inline_credential = 2; + + // The option to specify parameters for credential retrieval from an envoy data source, such as a file in AWS credential format. + CredentialsFileCredentialProvider credentials_file_provider = 3; + + // Create a custom credential provider chain instead of the default credential provider chain. + // If set to TRUE, the credential provider chain that is created contains only those set in this credential provider message. + // If set to FALSE, the settings provided here will act as modifiers to the default credential provider chain. + // Defaults to FALSE. + // + // This has no effect if inline_credential is provided. + bool custom_credential_provider_chain = 4; } // Configuration to use an inline AWS credential. This is an equivalent to setting the well-known @@ -43,12 +53,27 @@ message InlineCredentialProvider { } // Configuration to use `AssumeRoleWithWebIdentity `_ -// to get AWS credentials. +// to retrieve AWS credentials. message AssumeRoleWithWebIdentityCredentialProvider { + // Data source for a web identity token that is provided by the identity provider to assume the role. + // When using this data source, even if a ``watched_directory`` is provided, the token file will only be re-read when the credentials + // returned from AssumeRoleWithWebIdentity expire. + config.core.v3.DataSource web_identity_token_data_source = 1 + [(udpa.annotations.sensitive) = true]; + // The ARN of the role to assume. - string role_arn = 1 [(validate.rules).string = {min_len: 1}]; + string role_arn = 2 [(validate.rules).string = {min_len: 1}]; - // The web identity token that is provided by the identity provider to assume the role. - string web_identity_token = 2 - [(validate.rules).string = {min_len: 1}, (udpa.annotations.sensitive) = true]; + // Optional role session name to use in AssumeRoleWithWebIdentity API call. + string role_session_name = 3; +} + +message CredentialsFileCredentialProvider { + // Data source from which to retrieve AWS credentials + // When using this data source, if a ``watched_directory`` is provided, the credential file will be re-read when a file move is detected. + // See :ref:`watched_directory ` for more information about the ``watched_directory`` field. + config.core.v3.DataSource credentials_data_source = 1 [(udpa.annotations.sensitive) = true]; + + // The profile within the credentials_file data source. If not provided, the default profile will be used. + string profile = 2; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9100f1369544..909c0053f23d 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -248,7 +248,8 @@ new_features: change: | Added an optional field :ref:`credential_provider ` - to the AWS request signing filter to explicitly specify a source for AWS credentials. + to the AWS request signing filter to explicitly specify a source for AWS credentials. Credential file and AssumeRoleWithWebIdentity + behaviour can also be overridden with this field. - area: tls change: | Added support for P-384 and P-521 curves for TLS server certificates. diff --git a/docs/root/configuration/http/http_filters/_include/aws-request-signing-filter-credential-provider-config.yaml b/docs/root/configuration/http/http_filters/_include/aws-request-signing-filter-credential-provider-config.yaml new file mode 100644 index 000000000000..0969a29e13a0 --- /dev/null +++ b/docs/root/configuration/http/http_filters/_include/aws-request-signing-filter-credential-provider-config.yaml @@ -0,0 +1,70 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + http_filters: + - name: envoy.filters.http.router + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + route_config: + name: local_route + virtual_hosts: + - domains: + - '*' + name: local_service + routes: + - match: {prefix: "/"} + route: {cluster: default_service} + clusters: + - name: default_service + load_assignment: + cluster_name: default_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 10001 + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + upstream_http_protocol_options: + auto_sni: true + auto_san_validation: true + auto_config: + http2_protocol_options: {} + http_filters: + - name: envoy.filters.http.aws_request_signing + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.aws_request_signing.v3.AwsRequestSigning + credential_provider: + custom_credential_provider_chain: true + credentials_file_provider: + credentials_data_source: + filename: /tmp/a + watched_directory: + path: /tmp + service_name: vpc-lattice-svcs + region: '*' + signing_algorithm: AWS_SIGV4A + use_unsigned_payload: true + match_excluded_headers: + - prefix: x-envoy + - prefix: x-forwarded + - exact: x-amzn-trace-id + - name: envoy.filters.http.upstream_codec + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext diff --git a/docs/root/configuration/http/http_filters/_include/aws_credentials.rst b/docs/root/configuration/http/http_filters/_include/aws_credentials.rst index 0d945684a009..edf5138df5c0 100644 --- a/docs/root/configuration/http/http_filters/_include/aws_credentials.rst +++ b/docs/root/configuration/http/http_filters/_include/aws_credentials.rst @@ -5,13 +5,24 @@ The filter uses a number of different credentials providers to obtain an AWS acc By default, it moves through the credentials providers in the order described below, stopping when one of them returns an access key ID and a secret access key (the session token is optional). -1. Environment variables. The environment variables ``AWS_ACCESS_KEY_ID``, ``AWS_SECRET_ACCESS_KEY``, and ``AWS_SESSION_TOKEN`` are used. +1. :ref:`inline_credentials ` field. + If this field is configured, no other credentials providers will be used. -2. The AWS credentials file. The environment variables ``AWS_SHARED_CREDENTIALS_FILE`` and ``AWS_PROFILE`` are respected if they are set, else +2. :ref:`credential_provider ` field. + By using this field, the filter allows override of the default environment variables, credential parameters and file locations. + Currently this supports both AWS credentials file locations and content, and AssumeRoleWithWebIdentity token files. + If the :ref:`credential_provider ` field is provided, + it can be used either to modify the default credentials provider chain, or when :ref:`custom_credential_provider_chain ` + is set to ``true``, to create a custom credentials provider chain containing only the specified credentials provider settings. Examples of using these fields + are provided in :ref:`configuration examples `. + +3. Environment variables. The environment variables ``AWS_ACCESS_KEY_ID``, ``AWS_SECRET_ACCESS_KEY``, and ``AWS_SESSION_TOKEN`` are used. + +4. The AWS credentials file. The environment variables ``AWS_SHARED_CREDENTIALS_FILE`` and ``AWS_PROFILE`` are respected if they are set, else the file ``~/.aws/credentials`` and profile ``default`` are used. The fields ``aws_access_key_id``, ``aws_secret_access_key``, and ``aws_session_token`` defined for the profile in the credentials file are used. These credentials are cached for 1 hour. -3. From `AssumeRoleWithWebIdentity `_ API call +5. From `AssumeRoleWithWebIdentity `_ API call towards AWS Security Token Service using ``WebIdentityToken`` read from a file pointed by ``AWS_WEB_IDENTITY_TOKEN_FILE`` environment variable and role arn read from ``AWS_ROLE_ARN`` environment variable. The credentials are extracted from the fields ``AccessKeyId``, ``SecretAccessKey``, and ``SessionToken`` are used, and credentials are cached for 1 hour or until they expire (according to the field @@ -30,7 +41,7 @@ secret access key (the session token is optional). If you require the use of SigV4A signing and you are using an alternate partition, such as cn or GovCloud, you can ensure correct generation of the STS endpoint by setting the first region in your SigV4A region set to the correct region (such as ``cn-northwest-1`` with no wildcard) -4. Either EC2 instance metadata, ECS task metadata or EKS Pod Identity. +6. Either EC2 instance metadata, ECS task metadata or EKS Pod Identity. For EC2 instance metadata, the fields ``AccessKeyId``, ``SecretAccessKey``, and ``Token`` are used, and credentials are cached for 1 hour. For ECS task metadata, the fields ``AccessKeyId``, ``SecretAccessKey``, and ``Token`` are used, and credentials are cached for 1 hour or until they expire (according to the field ``Expiration``). @@ -46,9 +57,6 @@ secret access key (the session token is optional). The static internal cluster will still be added even if initially ``envoy.reloadable_features.use_http_client_to_fetch_aws_credentials`` is not set so that subsequently if the reloadable feature is set to ``true`` the cluster config is available to fetch the credentials. -Alternatively, each AWS filter (either AWS Request Signing or AWS Lambda) has its own optional configuration to specify the source of the credentials. For example, AWS Request Signing filter -has :ref:`credential_provider ` field. - Statistics ---------- diff --git a/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst b/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst index 7bf0e5ba447c..c25b8a1fc0da 100644 --- a/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst +++ b/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst @@ -59,6 +59,8 @@ the following HTTP header modifications will be made by this extension: Example configuration --------------------- +.. _config_http_filters_aws_request_signing_examples: + Example filter configuration: .. literalinclude:: _include/aws-request-signing-filter.yaml @@ -86,6 +88,16 @@ An example of configuring this filter to use ``AWS_SIGV4A`` signing with a wildc :linenos: :caption: :download:`aws-request-signing-filter-sigv4a.yaml <_include/aws-request-signing-filter-sigv4a.yaml>` +An example of using the credential provider configuration to modify the default behaviour of the credential provider chain. In this scenario, we use +the ``custom_credential_provider_chain`` option to disable the default credential provider chain and use specific settings for the credential file +credentials provider. These settings include a ``watched_directory``, which configures the filter to reload the credentials file when it changes. + +.. literalinclude:: _include/aws-request-signing-filter-credential-provider-config.yaml + :language: yaml + :lines: 46-56 + :lineno-start: 46 + :linenos: + :caption: :download:`aws-request-signing-filter-credential-provider-config.yaml <_include/aws-request-signing-filter-credential-provider-config.yaml>` Configuration as an upstream HTTP filter ---------------------------------------- diff --git a/source/extensions/common/aws/BUILD b/source/extensions/common/aws/BUILD index ddd52931367a..e16539526327 100644 --- a/source/extensions/common/aws/BUILD +++ b/source/extensions/common/aws/BUILD @@ -120,12 +120,14 @@ envoy_cc_library( "//envoy/api:api_interface", "//source/common/common:logger_lib", "//source/common/common:thread_lib", + "//source/common/config:datasource_lib", "//source/common/http:utility_lib", "//source/common/init:target_lib", "//source/common/json:json_loader_lib", "//source/common/runtime:runtime_features_lib", "//source/common/tracing:http_tracer_lib", "@com_google_absl//absl/time", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/common/aws/v3:pkg_cc_proto", ], ) @@ -176,5 +178,6 @@ envoy_cc_library( ":region_provider_interface", ":utility_lib", "//source/common/common:logger_lib", + "@envoy_api//envoy/extensions/common/aws/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/common/aws/credentials_provider_impl.cc b/source/extensions/common/aws/credentials_provider_impl.cc index 82728d8d39f9..c72f23a4e432 100644 --- a/source/extensions/common/aws/credentials_provider_impl.cc +++ b/source/extensions/common/aws/credentials_provider_impl.cc @@ -324,36 +324,78 @@ void MetadataCredentialsProviderBase::setCredentialsToAllThreads( } } +CredentialsFileCredentialsProvider::CredentialsFileCredentialsProvider( + Server::Configuration::ServerFactoryContext& context, + const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& + credential_file_config) + : context_(context), profile_("") { + + if (credential_file_config.has_credentials_data_source()) { + auto provider_or_error_ = Config::DataSource::DataSourceProvider::create( + credential_file_config.credentials_data_source(), context.mainThreadDispatcher(), + context.threadLocal(), context.api(), false, 4096); + if (provider_or_error_.ok()) { + credential_file_data_source_provider_ = std::move(provider_or_error_.value()); + if (credential_file_config.credentials_data_source().has_watched_directory()) { + has_watched_directory_ = true; + } + } else { + ENVOY_LOG_MISC(info, "Invalid credential file data source"); + credential_file_data_source_provider_.reset(); + } + } + if (!credential_file_config.profile().empty()) { + profile_ = credential_file_config.profile(); + } +} + bool CredentialsFileCredentialsProvider::needsRefresh() { - return api_.timeSource().systemTime() - last_updated_ > REFRESH_INTERVAL; + return has_watched_directory_ + ? true + : context_.api().timeSource().systemTime() - last_updated_ > REFRESH_INTERVAL; } void CredentialsFileCredentialsProvider::refresh() { + auto profile = profile_.empty() ? Utility::getCredentialProfileName() : profile_; + ENVOY_LOG(debug, "Getting AWS credentials from the credentials file"); - auto credentials_file = Utility::getCredentialFilePath(); - auto profile = profile_.empty() ? Utility::getCredentialProfileName() : profile_; + std::string credential_file_data, credential_file_path; - ENVOY_LOG(debug, "Credentials file path = {}, profile name = {}", credentials_file, profile); + // Use data source if provided, otherwise read from default AWS credential file path + if (credential_file_data_source_provider_.has_value()) { + credential_file_data = credential_file_data_source_provider_.value()->data(); + credential_file_path = ""; + } else { + credential_file_path = Utility::getCredentialFilePath(); + auto credential_file = context_.api().fileSystem().fileReadToEnd(credential_file_path); + if (credential_file.ok()) { + credential_file_data = credential_file.value(); + } else { + ENVOY_LOG(debug, "Unable to read from credential file {}", credential_file_path); + // Update last_updated_ now so that even if this function returns before successfully + // extracting credentials, this function won't be called again until after the + // REFRESH_INTERVAL. This prevents envoy from attempting and failing to read the credentials + // file on every request if there are errors extracting credentials from it (e.g. if the + // credentials file doesn't exist). + last_updated_ = context_.api().timeSource().systemTime(); + return; + } + } + ENVOY_LOG(debug, "Credentials file path = {}, profile name = {}", credential_file_path, profile); - extractCredentials(credentials_file, profile); + extractCredentials(credential_file_data.data(), profile); } -void CredentialsFileCredentialsProvider::extractCredentials(const std::string& credentials_file, - const std::string& profile) { - // Update last_updated_ now so that even if this function returns before successfully - // extracting credentials, this function won't be called again until after the REFRESH_INTERVAL. - // This prevents envoy from attempting and failing to read the credentials file on every request - // if there are errors extracting credentials from it (e.g. if the credentials file doesn't - // exist). - last_updated_ = api_.timeSource().systemTime(); +void CredentialsFileCredentialsProvider::extractCredentials(absl::string_view credentials_string, + absl::string_view profile) { std::string access_key_id, secret_access_key, session_token; absl::flat_hash_map elements = { {AWS_ACCESS_KEY_ID, ""}, {AWS_SECRET_ACCESS_KEY, ""}, {AWS_SESSION_TOKEN, ""}}; absl::flat_hash_map::iterator it; - Utility::resolveProfileElements(credentials_file, profile, elements); + Utility::resolveProfileElementsFromString(credentials_string.data(), profile.data(), elements); // if profile file fails to load, or these elements are not found in the profile, their values // will remain blank when retrieving them from the hash map access_key_id = elements.find(AWS_ACCESS_KEY_ID)->second; @@ -364,14 +406,14 @@ void CredentialsFileCredentialsProvider::extractCredentials(const std::string& c // Return empty credentials if we're unable to retrieve from profile cached_credentials_ = Credentials(); } else { - ENVOY_LOG(debug, "Found following AWS credentials for profile '{}' in {}: {}={}, {}={}, {}={}", - profile, credentials_file, AWS_ACCESS_KEY_ID, access_key_id, AWS_SECRET_ACCESS_KEY, + ENVOY_LOG(debug, "Found following AWS credentials for profile '{}': {}={}, {}={}, {}={}", + profile, AWS_ACCESS_KEY_ID, access_key_id, AWS_SECRET_ACCESS_KEY, secret_access_key.empty() ? "" : "*****", AWS_SESSION_TOKEN, session_token.empty() ? "" : "*****"); cached_credentials_ = Credentials(access_key_id, secret_access_key, session_token); } - last_updated_ = api_.timeSource().systemTime(); + last_updated_ = context_.api().timeSource().systemTime(); } InstanceProfileCredentialsProvider::InstanceProfileCredentialsProvider( @@ -736,21 +778,33 @@ void ContainerCredentialsProvider::onMetadataError(Failure reason) { } WebIdentityCredentialsProvider::WebIdentityCredentialsProvider( - Api::Api& api, ServerFactoryContextOptRef context, - const CurlMetadataFetcher& fetch_metadata_using_curl, - CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view token_file_path, - absl::string_view token, absl::string_view sts_endpoint, absl::string_view role_arn, - absl::string_view role_session_name, + Server::Configuration::ServerFactoryContext& context, + CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view sts_endpoint, MetadataFetcher::MetadataReceiver::RefreshState refresh_state, - std::chrono::seconds initialization_timer, absl::string_view cluster_name = {}) + std::chrono::seconds initialization_timer, + const envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider& + web_identity_config, + absl::string_view cluster_name = {}) : MetadataCredentialsProviderBase( - api, context, fetch_metadata_using_curl, create_metadata_fetcher_cb, cluster_name, + context.api(), context, nullptr, create_metadata_fetcher_cb, cluster_name, envoy::config::cluster::v3::Cluster::LOGICAL_DNS /*cluster_type*/, sts_endpoint, refresh_state, initialization_timer), - token_file_path_(token_file_path), token_(token), sts_endpoint_(sts_endpoint), - role_arn_(role_arn), role_session_name_(role_session_name) {} + sts_endpoint_(sts_endpoint), role_arn_(web_identity_config.role_arn()), + role_session_name_(web_identity_config.role_session_name()) { + + auto provider_or_error_ = Config::DataSource::DataSourceProvider::create( + web_identity_config.web_identity_token_data_source(), context.mainThreadDispatcher(), + context.threadLocal(), context.api(), false, 4096); + if (provider_or_error_.ok()) { + web_identity_data_source_provider_ = std::move(provider_or_error_.value()); + } else { + ENVOY_LOG_MISC(info, "Invalid web identity data source"); + web_identity_data_source_provider_.reset(); + } +} bool WebIdentityCredentialsProvider::needsRefresh() { + const auto now = api_.timeSource().systemTime(); auto expired = (now - last_updated_ > REFRESH_INTERVAL); @@ -762,19 +816,18 @@ bool WebIdentityCredentialsProvider::needsRefresh() { } void WebIdentityCredentialsProvider::refresh() { - ENVOY_LOG(debug, "Getting AWS web identity credentials from STS: {}", sts_endpoint_); - std::string identity_token = token_; - if (identity_token.empty()) { - const auto web_token_file_or_error = api_.fileSystem().fileReadToEnd(token_file_path_); - if (!web_token_file_or_error.ok()) { - ENVOY_LOG(debug, "Unable to read AWS web identity credentials from {}", token_file_path_); - cached_credentials_ = Credentials(); - return; - } - identity_token = web_token_file_or_error.value(); + absl::string_view web_identity_data; + + // If we're unable to read from the configured data source, exit early. + if (!web_identity_data_source_provider_.has_value()) { + return; } + ENVOY_LOG(debug, "Getting AWS web identity credentials from STS: {}", sts_endpoint_); + + web_identity_data = web_identity_data_source_provider_.value()->data(); + Http::RequestMessageImpl message; message.headers().setScheme(Http::Headers::get().SchemeValues.Https); message.headers().setMethod(Http::Headers::get().MethodValues.Get); @@ -787,7 +840,7 @@ void WebIdentityCredentialsProvider::refresh() { "&WebIdentityToken={}", Envoy::Http::Utility::PercentEncoding::encode(role_session_name_), Envoy::Http::Utility::PercentEncoding::encode(role_arn_), - Envoy::Http::Utility::PercentEncoding::encode(identity_token))); + Envoy::Http::Utility::PercentEncoding::encode(web_identity_data))); // Use the Accept header to ensure that AssumeRoleWithWebIdentityResponse is returned as JSON. message.headers().setReference(Http::CustomHeaders::get().Accept, Http::Headers::get().ContentTypeValues.Json); @@ -941,30 +994,77 @@ std::string stsClusterName(absl::string_view region) { return absl::StrCat(STS_TOKEN_CLUSTER, "-", region); } +CustomCredentialsProviderChain::CustomCredentialsProviderChain( + Server::Configuration::ServerFactoryContext& context, absl::string_view region, + const envoy::extensions::common::aws::v3::AwsCredentialProvider& credential_provider_config, + const CustomCredentialsProviderChainFactories& factories) { + + // Custom chain currently only supports file based and web identity credentials + if (credential_provider_config.has_assume_role_with_web_identity_provider()) { + auto web_identity = credential_provider_config.assume_role_with_web_identity_provider(); + const std::string sts_endpoint = Utility::getSTSEndpoint(region) + ":443"; + const auto region_uuid = absl::StrCat(region, "_", context.api().randomGenerator().uuid()); + const std::string cluster_name = stsClusterName(region_uuid); + std::string role_session_name = web_identity.role_session_name(); + if (role_session_name.empty()) { + web_identity.set_role_session_name(sessionName(context.api())); + } + const auto refresh_state = MetadataFetcher::MetadataReceiver::RefreshState::FirstRefresh; + const auto initialization_timer = std::chrono::seconds(2); + add(factories.createWebIdentityCredentialsProvider( + context, MetadataFetcher::create, sts_endpoint, refresh_state, initialization_timer, + web_identity, cluster_name)); + } + + if (credential_provider_config.has_credentials_file_provider()) { + add(factories.createCredentialsFileCredentialsProvider( + context, credential_provider_config.credentials_file_provider())); + } +} + DefaultCredentialsProviderChain::DefaultCredentialsProviderChain( Api::Api& api, ServerFactoryContextOptRef context, Singleton::Manager& singleton_manager, absl::string_view region, const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, + const envoy::extensions::common::aws::v3::AwsCredentialProvider& credential_provider_config, const CredentialsProviderChainFactories& factories) { ENVOY_LOG(debug, "Using environment credentials provider"); add(factories.createEnvironmentCredentialsProvider()); - ENVOY_LOG(debug, "Using credentials file credentials provider"); - add(factories.createCredentialsFileCredentialsProvider(api)); - // Initial state for an async credential receiver auto refresh_state = MetadataFetcher::MetadataReceiver::RefreshState::FirstRefresh; // Initial amount of time for async credential receivers to wait for an initial refresh to succeed auto initialization_timer = std::chrono::seconds(2); - // WebIdentityCredentialsProvider can be used only if `context` is supplied which is required to - // use http async http client to make http calls to fetch the credentials. if (context) { - const auto web_token_path = absl::NullSafeStringView(std::getenv(AWS_WEB_IDENTITY_TOKEN_FILE)); - const auto role_arn = absl::NullSafeStringView(std::getenv(AWS_ROLE_ARN)); - if (!web_token_path.empty() && !role_arn.empty()) { - const auto session_name = sessionName(api); + + ENVOY_LOG(debug, "Using credentials file credentials provider"); + add(factories.createCredentialsFileCredentialsProvider( + context.value(), credential_provider_config.credentials_file_provider())); + + auto web_identity = credential_provider_config.assume_role_with_web_identity_provider(); + + // Configure defaults if nothing is set in the config + if (!web_identity.has_web_identity_token_data_source()) { + web_identity.mutable_web_identity_token_data_source()->set_filename( + absl::NullSafeStringView(std::getenv(AWS_WEB_IDENTITY_TOKEN_FILE))); + } + + if (web_identity.role_arn().empty()) { + web_identity.set_role_arn(absl::NullSafeStringView(std::getenv(AWS_ROLE_ARN))); + } + + if (web_identity.role_session_name().empty()) { + web_identity.set_role_session_name(sessionName(api)); + } + + if ((!web_identity.web_identity_token_data_source().filename().empty() || + !web_identity.web_identity_token_data_source().inline_bytes().empty() || + !web_identity.web_identity_token_data_source().inline_string().empty() || + !web_identity.web_identity_token_data_source().environment_variable().empty()) && + !web_identity.role_arn().empty()) { + const auto sts_endpoint = Utility::getSTSEndpoint(region) + ":443"; const auto region_uuid = absl::StrCat(region, "_", context->api().randomGenerator().uuid()); @@ -973,11 +1073,10 @@ DefaultCredentialsProviderChain::DefaultCredentialsProviderChain( ENVOY_LOG( debug, "Using web identity credentials provider with STS endpoint: {} and session name: {}", - sts_endpoint, session_name); + sts_endpoint, web_identity.role_session_name()); add(factories.createWebIdentityCredentialsProvider( - api, context, fetch_metadata_using_curl, MetadataFetcher::create, cluster_name, - web_token_path, "", sts_endpoint, role_arn, session_name, refresh_state, - initialization_timer)); + context.value(), MetadataFetcher::create, sts_endpoint, refresh_state, + initialization_timer, web_identity, cluster_name)); } } @@ -1060,34 +1159,6 @@ DefaultCredentialsProviderChain::createInstanceProfileCredentialsProvider( }); } -absl::StatusOr createCredentialsProviderFromConfig( - Server::Configuration::ServerFactoryContext& context, absl::string_view region, - const envoy::extensions::common::aws::v3::AwsCredentialProvider& config) { - // The precedence order is: inline_credential > assume_role_with_web_identity. - if (config.has_inline_credential()) { - const auto& inline_credential = config.inline_credential(); - return std::make_shared(inline_credential.access_key_id(), - inline_credential.secret_access_key(), - inline_credential.session_token()); - } else if (config.has_assume_role_with_web_identity()) { - const auto& web_identity = config.assume_role_with_web_identity(); - const std::string& role_arn = web_identity.role_arn(); - const std::string& token = web_identity.web_identity_token(); - const std::string sts_endpoint = Utility::getSTSEndpoint(region) + ":443"; - const auto region_uuid = absl::StrCat(region, "_", context.api().randomGenerator().uuid()); - const std::string cluster_name = stsClusterName(region_uuid); - const std::string role_session_name = sessionName(context.api()); - const auto refresh_state = MetadataFetcher::MetadataReceiver::RefreshState::FirstRefresh; - // This "two seconds" is a bit arbitrary, but matches the other places in the codebase. - const auto initialization_timer = std::chrono::seconds(2); - return std::make_shared( - context.api(), context, nullptr, MetadataFetcher::create, "", token, sts_endpoint, role_arn, - role_session_name, refresh_state, initialization_timer, cluster_name); - } else { - return absl::InvalidArgumentError("No AWS credential provider specified"); - } -} - } // namespace Aws } // namespace Common } // namespace Extensions diff --git a/source/extensions/common/aws/credentials_provider_impl.h b/source/extensions/common/aws/credentials_provider_impl.h index ed5c8ce386e1..e7a10293bf4c 100644 --- a/source/extensions/common/aws/credentials_provider_impl.h +++ b/source/extensions/common/aws/credentials_provider_impl.h @@ -7,6 +7,7 @@ #include "envoy/api/api.h" #include "envoy/common/optref.h" +#include "envoy/config/core/v3/base.pb.h" #include "envoy/event/timer.h" #include "envoy/extensions/common/aws/v3/credential_provider.pb.h" #include "envoy/http/message.h" @@ -15,6 +16,7 @@ #include "source/common/common/lock_guard.h" #include "source/common/common/logger.h" #include "source/common/common/thread.h" +#include "source/common/config/datasource.h" #include "source/common/init/target_impl.h" #include "source/common/protobuf/message_validator_impl.h" #include "source/common/protobuf/utility.h" @@ -92,18 +94,20 @@ class CachedCredentialsProviderBase : public CredentialsProvider, */ class CredentialsFileCredentialsProvider : public CachedCredentialsProviderBase { public: - CredentialsFileCredentialsProvider(Api::Api& api) : CredentialsFileCredentialsProvider(api, "") {} - - CredentialsFileCredentialsProvider(Api::Api& api, const std::string& profile) - : api_(api), profile_(profile) {} + CredentialsFileCredentialsProvider( + Server::Configuration::ServerFactoryContext& context, + const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& + credential_file_config = {}); private: - Api::Api& api_; - const std::string profile_; + Server::Configuration::ServerFactoryContext& context_; + std::string profile_; + absl::optional credential_file_data_source_provider_; + bool has_watched_directory_ = false; bool needsRefresh() override; void refresh() override; - void extractCredentials(const std::string& credentials_file, const std::string& profile); + void extractCredentials(absl::string_view credentials_string, absl::string_view profile); }; class LoadClusterEntryHandle { @@ -321,29 +325,22 @@ class WebIdentityCredentialsProvider : public MetadataCredentialsProviderBase, public: // token and token_file_path are mutually exclusive. If token is not empty, token_file_path is // not used, and vice versa. - WebIdentityCredentialsProvider(Api::Api& api, ServerFactoryContextOptRef context, - const CurlMetadataFetcher& fetch_metadata_using_curl, - CreateMetadataFetcherCb create_metadata_fetcher_cb, - absl::string_view token_file_path, absl::string_view token, - absl::string_view sts_endpoint, absl::string_view role_arn, - absl::string_view role_session_name, - MetadataFetcher::MetadataReceiver::RefreshState refresh_state, - std::chrono::seconds initialization_timer, - absl::string_view cluster_name); + WebIdentityCredentialsProvider( + Server::Configuration::ServerFactoryContext& context, + CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view sts_endpoint, + MetadataFetcher::MetadataReceiver::RefreshState refresh_state, + std::chrono::seconds initialization_timer, + const envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider& + web_identity_config, + absl::string_view cluster_name); // Following functions are for MetadataFetcher::MetadataReceiver interface void onMetadataSuccess(const std::string&& body) override; void onMetadataError(Failure reason) override; - const std::string& tokenForTesting() const { return token_; } - const std::string& roleArnForTesting() const { return role_arn_; } - private: - // token_ and token_file_path_ are mutually exclusive. If token_ is set, token_file_path_ is not - // used. - const std::string token_file_path_; - const std::string token_; const std::string sts_endpoint_; + absl::optional web_identity_data_source_provider_; const std::string role_arn_; const std::string role_session_name_; @@ -376,17 +373,19 @@ class CredentialsProviderChainFactories { virtual CredentialsProviderSharedPtr createEnvironmentCredentialsProvider() const PURE; - virtual CredentialsProviderSharedPtr - createCredentialsFileCredentialsProvider(Api::Api& api) const PURE; + virtual CredentialsProviderSharedPtr createCredentialsFileCredentialsProvider( + Server::Configuration::ServerFactoryContext& context, + const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& + credential_file_config = {}) const PURE; virtual CredentialsProviderSharedPtr createWebIdentityCredentialsProvider( - Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, - CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view cluster_name, - absl::string_view token_file_path, absl::string_view token, absl::string_view sts_endpoint, - absl::string_view role_arn, absl::string_view role_session_name, + Server::Configuration::ServerFactoryContext& context, + CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view sts_endpoint, MetadataFetcher::MetadataReceiver::RefreshState refresh_state, - std::chrono::seconds initialization_timer) const PURE; + std::chrono::seconds initialization_timer, + const envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider& + web_identity_config, + absl::string_view cluster_name) const PURE; virtual CredentialsProviderSharedPtr createContainerCredentialsProvider( Api::Api& api, ServerFactoryContextOptRef context, Singleton::Manager& singleton_manager, @@ -405,6 +404,79 @@ class CredentialsProviderChainFactories { std::chrono::seconds initialization_timer, absl::string_view cluster_name) const PURE; }; +class CustomCredentialsProviderChainFactories { +public: + virtual ~CustomCredentialsProviderChainFactories() = default; + + virtual CredentialsProviderSharedPtr createCredentialsFileCredentialsProvider( + Server::Configuration::ServerFactoryContext& context, + const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& + credential_file_config = {}) const PURE; + + virtual CredentialsProviderSharedPtr createWebIdentityCredentialsProvider( + Server::Configuration::ServerFactoryContext& context, + CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view sts_endpoint, + MetadataFetcher::MetadataReceiver::RefreshState refresh_state, + std::chrono::seconds initialization_timer, + const envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider& + web_identity_config, + absl::string_view cluster_name) const PURE; +}; + +// TODO(nbaws) Add additional providers to the custom chain. +class CustomCredentialsProviderChain : public CredentialsProviderChain, + public CustomCredentialsProviderChainFactories { +public: + CustomCredentialsProviderChain( + Server::Configuration::ServerFactoryContext& context, absl::string_view region, + const envoy::extensions::common::aws::v3::AwsCredentialProvider& credential_provider_config, + const CustomCredentialsProviderChainFactories& factories); + + CustomCredentialsProviderChain( + Server::Configuration::ServerFactoryContext& context, absl::string_view region, + const envoy::extensions::common::aws::v3::AwsCredentialProvider& credential_provider_config) + : CustomCredentialsProviderChain(context, region, credential_provider_config, *this) {} + + CredentialsProviderSharedPtr createCredentialsFileCredentialsProvider( + Server::Configuration::ServerFactoryContext& context, + const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& + credential_file_config = {} + + ) const override { + + return std::make_shared(context, credential_file_config); + }; + + CredentialsProviderSharedPtr createWebIdentityCredentialsProvider( + Server::Configuration::ServerFactoryContext& context, + CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view sts_endpoint, + MetadataFetcher::MetadataReceiver::RefreshState refresh_state, + std::chrono::seconds initialization_timer, + const envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider& + web_identity_config, + absl::string_view cluster_name) const override { + return std::make_shared( + context, create_metadata_fetcher_cb, sts_endpoint, refresh_state, initialization_timer, + web_identity_config, cluster_name); + }; +}; + +/** + * Credential provider based on an inline credential. + */ +class InlineCredentialProvider : public CredentialsProvider { +public: + explicit InlineCredentialProvider(absl::string_view access_key_id, + absl::string_view secret_access_key, + absl::string_view session_token) + : credentials_(access_key_id, secret_access_key, session_token) {} + + Credentials getCredentials() override { return credentials_; } + +private: + const Credentials credentials_; +}; + /** * Default AWS credentials provider chain. * @@ -417,14 +489,18 @@ class DefaultCredentialsProviderChain : public CredentialsProviderChain, DefaultCredentialsProviderChain( Api::Api& api, ServerFactoryContextOptRef context, Singleton::Manager& singleton_manager, absl::string_view region, - const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl) + const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, + const envoy::extensions::common::aws::v3::AwsCredentialProvider& credential_provider_config = + {}) : DefaultCredentialsProviderChain(api, context, singleton_manager, region, - fetch_metadata_using_curl, *this) {} + fetch_metadata_using_curl, credential_provider_config, + *this) {} DefaultCredentialsProviderChain( Api::Api& api, ServerFactoryContextOptRef context, Singleton::Manager& singleton_manager, absl::string_view region, const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, + const envoy::extensions::common::aws::v3::AwsCredentialProvider& credential_provider_config, const CredentialsProviderChainFactories& factories); private: @@ -432,10 +508,14 @@ class DefaultCredentialsProviderChain : public CredentialsProviderChain, return std::make_shared(); } - CredentialsProviderSharedPtr - createCredentialsFileCredentialsProvider(Api::Api& api) const override { - return std::make_shared(api); - } + CredentialsProviderSharedPtr createCredentialsFileCredentialsProvider( + Server::Configuration::ServerFactoryContext& context, + const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& + credential_file_config + + ) const override { + return std::make_shared(context, credential_file_config); + }; CredentialsProviderSharedPtr createContainerCredentialsProvider( Api::Api& api, ServerFactoryContextOptRef context, Singleton::Manager& singleton_manager, @@ -454,44 +534,19 @@ class DefaultCredentialsProviderChain : public CredentialsProviderChain, std::chrono::seconds initialization_timer, absl::string_view cluster_name) const override; CredentialsProviderSharedPtr createWebIdentityCredentialsProvider( - Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, - CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view cluster_name, - absl::string_view token_file_path, absl::string_view token, absl::string_view sts_endpoint, - absl::string_view role_arn, absl::string_view role_session_name, + Server::Configuration::ServerFactoryContext& context, + CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view sts_endpoint, MetadataFetcher::MetadataReceiver::RefreshState refresh_state, - std::chrono::seconds initialization_timer) const override { + std::chrono::seconds initialization_timer, + const envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider& + web_identity_config, + absl::string_view cluster_name) const override { return std::make_shared( - api, context, fetch_metadata_using_curl, create_metadata_fetcher_cb, token_file_path, token, - sts_endpoint, role_arn, role_session_name, refresh_state, initialization_timer, - cluster_name); + context, create_metadata_fetcher_cb, sts_endpoint, refresh_state, initialization_timer, + web_identity_config, cluster_name); } }; -/** - * Credential provider based on an inline credential. - */ -class InlineCredentialProvider : public CredentialsProvider { -public: - explicit InlineCredentialProvider(absl::string_view access_key_id, - absl::string_view secret_access_key, - absl::string_view session_token) - : credentials_(access_key_id, secret_access_key, session_token) {} - - Credentials getCredentials() override { return credentials_; } - -private: - const Credentials credentials_; -}; - -/** - * Create an AWS credentials provider from the proto configuration instead of using the default - * credentials provider chain. - */ -absl::StatusOr createCredentialsProviderFromConfig( - Server::Configuration::ServerFactoryContext& context, absl::string_view region, - const envoy::extensions::common::aws::v3::AwsCredentialProvider& config); - using InstanceProfileCredentialsProviderPtr = std::shared_ptr; using ContainerCredentialsProviderPtr = std::shared_ptr; using WebIdentityCredentialsProviderPtr = std::shared_ptr; diff --git a/source/extensions/common/aws/region_provider_impl.cc b/source/extensions/common/aws/region_provider_impl.cc index ca7255e00843..55f4b1ae2244 100644 --- a/source/extensions/common/aws/region_provider_impl.cc +++ b/source/extensions/common/aws/region_provider_impl.cc @@ -40,14 +40,29 @@ absl::optional EnvironmentRegionProvider::getRegionSet() { return regionSet; } +AWSCredentialsFileRegionProvider::AWSCredentialsFileRegionProvider( + const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& + credential_file_config) { + if (credential_file_config.has_credentials_data_source() && + credential_file_config.credentials_data_source().has_filename()) { + credential_file_path_ = credential_file_config.credentials_data_source().filename(); + } + if (!credential_file_config.profile().empty()) { + profile_ = credential_file_config.profile(); + } +} + absl::optional AWSCredentialsFileRegionProvider::getRegion() { absl::flat_hash_map elements = {{REGION, ""}}; absl::flat_hash_map::iterator it; - // Search for the region in the credentials file + std::string file_path, profile; + file_path = credential_file_path_.has_value() ? credential_file_path_.value() + : Utility::getCredentialFilePath(); + profile = profile_.has_value() ? profile_.value() : Utility::getCredentialProfileName(); - if (!Utility::resolveProfileElements(Utility::getCredentialFilePath(), - Utility::getCredentialProfileName(), elements)) { + // Search for the region in the credentials file + if (!Utility::resolveProfileElementsFromFile(file_path, profile, elements)) { return absl::nullopt; } it = elements.find(REGION); @@ -64,10 +79,14 @@ absl::optional AWSCredentialsFileRegionProvider::getRegionSet() { absl::flat_hash_map elements = {{SIGV4A_SIGNING_REGION_SET, ""}}; absl::flat_hash_map::iterator it; - // Search for the region in the credentials file + std::string file_path, profile; + file_path = credential_file_path_.has_value() ? credential_file_path_.value() + : Utility::getCredentialFilePath(); - if (!Utility::resolveProfileElements(Utility::getCredentialFilePath(), - Utility::getCredentialProfileName(), elements)) { + profile = profile_.has_value() ? profile_.value() : Utility::getCredentialProfileName(); + + // Search for the region in the credentials file + if (!Utility::resolveProfileElementsFromFile(file_path, profile, elements)) { return absl::nullopt; } it = elements.find(SIGV4A_SIGNING_REGION_SET); @@ -86,8 +105,8 @@ absl::optional AWSConfigFileRegionProvider::getRegion() { // Search for the region in the config file - if (!Utility::resolveProfileElements(Utility::getConfigFilePath(), - Utility::getConfigProfileName(), elements)) { + if (!Utility::resolveProfileElementsFromFile(Utility::getConfigFilePath(), + Utility::getConfigProfileName(), elements)) { return absl::nullopt; } @@ -106,8 +125,8 @@ absl::optional AWSConfigFileRegionProvider::getRegionSet() { // Search for the region in the config file - if (!Utility::resolveProfileElements(Utility::getConfigFilePath(), - Utility::getConfigProfileName(), elements)) { + if (!Utility::resolveProfileElementsFromFile(Utility::getConfigFilePath(), + Utility::getConfigProfileName(), elements)) { return absl::nullopt; } @@ -133,10 +152,12 @@ absl::optional AWSConfigFileRegionProvider::getRegionSet() { // Credentials and profile format can be found here: // https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html // -RegionProviderChain::RegionProviderChain() { +RegionProviderChain::RegionProviderChain( + const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& + credential_file_config) { // TODO(nbaws): Verify that bypassing virtual dispatch here was intentional add(RegionProviderChain::createEnvironmentRegionProvider()); - add(RegionProviderChain::createAWSCredentialsFileRegionProvider()); + add(RegionProviderChain::createAWSCredentialsFileRegionProvider(credential_file_config)); add(RegionProviderChain::createAWSConfigFileRegionProvider()); } diff --git a/source/extensions/common/aws/region_provider_impl.h b/source/extensions/common/aws/region_provider_impl.h index b62deaa0a199..dae381f1cfb4 100644 --- a/source/extensions/common/aws/region_provider_impl.h +++ b/source/extensions/common/aws/region_provider_impl.h @@ -1,5 +1,7 @@ #pragma once +#include "envoy/extensions/common/aws/v3/credential_provider.pb.h" + #include "source/common/common/logger.h" #include "source/extensions/common/aws/region_provider.h" @@ -23,11 +25,17 @@ class EnvironmentRegionProvider : public RegionProvider, public Logger::Loggable class AWSCredentialsFileRegionProvider : public RegionProvider, public Logger::Loggable { public: - AWSCredentialsFileRegionProvider() = default; + AWSCredentialsFileRegionProvider( + const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& + credential_file_config); absl::optional getRegion() override; absl::optional getRegionSet() override; + +private: + absl::optional credential_file_path_; + absl::optional profile_; }; class AWSConfigFileRegionProvider : public RegionProvider, @@ -45,7 +53,9 @@ class RegionProviderChainFactories { virtual ~RegionProviderChainFactories() = default; virtual RegionProviderSharedPtr createEnvironmentRegionProvider() const PURE; - virtual RegionProviderSharedPtr createAWSCredentialsFileRegionProvider() const PURE; + virtual RegionProviderSharedPtr createAWSCredentialsFileRegionProvider( + const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& + credential_file_config) const PURE; virtual RegionProviderSharedPtr createAWSConfigFileRegionProvider() const PURE; }; @@ -57,7 +67,8 @@ class RegionProviderChain : public RegionProvider, public RegionProviderChainFactories, public Logger::Loggable { public: - RegionProviderChain(); + RegionProviderChain(const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& + credential_file_config = {}); ~RegionProviderChain() override = default; @@ -72,8 +83,10 @@ class RegionProviderChain : public RegionProvider, RegionProviderSharedPtr createEnvironmentRegionProvider() const override { return std::make_shared(); } - RegionProviderSharedPtr createAWSCredentialsFileRegionProvider() const override { - return std::make_shared(); + RegionProviderSharedPtr createAWSCredentialsFileRegionProvider( + const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& + credential_file_config) const override { + return std::make_shared(credential_file_config); } RegionProviderSharedPtr createAWSConfigFileRegionProvider() const override { return std::make_shared(); diff --git a/source/extensions/common/aws/utility.cc b/source/extensions/common/aws/utility.cc index 13ebbd36c934..b4642ad9cdfb 100644 --- a/source/extensions/common/aws/utility.cc +++ b/source/extensions/common/aws/utility.cc @@ -451,19 +451,38 @@ std::string Utility::getEnvironmentVariableOrDefault(const std::string& variable return (value != nullptr) && (value[0] != '\0') ? value : default_value; } -bool Utility::resolveProfileElements(const std::string& profile_file, - const std::string& profile_name, - absl::flat_hash_map& elements) { +bool Utility::resolveProfileElementsFromString( + const std::string& string_data, const std::string& profile_name, + absl::flat_hash_map& elements) { + std::unique_ptr stream; + + stream = std::make_unique(std::istringstream{string_data}); + return resolveProfileElementsFromStream(*stream, profile_name, elements); +} + +bool Utility::resolveProfileElementsFromFile( + const std::string& profile_file, const std::string& profile_name, + absl::flat_hash_map& elements) { std::ifstream file(profile_file); if (!file.is_open()) { ENVOY_LOG_MISC(debug, "Error opening credentials file {}", profile_file); return false; } + std::unique_ptr stream; + stream = std::make_unique(std::move(file)); + return resolveProfileElementsFromStream(*stream, profile_name, elements); +} + +bool Utility::resolveProfileElementsFromStream( + std::istream& stream, const std::string& profile_name, + absl::flat_hash_map& elements) { + const auto profile_start = absl::StrFormat("[%s]", profile_name); bool found_profile = false; std::string line; - while (std::getline(file, line)) { + + while (std::getline(stream, line)) { line = std::string(StringUtil::trim(line)); if (line.empty()) { continue; diff --git a/source/extensions/common/aws/utility.h b/source/extensions/common/aws/utility.h index 00e000f46a64..68685d24d53e 100644 --- a/source/extensions/common/aws/utility.h +++ b/source/extensions/common/aws/utility.h @@ -140,9 +140,18 @@ class Utility { * @return true if profile file could be read and searched. * @return false if profile file could not be read. */ - static bool resolveProfileElements(const std::string& profile_file, - const std::string& profile_name, - absl::flat_hash_map& elements); + + static bool + resolveProfileElementsFromString(const std::string& string_data, const std::string& profile_name, + absl::flat_hash_map& elements); + + static bool + resolveProfileElementsFromFile(const std::string& profile_file, const std::string& profile_name, + absl::flat_hash_map& elements); + + static bool + resolveProfileElementsFromStream(std::istream& stream, const std::string& profile_name, + absl::flat_hash_map& elements); /** * @brief Return the path of AWS credential file, following environment variable expansions diff --git a/source/extensions/filters/http/aws_lambda/config.cc b/source/extensions/filters/http/aws_lambda/config.cc index e23ea02e6643..90a0db569b0b 100644 --- a/source/extensions/filters/http/aws_lambda/config.cc +++ b/source/extensions/filters/http/aws_lambda/config.cc @@ -55,8 +55,10 @@ AwsLambdaFilterFactory::getCredentialsProvider( "credentials profile is set to \"{}\" in config, default credentials providers chain " "will be ignored and only credentials file provider will be used", proto_config.credentials_profile()); + envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider credential_file_config; + credential_file_config.set_profile(proto_config.credentials_profile()); return std::make_shared( - server_context.api(), proto_config.credentials_profile()); + server_context, credential_file_config); } return std::make_shared( server_context.api(), makeOptRef(server_context), server_context.singletonManager(), region, diff --git a/source/extensions/filters/http/aws_request_signing/config.cc b/source/extensions/filters/http/aws_request_signing/config.cc index 2d25b4376529..8e1fcc0ac2cc 100644 --- a/source/extensions/filters/http/aws_request_signing/config.cc +++ b/source/extensions/filters/http/aws_request_signing/config.cc @@ -36,66 +36,12 @@ AwsRequestSigningFilterFactory::createFilterFactoryFromProtoTyped( const AwsRequestSigningProtoConfig& config, const std::string& stats_prefix, DualInfo dual_info, Server::Configuration::ServerFactoryContext& server_context) { - std::string region; - region = config.region(); - - if (region.empty()) { - auto region_provider = std::make_shared(); - absl::optional regionOpt; - if (config.signing_algorithm() == AwsRequestSigning_SigningAlgorithm_AWS_SIGV4A) { - regionOpt = region_provider->getRegionSet(); - } else { - regionOpt = region_provider->getRegion(); - } - if (!regionOpt.has_value()) { - return absl::InvalidArgumentError( - "AWS region is not set in xDS configuration and failed to retrieve from " - "environment variable or AWS profile/config files."); - } - region = regionOpt.value(); - } - - bool query_string = config.has_query_string(); - - uint16_t expiration_time = PROTOBUF_GET_SECONDS_OR_DEFAULT( - config.query_string(), expiration_time, - Extensions::Common::Aws::SignatureQueryParameterValues::DefaultExpiration); - - absl::StatusOr - credentials_provider = - config.has_credential_provider() - ? Extensions::Common::Aws::createCredentialsProviderFromConfig( - server_context, region, config.credential_provider()) - : std::make_shared( - server_context.api(), makeOptRef(server_context), - server_context.singletonManager(), region, nullptr); - if (!credentials_provider.ok()) { - return credentials_provider.status(); + auto signer = createSigner(config, server_context); + if (!signer.ok()) { + return absl::InvalidArgumentError(std::string(signer.status().message())); } - - const auto matcher_config = Extensions::Common::Aws::AwsSigningHeaderExclusionVector( - config.match_excluded_headers().begin(), config.match_excluded_headers().end()); - - std::unique_ptr signer; - - if (config.signing_algorithm() == AwsRequestSigning_SigningAlgorithm_AWS_SIGV4A) { - signer = std::make_unique( - config.service_name(), region, credentials_provider.value(), server_context, matcher_config, - query_string, expiration_time); - } else { - // Verify that we have not specified a region set when using sigv4 algorithm - if (isARegionSet(region)) { - return absl::InvalidArgumentError( - "SigV4 region string cannot contain wildcards or commas. Region sets " - "can be specified when using signing_algorithm: AWS_SIGV4A."); - } - signer = std::make_unique( - config.service_name(), region, credentials_provider.value(), server_context, matcher_config, - query_string, expiration_time); - } - auto filter_config = - std::make_shared(std::move(signer), stats_prefix, dual_info.scope, + std::make_shared(std::move(signer.value()), stats_prefix, dual_info.scope, config.host_rewrite(), config.use_unsigned_payload()); return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) -> void { auto filter = std::make_shared(filter_config); @@ -106,16 +52,43 @@ AwsRequestSigningFilterFactory::createFilterFactoryFromProtoTyped( absl::StatusOr AwsRequestSigningFilterFactory::createRouteSpecificFilterConfigTyped( const AwsRequestSigningProtoPerRouteConfig& per_route_config, - Server::Configuration::ServerFactoryContext& context, ProtobufMessage::ValidationVisitor&) { - std::string region; + Server::Configuration::ServerFactoryContext& server_context, + ProtobufMessage::ValidationVisitor&) { + + auto signer = createSigner(per_route_config.aws_request_signing(), server_context); + if (!signer.ok()) { + return absl::InvalidArgumentError(std::string(signer.status().message())); + } + + return std::make_shared( + std::move(signer.value()), per_route_config.stat_prefix(), server_context.scope(), + per_route_config.aws_request_signing().host_rewrite(), + per_route_config.aws_request_signing().use_unsigned_payload()); +} - region = per_route_config.aws_request_signing().region(); +absl::StatusOr +AwsRequestSigningFilterFactory::createSigner( + const AwsRequestSigningProtoConfig& config, + Server::Configuration::ServerFactoryContext& server_context) { + + std::string region = config.region(); + + envoy::extensions::common::aws::v3::AwsCredentialProvider credential_provider_config = {}; + + // If we have an overriding credential provider configuration, read it here as it may contain + // references to the region + envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider credential_file_config = {}; + if (config.has_credential_provider()) { + if (config.credential_provider().has_credentials_file_provider()) { + credential_file_config = config.credential_provider().credentials_file_provider(); + } + } if (region.empty()) { - auto region_provider = std::make_shared(); + auto region_provider = + std::make_shared(credential_file_config); absl::optional regionOpt; - if (per_route_config.aws_request_signing().signing_algorithm() == - AwsRequestSigning_SigningAlgorithm_AWS_SIGV4A) { + if (config.signing_algorithm() == AwsRequestSigning_SigningAlgorithm_AWS_SIGV4A) { regionOpt = region_provider->getRegionSet(); } else { regionOpt = region_provider->getRegion(); @@ -128,32 +101,67 @@ AwsRequestSigningFilterFactory::createRouteSpecificFilterConfigTyped( region = regionOpt.value(); } - bool query_string = per_route_config.aws_request_signing().has_query_string(); - uint16_t expiration_time = PROTOBUF_GET_SECONDS_OR_DEFAULT( - per_route_config.aws_request_signing().query_string(), expiration_time, 5); - absl::StatusOr credentials_provider = - per_route_config.aws_request_signing().has_credential_provider() - ? Extensions::Common::Aws::createCredentialsProviderFromConfig( - context, region, per_route_config.aws_request_signing().credential_provider()) - : std::make_shared( - context.api(), makeOptRef(context), context.singletonManager(), region, - nullptr); + absl::InvalidArgumentError("No credentials provider settings configured."); + + const bool has_credential_provider_settings = + config.has_credential_provider() && + (config.credential_provider().has_assume_role_with_web_identity_provider() || + config.credential_provider().has_credentials_file_provider()); + + if (config.has_credential_provider()) { + if (config.credential_provider().has_inline_credential()) { + // If inline credential provider is set, use it instead of the default or custom credentials + // chain + const auto& inline_credential = config.credential_provider().inline_credential(); + credentials_provider = std::make_shared( + inline_credential.access_key_id(), inline_credential.secret_access_key(), + inline_credential.session_token()); + } else if (config.credential_provider().custom_credential_provider_chain()) { + // Custom credential provider chain + if (has_credential_provider_settings) { + credentials_provider = + std::make_shared( + server_context, region, config.credential_provider()); + } + } else { + // Override default credential provider chain settings with any provided settings + if (has_credential_provider_settings) { + credential_provider_config = config.credential_provider(); + } + credentials_provider = + std::make_shared( + server_context.api(), makeOptRef(server_context), server_context.singletonManager(), + region, nullptr, credential_provider_config); + } + } else { + // No credential provider settings provided, so make the default credentials provider chain + credentials_provider = + std::make_shared( + server_context.api(), makeOptRef(server_context), server_context.singletonManager(), + region, nullptr, credential_provider_config); + } + if (!credentials_provider.ok()) { return absl::InvalidArgumentError(std::string(credentials_provider.status().message())); } const auto matcher_config = Extensions::Common::Aws::AwsSigningHeaderExclusionVector( - per_route_config.aws_request_signing().match_excluded_headers().begin(), - per_route_config.aws_request_signing().match_excluded_headers().end()); + config.match_excluded_headers().begin(), config.match_excluded_headers().end()); + + const bool query_string = config.has_query_string(); + + const uint16_t expiration_time = PROTOBUF_GET_SECONDS_OR_DEFAULT( + config.query_string(), expiration_time, + Extensions::Common::Aws::SignatureQueryParameterValues::DefaultExpiration); + std::unique_ptr signer; - if (per_route_config.aws_request_signing().signing_algorithm() == - AwsRequestSigning_SigningAlgorithm_AWS_SIGV4A) { - signer = std::make_unique( - per_route_config.aws_request_signing().service_name(), region, credentials_provider.value(), - context, matcher_config, query_string, expiration_time); + if (config.signing_algorithm() == AwsRequestSigning_SigningAlgorithm_AWS_SIGV4A) { + return std::make_unique( + config.service_name(), region, credentials_provider.value(), server_context, matcher_config, + query_string, expiration_time); } else { // Verify that we have not specified a region set when using sigv4 algorithm if (isARegionSet(region)) { @@ -161,15 +169,10 @@ AwsRequestSigningFilterFactory::createRouteSpecificFilterConfigTyped( "SigV4 region string cannot contain wildcards or commas. Region sets " "can be specified when using signing_algorithm: AWS_SIGV4A."); } - signer = std::make_unique( - per_route_config.aws_request_signing().service_name(), region, credentials_provider.value(), - context, matcher_config, query_string, expiration_time); + return std::make_unique( + config.service_name(), region, credentials_provider.value(), server_context, matcher_config, + query_string, expiration_time); } - - return std::make_shared( - std::move(signer), per_route_config.stat_prefix(), context.scope(), - per_route_config.aws_request_signing().host_rewrite(), - per_route_config.aws_request_signing().use_unsigned_payload()); } /** diff --git a/source/extensions/filters/http/aws_request_signing/config.h b/source/extensions/filters/http/aws_request_signing/config.h index 62805bc468a6..48c3d40f6dd5 100644 --- a/source/extensions/filters/http/aws_request_signing/config.h +++ b/source/extensions/filters/http/aws_request_signing/config.h @@ -3,6 +3,7 @@ #include "envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.pb.h" #include "envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.pb.validate.h" +#include "source/extensions/common/aws/signer.h" #include "source/extensions/filters/http/common/factory_base.h" namespace Envoy { @@ -35,6 +36,10 @@ class AwsRequestSigningFilterFactory createRouteSpecificFilterConfigTyped(const AwsRequestSigningProtoPerRouteConfig& per_route_config, Server::Configuration::ServerFactoryContext& context, ProtobufMessage::ValidationVisitor&) override; + + absl::StatusOr + createSigner(const AwsRequestSigningProtoConfig& config, + Server::Configuration::ServerFactoryContext& server_context); }; using UpstreamAwsRequestSigningFilterFactory = AwsRequestSigningFilterFactory; diff --git a/test/extensions/common/aws/credentials_provider_impl_test.cc b/test/extensions/common/aws/credentials_provider_impl_test.cc index a25fcd165b6e..8283fc5a63ab 100644 --- a/test/extensions/common/aws/credentials_provider_impl_test.cc +++ b/test/extensions/common/aws/credentials_provider_impl_test.cc @@ -29,7 +29,7 @@ using testing::InSequence; using testing::NiceMock; using testing::Ref; using testing::Return; - +using testing::WithArg; namespace Envoy { namespace Extensions { namespace Common { @@ -175,13 +175,15 @@ TEST_F(EvironmentCredentialsProviderTest, NoSessionToken) { class CredentialsFileCredentialsProviderTest : public testing::Test { public: CredentialsFileCredentialsProviderTest() - : api_(Api::createApiForTest(time_system_)), provider_(*api_) {} + : api_(Api::createApiForTest(time_system_)), provider_(context_) {} ~CredentialsFileCredentialsProviderTest() override { TestEnvironment::unsetEnvVar("AWS_SHARED_CREDENTIALS_FILE"); TestEnvironment::unsetEnvVar("AWS_PROFILE"); } + void SetUp() override { EXPECT_CALL(context_, api()).WillRepeatedly(testing::ReturnRef(*api_)); } + void setUpTest(std::string file_contents, std::string profile) { auto file_path = TestEnvironment::writeStringToFileForTest(CREDENTIALS_FILE, file_contents); TestEnvironment::setEnvVar("AWS_SHARED_CREDENTIALS_FILE", file_path, 1); @@ -189,7 +191,11 @@ class CredentialsFileCredentialsProviderTest : public testing::Test { } Event::SimulatedTimeSystem time_system_; + NiceMock context_; + Api::ApiPtr api_; + // Event::DispatcherPtr dispatcher_; + // NiceMock tls_; CredentialsFileCredentialsProvider provider_; }; @@ -197,8 +203,37 @@ TEST_F(CredentialsFileCredentialsProviderTest, CustomProfileFromConfigShouldBeHo auto file_path = TestEnvironment::writeStringToFileForTest(CREDENTIALS_FILE, CREDENTIALS_FILE_CONTENTS); TestEnvironment::setEnvVar("AWS_SHARED_CREDENTIALS_FILE", file_path, 1); + envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider config = {}; + config.set_profile("profile4"); + auto provider = CredentialsFileCredentialsProvider(context_, config); + const auto credentials = provider.getCredentials(); + EXPECT_EQ("profile4_access_key", credentials.accessKeyId().value()); + EXPECT_EQ("profile4_secret", credentials.secretAccessKey().value()); + EXPECT_EQ("profile4_token", credentials.sessionToken().value()); +} - auto provider = CredentialsFileCredentialsProvider(*api_, "profile4"); +TEST_F(CredentialsFileCredentialsProviderTest, CustomFilePathFromConfig) { + auto file_path = + TestEnvironment::writeStringToFileForTest(CREDENTIALS_FILE, CREDENTIALS_FILE_CONTENTS); + + envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider config = {}; + config.mutable_credentials_data_source()->set_filename(file_path); + auto provider = CredentialsFileCredentialsProvider(context_, config); + const auto credentials = provider.getCredentials(); + EXPECT_EQ("default_access_key", credentials.accessKeyId().value()); + EXPECT_EQ("default_secret", credentials.secretAccessKey().value()); + EXPECT_EQ("default_token", credentials.sessionToken().value()); +} + +TEST_F(CredentialsFileCredentialsProviderTest, CustomFilePathAndProfileFromConfig) { + auto file_path = + TestEnvironment::writeStringToFileForTest(CREDENTIALS_FILE, CREDENTIALS_FILE_CONTENTS); + + envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider config = {}; + config.mutable_credentials_data_source()->set_filename(file_path); + config.set_profile("profile4"); + + auto provider = CredentialsFileCredentialsProvider(context_, config); const auto credentials = provider.getCredentials(); EXPECT_EQ("profile4_access_key", credentials.accessKeyId().value()); EXPECT_EQ("profile4_secret", credentials.secretAccessKey().value()); @@ -210,7 +245,10 @@ TEST_F(CredentialsFileCredentialsProviderTest, UnexistingCustomProfileFomConfig) TestEnvironment::writeStringToFileForTest(CREDENTIALS_FILE, CREDENTIALS_FILE_CONTENTS); TestEnvironment::setEnvVar("AWS_SHARED_CREDENTIALS_FILE", file_path, 1); - auto provider = CredentialsFileCredentialsProvider(*api_, "unexistening_profile"); + envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider config = {}; + config.set_profile("unexistening_profile"); + + auto provider = CredentialsFileCredentialsProvider(context_, config); const auto credentials = provider.getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); @@ -1945,17 +1983,26 @@ class WebIdentityCredentialsProviderTest : public testing::Test { std::chrono::seconds initialization_timer = std::chrono::seconds(2)) { ON_CALL(context_, clusterManager()).WillByDefault(ReturnRef(cluster_manager_)); std::string token_file_path; + envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider cred_provider = + {}; + if (token_.empty()) { token_file_path = TestEnvironment::writeStringToFileForTest("web_token_file", "web_token"); + cred_provider.mutable_web_identity_token_data_source()->set_inline_string("web_token"); + } else { + cred_provider.mutable_web_identity_token_data_source()->set_inline_string(token_); } + cred_provider.set_role_arn("aws:iam::123456789012:role/arn"); + cred_provider.set_role_session_name("role-session-name"); + provider_ = std::make_shared( - *api_, context_, nullptr, + context_, [this](Upstream::ClusterManager&, absl::string_view) { metadata_fetcher_.reset(raw_metadata_fetcher_); return std::move(metadata_fetcher_); }, - token_file_path, token_, "sts.region.amazonaws.com:443", "aws:iam::123456789012:role/arn", - "role-session-name", refresh_state, initialization_timer, "credentials_provider_cluster"); + "sts.region.amazonaws.com:443", refresh_state, initialization_timer, cred_provider, + "credentials_provider_cluster"); } void @@ -1975,18 +2022,27 @@ class WebIdentityCredentialsProviderTest : public testing::Test { MetadataFetcher::MetadataReceiver::RefreshState::Ready, std::chrono::seconds initialization_timer = std::chrono::seconds(2)) { std::string token_file_path; + envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider cred_provider = + {}; + if (token_.empty()) { token_file_path = TestEnvironment::writeStringToFileForTest("web_token_file", "web_token"); + cred_provider.mutable_web_identity_token_data_source()->set_inline_string("web_token"); + } else { + cred_provider.mutable_web_identity_token_data_source()->set_inline_string(token_); } + cred_provider.set_role_arn("aws:iam::123456789012:role/arn"); + cred_provider.set_role_session_name("role-session-name"); + ON_CALL(context_, clusterManager()).WillByDefault(ReturnRef(cluster_manager_)); provider_ = std::make_shared( - *api_, context_, nullptr, + context_, [this](Upstream::ClusterManager&, absl::string_view) { metadata_fetcher_.reset(raw_metadata_fetcher_); return std::move(metadata_fetcher_); }, - token_file_path, token_, "sts.region.amazonaws.com:443", "aws:iam::123456789012:role/arn", - "role-session-name", refresh_state, initialization_timer, "credentials_provider_cluster"); + "sts.region.amazonaws.com:443", refresh_state, initialization_timer, cred_provider, + "credentials_provider_cluster"); } void expectDocument(const uint64_t status_code, const std::string&& document) { @@ -2435,6 +2491,69 @@ TEST_F(WebIdentityCredentialsProviderTest, LibcurlEnabled) { metadata_fetcher_.reset(raw_metadata_fetcher_); } +class MockCredentialsProviderChainFactories : public CredentialsProviderChainFactories { +public: + MOCK_METHOD(CredentialsProviderSharedPtr, createEnvironmentCredentialsProvider, (), (const)); + MOCK_METHOD( + CredentialsProviderSharedPtr, mockCreateCredentialsFileCredentialsProvider, + (Server::Configuration::ServerFactoryContext&, + (const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& config)), + (const)); + + CredentialsProviderSharedPtr createCredentialsFileCredentialsProvider( + Server::Configuration::ServerFactoryContext& context, + const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& config) + const override { + return mockCreateCredentialsFileCredentialsProvider(context, config); + } + + MOCK_METHOD( + CredentialsProviderSharedPtr, createWebIdentityCredentialsProvider, + (Server::Configuration::ServerFactoryContext&, CreateMetadataFetcherCb, absl::string_view, + MetadataFetcher::MetadataReceiver::RefreshState, std::chrono::seconds, + const envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider&, + absl::string_view), + (const)); + + MOCK_METHOD(CredentialsProviderSharedPtr, createContainerCredentialsProvider, + (Api::Api&, ServerFactoryContextOptRef, Singleton::Manager&, + const MetadataCredentialsProviderBase::CurlMetadataFetcher&, CreateMetadataFetcherCb, + absl::string_view, absl::string_view, + MetadataFetcher::MetadataReceiver::RefreshState, std::chrono::seconds, + absl::string_view), + (const)); + MOCK_METHOD(CredentialsProviderSharedPtr, createInstanceProfileCredentialsProvider, + (Api::Api&, ServerFactoryContextOptRef, Singleton::Manager&, + const MetadataCredentialsProviderBase::CurlMetadataFetcher&, CreateMetadataFetcherCb, + MetadataFetcher::MetadataReceiver::RefreshState, std::chrono::seconds, + absl::string_view), + (const)); +}; + +class MockCustomCredentialsProviderChainFactories : public CustomCredentialsProviderChainFactories { +public: + MOCK_METHOD( + CredentialsProviderSharedPtr, mockCreateCredentialsFileCredentialsProvider, + (Server::Configuration::ServerFactoryContext&, + (const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& config)), + (const)); + + CredentialsProviderSharedPtr createCredentialsFileCredentialsProvider( + Server::Configuration::ServerFactoryContext& context, + const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& config) + const override { + return mockCreateCredentialsFileCredentialsProvider(context, config); + } + + MOCK_METHOD( + CredentialsProviderSharedPtr, createWebIdentityCredentialsProvider, + (Server::Configuration::ServerFactoryContext&, CreateMetadataFetcherCb, absl::string_view, + MetadataFetcher::MetadataReceiver::RefreshState, std::chrono::seconds, + const envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider&, + absl::string_view), + (const)); +}; + class DefaultCredentialsProviderChainTest : public testing::Test { public: DefaultCredentialsProviderChainTest() : api_(Api::createApiForTest(time_system_)) { @@ -2455,33 +2574,6 @@ class DefaultCredentialsProviderChainTest : public testing::Test { TestEnvironment::unsetEnvVar("AWS_ROLE_SESSION_NAME"); } - class MockCredentialsProviderChainFactories : public CredentialsProviderChainFactories { - public: - MOCK_METHOD(CredentialsProviderSharedPtr, createEnvironmentCredentialsProvider, (), (const)); - MOCK_METHOD(CredentialsProviderSharedPtr, createCredentialsFileCredentialsProvider, (Api::Api&), - (const)); - MOCK_METHOD(CredentialsProviderSharedPtr, createWebIdentityCredentialsProvider, - (Api::Api&, ServerFactoryContextOptRef, - const MetadataCredentialsProviderBase::CurlMetadataFetcher&, - CreateMetadataFetcherCb, absl::string_view, absl::string_view, absl::string_view, - absl::string_view, absl::string_view, absl::string_view, - MetadataFetcher::MetadataReceiver::RefreshState, std::chrono::seconds), - (const)); - MOCK_METHOD(CredentialsProviderSharedPtr, createContainerCredentialsProvider, - (Api::Api&, ServerFactoryContextOptRef, Singleton::Manager&, - const MetadataCredentialsProviderBase::CurlMetadataFetcher&, - CreateMetadataFetcherCb, absl::string_view, absl::string_view, - MetadataFetcher::MetadataReceiver::RefreshState, std::chrono::seconds, - absl::string_view), - (const)); - MOCK_METHOD(CredentialsProviderSharedPtr, createInstanceProfileCredentialsProvider, - (Api::Api&, ServerFactoryContextOptRef, Singleton::Manager&, - const MetadataCredentialsProviderBase::CurlMetadataFetcher&, - CreateMetadataFetcherCb, MetadataFetcher::MetadataReceiver::RefreshState, - std::chrono::seconds, absl::string_view), - (const)); - }; - TestScopedRuntime scoped_runtime_; Event::SimulatedTimeSystem time_system_; Api::ApiPtr api_; @@ -2491,100 +2583,265 @@ class DefaultCredentialsProviderChainTest : public testing::Test { }; TEST_F(DefaultCredentialsProviderChainTest, NoEnvironmentVars) { - EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); + EXPECT_CALL(factories_, mockCreateCredentialsFileCredentialsProvider(Ref(context_), _)); EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _, _, _, _)); + envoy::extensions::common::aws::v3::AwsCredentialProvider credential_provider_config = {}; DefaultCredentialsProviderChain chain(*api_, context_, context_.singletonManager(), "region", - DummyMetadataFetcher(), factories_); + DummyMetadataFetcher(), credential_provider_config, + factories_); } TEST_F(DefaultCredentialsProviderChainTest, MetadataDisabled) { TestEnvironment::setEnvVar("AWS_EC2_METADATA_DISABLED", "true", 1); - EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); + EXPECT_CALL(factories_, mockCreateCredentialsFileCredentialsProvider(Ref(context_), _)); EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _, _, _, _)) .Times(0); + envoy::extensions::common::aws::v3::AwsCredentialProvider credential_provider_config = {}; + DefaultCredentialsProviderChain chain(*api_, context_, context_.singletonManager(), "region", - DummyMetadataFetcher(), factories_); + DummyMetadataFetcher(), credential_provider_config, + factories_); } TEST_F(DefaultCredentialsProviderChainTest, MetadataNotDisabled) { TestEnvironment::setEnvVar("AWS_EC2_METADATA_DISABLED", "false", 1); - EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); + EXPECT_CALL(factories_, mockCreateCredentialsFileCredentialsProvider(Ref(context_), _)); EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _, _, _, _)); + envoy::extensions::common::aws::v3::AwsCredentialProvider credential_provider_config = {}; + DefaultCredentialsProviderChain chain(*api_, context_, context_.singletonManager(), "region", - DummyMetadataFetcher(), factories_); + DummyMetadataFetcher(), credential_provider_config, + factories_); } TEST_F(DefaultCredentialsProviderChainTest, RelativeUri) { TestEnvironment::setEnvVar("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/path/to/creds", 1); - EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); + EXPECT_CALL(factories_, mockCreateCredentialsFileCredentialsProvider(Ref(context_), _)); EXPECT_CALL(factories_, createContainerCredentialsProvider(Ref(*api_), _, _, _, _, _, "169.254.170.2:80/path/to/creds", _, _, "")); + envoy::extensions::common::aws::v3::AwsCredentialProvider credential_provider_config = {}; + DefaultCredentialsProviderChain chain(*api_, context_, context_.singletonManager(), "region", - DummyMetadataFetcher(), factories_); + DummyMetadataFetcher(), credential_provider_config, + factories_); } TEST_F(DefaultCredentialsProviderChainTest, FullUriNoAuthorizationToken) { TestEnvironment::setEnvVar("AWS_CONTAINER_CREDENTIALS_FULL_URI", "http://host/path/to/creds", 1); - EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); + EXPECT_CALL(factories_, mockCreateCredentialsFileCredentialsProvider(Ref(context_), _)); EXPECT_CALL(factories_, createContainerCredentialsProvider( Ref(*api_), _, _, _, _, _, "http://host/path/to/creds", _, _, "")); + envoy::extensions::common::aws::v3::AwsCredentialProvider credential_provider_config = {}; + DefaultCredentialsProviderChain chain(*api_, context_, context_.singletonManager(), "region", - DummyMetadataFetcher(), factories_); + DummyMetadataFetcher(), credential_provider_config, + factories_); } TEST_F(DefaultCredentialsProviderChainTest, FullUriWithAuthorizationToken) { TestEnvironment::setEnvVar("AWS_CONTAINER_CREDENTIALS_FULL_URI", "http://host/path/to/creds", 1); TestEnvironment::setEnvVar("AWS_CONTAINER_AUTHORIZATION_TOKEN", "auth_token", 1); - EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); + EXPECT_CALL(factories_, mockCreateCredentialsFileCredentialsProvider(Ref(context_), _)); EXPECT_CALL(factories_, createContainerCredentialsProvider(Ref(*api_), _, _, _, _, _, "http://host/path/to/creds", _, _, "auth_token")); + envoy::extensions::common::aws::v3::AwsCredentialProvider credential_provider_config = {}; + DefaultCredentialsProviderChain chain(*api_, context_, context_.singletonManager(), "region", - DummyMetadataFetcher(), factories_); + DummyMetadataFetcher(), credential_provider_config, + factories_); } TEST_F(DefaultCredentialsProviderChainTest, NoWebIdentityRoleArn) { TestEnvironment::setEnvVar("AWS_WEB_IDENTITY_TOKEN_FILE", "/path/to/web_token", 1); - EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); + EXPECT_CALL(factories_, mockCreateCredentialsFileCredentialsProvider(Ref(context_), _)); EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _, _, _, _)); + envoy::extensions::common::aws::v3::AwsCredentialProvider credential_provider_config = {}; + DefaultCredentialsProviderChain chain(*api_, context_, context_.singletonManager(), "region", - DummyMetadataFetcher(), factories_); + DummyMetadataFetcher(), credential_provider_config, + factories_); } TEST_F(DefaultCredentialsProviderChainTest, NoWebIdentitySessionName) { TestEnvironment::setEnvVar("AWS_WEB_IDENTITY_TOKEN_FILE", "/path/to/web_token", 1); TestEnvironment::setEnvVar("AWS_ROLE_ARN", "aws:iam::123456789012:role/arn", 1); time_system_.setSystemTime(std::chrono::milliseconds(1234567890)); - EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); - EXPECT_CALL(factories_, - createWebIdentityCredentialsProvider( - Ref(*api_), _, _, _, _, "/path/to/web_token", _, "sts.region.amazonaws.com:443", - "aws:iam::123456789012:role/arn", "1234567890000000", _, _)); + EXPECT_CALL(factories_, mockCreateCredentialsFileCredentialsProvider(Ref(context_), _)); + EXPECT_CALL(factories_, createWebIdentityCredentialsProvider( + Ref(context_), _, "sts.region.amazonaws.com:443", _, _, _, _)); EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _, _, _, _)); + envoy::extensions::common::aws::v3::AwsCredentialProvider credential_provider_config = {}; DefaultCredentialsProviderChain chain(*api_, context_, context_.singletonManager(), "region", - DummyMetadataFetcher(), factories_); + DummyMetadataFetcher(), credential_provider_config, + factories_); } TEST_F(DefaultCredentialsProviderChainTest, WebIdentityWithSessionName) { TestEnvironment::setEnvVar("AWS_WEB_IDENTITY_TOKEN_FILE", "/path/to/web_token", 1); TestEnvironment::setEnvVar("AWS_ROLE_ARN", "aws:iam::123456789012:role/arn", 1); TestEnvironment::setEnvVar("AWS_ROLE_SESSION_NAME", "role-session-name", 1); - EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); + EXPECT_CALL(factories_, mockCreateCredentialsFileCredentialsProvider(Ref(context_), _)); + EXPECT_CALL(factories_, + createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _, _, _, _)); + EXPECT_CALL(factories_, createWebIdentityCredentialsProvider( + Ref(context_), _, "sts.region.amazonaws.com:443", _, _, _, _)); + + envoy::extensions::common::aws::v3::AwsCredentialProvider credential_provider_config = {}; + + DefaultCredentialsProviderChain chain(*api_, context_, context_.singletonManager(), "region", + DummyMetadataFetcher(), credential_provider_config, + factories_); +} + +TEST_F(DefaultCredentialsProviderChainTest, NoWebIdentityWithBlankConfig) { + TestEnvironment::unsetEnvVar("AWS_WEB_IDENTITY_TOKEN_FILE"); + TestEnvironment::unsetEnvVar("AWS_ROLE_ARN"); + EXPECT_CALL(factories_, mockCreateCredentialsFileCredentialsProvider(Ref(context_), _)); + EXPECT_CALL(factories_, + createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _, _, _, _)); + EXPECT_CALL(factories_, createWebIdentityCredentialsProvider( + Ref(context_), _, "sts.region.amazonaws.com:443", _, _, _, _)) + .Times(0); + + envoy::extensions::common::aws::v3::AwsCredentialProvider credential_provider_config = {}; + + DefaultCredentialsProviderChain chain(*api_, context_, context_.singletonManager(), "region", + DummyMetadataFetcher(), credential_provider_config, + factories_); +} +// These tests validate override of default credential provider with custom credential provider +// settings + +TEST_F(DefaultCredentialsProviderChainTest, WebIdentityWithCustomSessionName) { + TestEnvironment::setEnvVar("AWS_WEB_IDENTITY_TOKEN_FILE", "/path/to/web_token", 1); + TestEnvironment::setEnvVar("AWS_ROLE_ARN", "aws:iam::123456789012:role/arn", 1); + TestEnvironment::setEnvVar("AWS_ROLE_SESSION_NAME", "role-session-name", 1); + EXPECT_CALL(factories_, mockCreateCredentialsFileCredentialsProvider(Ref(context_), _)); + EXPECT_CALL(factories_, + createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _, _, _, _)); + + std::string role_session_name; + + EXPECT_CALL(factories_, createWebIdentityCredentialsProvider( + Ref(context_), _, "sts.region.amazonaws.com:443", _, _, _, _)) + .WillOnce(Invoke(WithArg<5>( + [&role_session_name]( + const envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider& + provider) -> CredentialsProviderSharedPtr { + role_session_name = provider.role_session_name(); + return nullptr; + }))); + + envoy::extensions::common::aws::v3::AwsCredentialProvider credential_provider_config = {}; + credential_provider_config.mutable_assume_role_with_web_identity_provider() + ->set_role_session_name("custom-role-session-name"); + + DefaultCredentialsProviderChain chain(*api_, context_, context_.singletonManager(), "region", + DummyMetadataFetcher(), credential_provider_config, + factories_); + EXPECT_EQ(role_session_name, "custom-role-session-name"); +} + +TEST_F(DefaultCredentialsProviderChainTest, WebIdentityWithCustomRoleArn) { + TestEnvironment::setEnvVar("AWS_WEB_IDENTITY_TOKEN_FILE", "/path/to/web_token", 1); + TestEnvironment::setEnvVar("AWS_ROLE_ARN", "aws:iam::123456789012:role/arn", 1); + TestEnvironment::setEnvVar("AWS_ROLE_SESSION_NAME", "role-session-name", 1); + EXPECT_CALL(factories_, mockCreateCredentialsFileCredentialsProvider(Ref(context_), _)); EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _, _, _, _)); + + std::string role_arn; + + EXPECT_CALL(factories_, createWebIdentityCredentialsProvider( + Ref(context_), _, "sts.region.amazonaws.com:443", _, _, _, _)) + .WillOnce(Invoke(WithArg<5>( + [&role_arn]( + const envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider& + provider) -> CredentialsProviderSharedPtr { + role_arn = provider.role_arn(); + return nullptr; + }))); + + envoy::extensions::common::aws::v3::AwsCredentialProvider credential_provider_config = {}; + credential_provider_config.mutable_assume_role_with_web_identity_provider()->set_role_arn( + "custom-role-arn"); + + DefaultCredentialsProviderChain chain(*api_, context_, context_.singletonManager(), "region", + DummyMetadataFetcher(), credential_provider_config, + factories_); + EXPECT_EQ(role_arn, "custom-role-arn"); +} + +TEST_F(DefaultCredentialsProviderChainTest, WebIdentityWithCustomDataSource) { + TestEnvironment::setEnvVar("AWS_WEB_IDENTITY_TOKEN_FILE", "/path/to/web_token", 1); + TestEnvironment::setEnvVar("AWS_ROLE_ARN", "aws:iam::123456789012:role/arn", 1); + TestEnvironment::setEnvVar("AWS_ROLE_SESSION_NAME", "role-session-name", 1); + EXPECT_CALL(factories_, mockCreateCredentialsFileCredentialsProvider(Ref(context_), _)); EXPECT_CALL(factories_, - createWebIdentityCredentialsProvider( - Ref(*api_), _, _, _, _, "/path/to/web_token", _, "sts.region.amazonaws.com:443", - "aws:iam::123456789012:role/arn", "role-session-name", _, _)); + createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _, _, _, _)); + + std::string inline_string; + + EXPECT_CALL(factories_, createWebIdentityCredentialsProvider( + Ref(context_), _, "sts.region.amazonaws.com:443", _, _, _, _)) + .WillOnce(Invoke(WithArg<5>( + [&inline_string]( + const envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider& + provider) -> CredentialsProviderSharedPtr { + inline_string = provider.web_identity_token_data_source().inline_string(); + return nullptr; + }))); + + envoy::extensions::common::aws::v3::AwsCredentialProvider credential_provider_config = {}; + credential_provider_config.mutable_assume_role_with_web_identity_provider() + ->mutable_web_identity_token_data_source() + ->set_inline_string("custom_token_string"); + DefaultCredentialsProviderChain chain(*api_, context_, context_.singletonManager(), "region", - DummyMetadataFetcher(), factories_); + DummyMetadataFetcher(), credential_provider_config, + factories_); + EXPECT_EQ(inline_string, "custom_token_string"); +} + +TEST_F(DefaultCredentialsProviderChainTest, CredentialsFileWithCustomDataSource) { + TestEnvironment::setEnvVar("AWS_WEB_IDENTITY_TOKEN_FILE", "/path/to/web_token", 1); + TestEnvironment::setEnvVar("AWS_ROLE_ARN", "aws:iam::123456789012:role/arn", 1); + TestEnvironment::setEnvVar("AWS_ROLE_SESSION_NAME", "role-session-name", 1); + + std::string inline_string; + + EXPECT_CALL(factories_, mockCreateCredentialsFileCredentialsProvider(Ref(context_), _)) + .WillOnce(Invoke(WithArg<1>( + [&inline_string]( + const envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider& provider) + -> CredentialsProviderSharedPtr { + inline_string = provider.credentials_data_source().inline_string(); + return nullptr; + }))); + + EXPECT_CALL(factories_, + createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _, _, _, _)); + + EXPECT_CALL(factories_, createWebIdentityCredentialsProvider( + Ref(context_), _, "sts.region.amazonaws.com:443", _, _, _, _)); + + envoy::extensions::common::aws::v3::AwsCredentialProvider credential_provider_config = {}; + credential_provider_config.mutable_credentials_file_provider() + ->mutable_credentials_data_source() + ->set_inline_string("custom_inline_string"); + + DefaultCredentialsProviderChain chain(*api_, context_, context_.singletonManager(), "region", + DummyMetadataFetcher(), credential_provider_config, + factories_); + EXPECT_EQ(inline_string, "custom_inline_string"); } TEST(CredentialsProviderChainTest, getCredentials_noCredentials) { @@ -2636,6 +2893,72 @@ TEST(CredentialsProviderChainTest, getCredentials_secondProviderReturns) { EXPECT_EQ(creds, ret_creds); } +class CustomCredentialsProviderChainTest : public testing::Test {}; + +TEST_F(CustomCredentialsProviderChainTest, CreateFileCredentialProviderOnly) { + NiceMock factories; + NiceMock server_context; + auto region = "ap-southeast-2"; + auto file_path = TestEnvironment::writeStringToFileForTest("credentials", "hello"); + + envoy::extensions::common::aws::v3::AwsCredentialProvider cred_provider = {}; + cred_provider.mutable_credentials_file_provider() + ->mutable_credentials_data_source() + ->set_filename(file_path); + + EXPECT_CALL(factories, mockCreateCredentialsFileCredentialsProvider(Ref(server_context), _)); + EXPECT_CALL(factories, + createWebIdentityCredentialsProvider(Ref(server_context), _, _, _, _, _, _)) + .Times(0); + + auto chain = std::make_shared( + server_context, region, cred_provider, factories); +} + +TEST_F(CustomCredentialsProviderChainTest, CreateWebIdentityCredentialProviderOnly) { + NiceMock factories; + NiceMock server_context; + auto region = "ap-southeast-2"; + auto file_path = TestEnvironment::writeStringToFileForTest("credentials", "hello"); + + envoy::extensions::common::aws::v3::AwsCredentialProvider cred_provider = {}; + cred_provider.mutable_assume_role_with_web_identity_provider()->set_role_arn("arn://1234"); + cred_provider.mutable_assume_role_with_web_identity_provider() + ->mutable_web_identity_token_data_source() + ->set_filename(file_path); + + EXPECT_CALL(factories, mockCreateCredentialsFileCredentialsProvider(Ref(server_context), _)) + .Times(0); + EXPECT_CALL(factories, + createWebIdentityCredentialsProvider(Ref(server_context), _, _, _, _, _, _)); + + auto chain = std::make_shared( + server_context, region, cred_provider, factories); +} + +TEST_F(CustomCredentialsProviderChainTest, CreateFileAndWebProviders) { + NiceMock factories; + NiceMock server_context; + auto region = "ap-southeast-2"; + auto file_path = TestEnvironment::writeStringToFileForTest("credentials", "hello"); + + envoy::extensions::common::aws::v3::AwsCredentialProvider cred_provider = {}; + cred_provider.mutable_credentials_file_provider() + ->mutable_credentials_data_source() + ->set_filename(file_path); + cred_provider.mutable_assume_role_with_web_identity_provider()->set_role_arn("arn://1234"); + cred_provider.mutable_assume_role_with_web_identity_provider() + ->mutable_web_identity_token_data_source() + ->set_filename(file_path); + + EXPECT_CALL(factories, mockCreateCredentialsFileCredentialsProvider(Ref(server_context), _)); + EXPECT_CALL(factories, + createWebIdentityCredentialsProvider(Ref(server_context), _, _, _, _, _, _)); + + auto chain = std::make_shared( + server_context, region, cred_provider, factories); +} + TEST(CreateCredentialsProviderFromConfig, InlineCredential) { NiceMock context; envoy::extensions::common::aws::v3::InlineCredentialProvider inline_credential; @@ -2646,49 +2969,15 @@ TEST(CreateCredentialsProviderFromConfig, InlineCredential) { envoy::extensions::common::aws::v3::AwsCredentialProvider base; base.mutable_inline_credential()->CopyFrom(inline_credential); - absl::StatusOr provider = - createCredentialsProviderFromConfig(context, "test-region", base); - EXPECT_TRUE(provider.ok()); - EXPECT_NE(nullptr, provider.value()); - const Credentials creds = provider.value()->getCredentials(); + auto provider = std::make_shared( + inline_credential.access_key_id(), inline_credential.secret_access_key(), + inline_credential.session_token()); + const Credentials creds = provider->getCredentials(); EXPECT_EQ("TestAccessKey", creds.accessKeyId().value()); EXPECT_EQ("TestSecret", creds.secretAccessKey().value()); EXPECT_EQ("TestSessionToken", creds.sessionToken().value()); } -TEST(CreateCredentialsProviderFromConfig, AssumeRoleWithWebIdentity) { - NiceMock context; - envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider - assume_role_provider; - assume_role_provider.set_role_arn("arn:aws:iam::123456789012:role/role-name"); - assume_role_provider.set_web_identity_token("this-is-a-token"); - - envoy::extensions::common::aws::v3::AwsCredentialProvider base; - base.mutable_assume_role_with_web_identity()->CopyFrom(assume_role_provider); - - absl::StatusOr provider = - createCredentialsProviderFromConfig(context, "test-region", base); - EXPECT_TRUE(provider.ok()); - EXPECT_NE(nullptr, provider.value()); - - const auto* web_identity_provider = - dynamic_cast(provider.value().get()); - EXPECT_NE(nullptr, web_identity_provider); - - const std::string& token = web_identity_provider->tokenForTesting(); - const std::string& role_arn = web_identity_provider->roleArnForTesting(); - EXPECT_EQ("this-is-a-token", token); - EXPECT_EQ("arn:aws:iam::123456789012:role/role-name", role_arn); -} - -TEST(CreateCredentialsProviderFromConfig, InvalidEnum) { - NiceMock context; - envoy::extensions::common::aws::v3::AwsCredentialProvider base; - absl::StatusOr result = - createCredentialsProviderFromConfig(context, "foo", base); - EXPECT_FALSE(result.ok()); -} - } // namespace Aws } // namespace Common } // namespace Extensions diff --git a/test/extensions/common/aws/region_provider_impl_test.cc b/test/extensions/common/aws/region_provider_impl_test.cc index 1ce717d2d8cd..82cf24380e06 100644 --- a/test/extensions/common/aws/region_provider_impl_test.cc +++ b/test/extensions/common/aws/region_provider_impl_test.cc @@ -39,8 +39,6 @@ class EnvironmentRegionProviderTest : public testing::Test { class AWSCredentialsFileRegionProviderTest : public testing::Test { public: void SetUp() override { setupEnvironment(); } - - AWSCredentialsFileRegionProvider provider_; }; class AWSConfigFileRegionProviderTest : public testing::Test { @@ -203,6 +201,7 @@ TEST_F(AWSConfigFileRegionProviderTest, NoRegionSet) { EXPECT_EQ(false, provider_.getRegionSet().has_value()); } + TEST_F(AWSCredentialsFileRegionProviderTest, CustomCredentialsFile) { auto temp = TestEnvironment::temporaryDirectory(); TestEnvironment::setEnvVar("HOME", temp, 1); @@ -213,8 +212,23 @@ TEST_F(AWSCredentialsFileRegionProviderTest, CustomCredentialsFile) { credentials_file, CREDENTIALS_FILE_CONTENTS, true, false); TestEnvironment::setEnvVar("AWS_SHARED_CREDENTIALS_FILE", credentials_file, 1); + auto provider = AWSCredentialsFileRegionProvider({}); + EXPECT_EQ("credentialsdefaultregion", provider.getRegion().value()); +} + +TEST_F(AWSCredentialsFileRegionProviderTest, CustomCredentialsFileViaCredentialProviderConfig) { + auto temp = TestEnvironment::temporaryDirectory(); + TestEnvironment::setEnvVar("HOME", temp, 1); + std::filesystem::create_directory(temp + "/.aws"); + std::string credentials_file(temp + "/.aws/customfile"); - EXPECT_EQ("credentialsdefaultregion", provider_.getRegion().value()); + auto file_path = TestEnvironment::writeStringToFileForTest( + credentials_file, CREDENTIALS_FILE_CONTENTS, true, false); + + envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider credential_file_config; + credential_file_config.mutable_credentials_data_source()->set_filename(credentials_file); + auto provider = AWSCredentialsFileRegionProvider(credential_file_config); + EXPECT_EQ("credentialsdefaultregion", provider.getRegion().value()); } TEST_F(AWSCredentialsFileRegionProviderTest, CustomCredentialsFileRegionSet) { @@ -227,8 +241,26 @@ TEST_F(AWSCredentialsFileRegionProviderTest, CustomCredentialsFileRegionSet) { credentials_file, CREDENTIALS_FILE_CONTENTS_REGION_SET, true, false); TestEnvironment::setEnvVar("AWS_SHARED_CREDENTIALS_FILE", credentials_file, 1); + auto provider = AWSCredentialsFileRegionProvider({}); - EXPECT_EQ("*", provider_.getRegionSet().value()); + EXPECT_EQ("*", provider.getRegionSet().value()); +} + +TEST_F(AWSCredentialsFileRegionProviderTest, + CustomCredentialsFileRegionSetViaCredentialProviderConfig) { + auto temp = TestEnvironment::temporaryDirectory(); + TestEnvironment::setEnvVar("HOME", temp, 1); + std::filesystem::create_directory(temp + "/.aws"); + std::string credentials_file(temp + "/.aws/customfile"); + + auto file_path = TestEnvironment::writeStringToFileForTest( + credentials_file, CREDENTIALS_FILE_CONTENTS_REGION_SET, true, false); + + envoy::extensions::common::aws::v3::CredentialsFileCredentialProvider credential_file_config; + credential_file_config.mutable_credentials_data_source()->set_filename(credentials_file); + auto provider = AWSCredentialsFileRegionProvider(credential_file_config); + + EXPECT_EQ("*", provider.getRegionSet().value()); } TEST_F(AWSCredentialsFileRegionProviderTest, CustomProfileSharedCredentialsFile) { @@ -242,8 +274,8 @@ TEST_F(AWSCredentialsFileRegionProviderTest, CustomProfileSharedCredentialsFile) TestEnvironment::setEnvVar("AWS_SHARED_CREDENTIALS_FILE", credentials_file, 1); TestEnvironment::setEnvVar("AWS_PROFILE", "profile1", 1); - - EXPECT_EQ("profile1region", provider_.getRegion().value()); + auto provider = AWSCredentialsFileRegionProvider({}); + EXPECT_EQ("profile1region", provider.getRegion().value()); } TEST_F(AWSCredentialsFileRegionProviderTest, CustomProfileSharedCredentialsFileRegionSet) { @@ -257,8 +289,9 @@ TEST_F(AWSCredentialsFileRegionProviderTest, CustomProfileSharedCredentialsFileR TestEnvironment::setEnvVar("AWS_SHARED_CREDENTIALS_FILE", credentials_file, 1); TestEnvironment::setEnvVar("AWS_PROFILE", "profile1", 1); + auto provider = AWSCredentialsFileRegionProvider({}); - EXPECT_EQ("us-east-1,us-east-2", provider_.getRegionSet().value()); + EXPECT_EQ("us-east-1,us-east-2", provider.getRegionSet().value()); } TEST_F(AWSCredentialsFileRegionProviderTest, NoRegion) { @@ -269,8 +302,8 @@ TEST_F(AWSCredentialsFileRegionProviderTest, NoRegion) { auto file_path = TestEnvironment::writeStringToFileForTest( credentials_file, CREDENTIALS_FILE_NO_REGION, true, false); - - EXPECT_EQ(false, provider_.getRegion().has_value()); + auto provider = AWSCredentialsFileRegionProvider({}); + EXPECT_EQ(false, provider.getRegion().has_value()); } TEST_F(AWSCredentialsFileRegionProviderTest, NoRegionSet) { @@ -281,8 +314,8 @@ TEST_F(AWSCredentialsFileRegionProviderTest, NoRegionSet) { auto file_path = TestEnvironment::writeStringToFileForTest( credentials_file, CREDENTIALS_FILE_NO_REGION, true, false); - - EXPECT_EQ(false, provider_.getRegionSet().has_value()); + auto provider = AWSCredentialsFileRegionProvider({}); + EXPECT_EQ(false, provider.getRegionSet().has_value()); } TEST_F(RegionProviderChainTest, EnvironmentBeforeCredentialsFile) { diff --git a/test/extensions/common/aws/utility_test.cc b/test/extensions/common/aws/utility_test.cc index 6cabe0ebd79e..0dac68522db5 100644 --- a/test/extensions/common/aws/utility_test.cc +++ b/test/extensions/common/aws/utility_test.cc @@ -66,10 +66,10 @@ TEST(UtilityTest, TestProfileResolver) { auto file_path = TestEnvironment::writeStringToFileForTest( credential_file, CREDENTIALS_FILE_CONTENTS, true, false); - Utility::resolveProfileElements(file_path, "default", elements); + Utility::resolveProfileElementsFromFile(file_path, "default", elements); it = elements.find("AWS_ACCESS_KEY_ID"); EXPECT_EQ(it->second, "default_access_key"); - Utility::resolveProfileElements(file_path, "profile4", elements); + Utility::resolveProfileElementsFromFile(file_path, "profile4", elements); it = elements.find("AWS_ACCESS_KEY_ID"); EXPECT_EQ(it->second, "profile4_access_key"); } diff --git a/test/extensions/filters/http/aws_request_signing/config_test.cc b/test/extensions/filters/http/aws_request_signing/config_test.cc index f007730ebcd9..0e79aad0830c 100644 --- a/test/extensions/filters/http/aws_request_signing/config_test.cc +++ b/test/extensions/filters/http/aws_request_signing/config_test.cc @@ -96,8 +96,9 @@ TEST(AwsRequestSigningFilterConfigTest, CredentialProvider_assume_role_web_ident service_name: s3 region: us-west-2 credential_provider: - assume_role_with_web_identity: - web_identity_token: this-is-token + assume_role_with_web_identity_provider: + web_identity_token_data_source: + inline_string: this-is-token role_arn: arn:aws:iam::123456789012:role/role-name )EOF"; @@ -107,9 +108,9 @@ region: us-west-2 AwsRequestSigningProtoConfig expected_config; expected_config.set_service_name("s3"); expected_config.set_region("us-west-2"); - auto credential_provider = - expected_config.mutable_credential_provider()->mutable_assume_role_with_web_identity(); - credential_provider->set_web_identity_token("this-is-token"); + auto credential_provider = expected_config.mutable_credential_provider() + ->mutable_assume_role_with_web_identity_provider(); + credential_provider->mutable_web_identity_token_data_source()->set_inline_string("this-is-token"); credential_provider->set_role_arn("arn:aws:iam::123456789012:role/role-name"); Protobuf::util::MessageDifferencer differencer; @@ -127,11 +128,90 @@ region: us-west-2 cb(filter_callbacks); } +TEST(AwsRequestSigningFilterConfigTest, CredentialProvider_credential_file) { + const std::string yaml = R"EOF( +service_name: s3 +region: us-west-2 +credential_provider: + credentials_file_provider: + profile: profile1 + credentials_data_source: + filename: this-is-filename + )EOF"; + + AwsRequestSigningProtoConfig proto_config; + TestUtility::loadFromYamlAndValidate(yaml, proto_config); + + AwsRequestSigningProtoConfig expected_config; + expected_config.set_service_name("s3"); + expected_config.set_region("us-west-2"); + auto credential_provider = + expected_config.mutable_credential_provider()->mutable_credentials_file_provider(); + credential_provider->mutable_credentials_data_source()->set_filename("this-is-filename"); + credential_provider->set_profile("profile1"); + + Protobuf::util::MessageDifferencer differencer; + differencer.set_message_field_comparison(Protobuf::util::MessageDifferencer::EQUAL); + differencer.set_repeated_field_comparison(Protobuf::util::MessageDifferencer::AS_SET); + EXPECT_TRUE(differencer.Compare(expected_config, proto_config)); + + testing::NiceMock context; + AwsRequestSigningFilterFactory factory; + + Http::FilterFactoryCb cb = + factory.createFilterFactoryFromProto(proto_config, "stats", context).value(); + Http::MockFilterChainFactoryCallbacks filter_callbacks; + EXPECT_CALL(filter_callbacks, addStreamDecoderFilter(_)); + cb(filter_callbacks); +} + +TEST(AwsRequestSigningFilterConfigTest, CredentialProvider_credential_file_watched_dir) { + const std::string yaml = R"EOF( +service_name: s3 +region: us-west-2 +credential_provider: + credentials_file_provider: + profile: profile5 + credentials_data_source: + filename: this-is-filename + watched_directory: + path: /tmp + )EOF"; + + AwsRequestSigningProtoConfig proto_config; + TestUtility::loadFromYamlAndValidate(yaml, proto_config); + + AwsRequestSigningProtoConfig expected_config; + expected_config.set_service_name("s3"); + expected_config.set_region("us-west-2"); + auto credential_provider = + expected_config.mutable_credential_provider()->mutable_credentials_file_provider(); + credential_provider->mutable_credentials_data_source()->set_filename("this-is-filename"); + credential_provider->mutable_credentials_data_source()->mutable_watched_directory()->set_path( + "/tmp"); + credential_provider->set_profile("profile5"); + + Protobuf::util::MessageDifferencer differencer; + differencer.set_message_field_comparison(Protobuf::util::MessageDifferencer::EQUAL); + differencer.set_repeated_field_comparison(Protobuf::util::MessageDifferencer::AS_SET); + EXPECT_TRUE(differencer.Compare(expected_config, proto_config)); + + testing::NiceMock context; + AwsRequestSigningFilterFactory factory; + + Http::FilterFactoryCb cb = + factory.createFilterFactoryFromProto(proto_config, "stats", context).value(); + Http::MockFilterChainFactoryCallbacks filter_callbacks; + EXPECT_CALL(filter_callbacks, addStreamDecoderFilter(_)); + cb(filter_callbacks); +} + TEST(AwsRequestSigningFilterConfigTest, CredentialProvider_invalid) { const std::string yaml = R"EOF( service_name: s3 region: us-west-2 -credential_provider: {} +credential_provider: + custom_credential_provider_chain: true )EOF"; AwsRequestSigningProtoConfig proto_config;