From eff3ed3143d0b229e0ea5b2d65608c375d26afa5 Mon Sep 17 00:00:00 2001 From: botengyao Date: Fri, 18 Oct 2024 07:11:17 -0400 Subject: [PATCH 1/6] security-release: update the q3 release record (#36689) Signed-off-by: Boteng Yao --- RELEASES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASES.md b/RELEASES.md index 30a0661359..08a4be9acb 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -66,6 +66,7 @@ actual mechanics of the release itself. | 2023 Q3 | Boteng Yao ([botengyao](https://github.com/botengyao)) | Kateryna Nezdolii ([nezdolik](https://github.com/nezdolik)) | | 2023 Q4 | Paul Merrison ([pmerrison](https://github.com/pmerrison)) | Brian Sonnenberg ([briansonnenberg](https://github.com/briansonnenberg)) | | 2024 Q2 | Ryan Northey ([phlax](https://github.com/phlax)) | Boteng Yao ([botengyao](https://github.com/botengyao)) | +| 2024 Q3 | Ryan Northey ([phlax](https://github.com/phlax)) | Boteng Yao ([botengyao](https://github.com/botengyao)) | ## Major release schedule From 421034071875c4bd266b6d0c16cf19a2f0846b5f Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Fri, 18 Oct 2024 11:13:53 -0400 Subject: [PATCH 2/6] xds-failover: fixing runtime feature flag in tests (#36659) Signed-off-by: Adi Suissa-Peleg --- .../config_subscription/grpc/grpc_mux_failover_test.cc | 3 +++ .../grpc/xds_failover_integration_test.cc | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/test/extensions/config_subscription/grpc/grpc_mux_failover_test.cc b/test/extensions/config_subscription/grpc/grpc_mux_failover_test.cc index dae34fd62e..49cf8f53ee 100644 --- a/test/extensions/config_subscription/grpc/grpc_mux_failover_test.cc +++ b/test/extensions/config_subscription/grpc/grpc_mux_failover_test.cc @@ -409,6 +409,9 @@ TEST_F(GrpcMuxFailoverTest, PrimaryOnlyAttemptsAfterPrimaryAvailable) { // will try to reconnect to the primary (and then failover), and keep // alternating between the two. TEST_F(GrpcMuxFailoverTest, AlternatingPrimaryAndFailoverAttemptsAfterFailoverAvailable) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.xds_failover_to_primary_enabled", "true"}}); connectToFailover(); // Emulate a 5 times disconnects. diff --git a/test/extensions/config_subscription/grpc/xds_failover_integration_test.cc b/test/extensions/config_subscription/grpc/xds_failover_integration_test.cc index a1efe8febd..2715f5d4b3 100644 --- a/test/extensions/config_subscription/grpc/xds_failover_integration_test.cc +++ b/test/extensions/config_subscription/grpc/xds_failover_integration_test.cc @@ -575,6 +575,8 @@ TEST_P(XdsFailoverAdsIntegrationTest, NoFailoverUseAfterPrimaryResponse) { // Validate that once failover responds, and then disconnects, primary will be attempted. TEST_P(XdsFailoverAdsIntegrationTest, PrimaryUseAfterFailoverResponseAndDisconnect) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.xds_failover_to_primary_enabled", + "true"); // These tests are not executed with GoogleGrpc because they are flaky due to // the large timeout values for retries. SKIP_IF_GRPC_CLIENT(Grpc::ClientType::GoogleGrpc); @@ -692,6 +694,8 @@ TEST_P(XdsFailoverAdsIntegrationTest, PrimaryUseAfterFailoverResponseAndDisconne // still doesn't respond, failover will be attempted with the correct // initial_resource_versions. TEST_P(XdsFailoverAdsIntegrationTest, FailoverUseAfterFailoverResponseAndDisconnect) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.xds_failover_to_primary_enabled", + "true"); // These tests are not executed with GoogleGrpc because they are flaky due to // the large timeout values for retries. SKIP_IF_GRPC_CLIENT(Grpc::ClientType::GoogleGrpc); @@ -814,6 +818,8 @@ TEST_P(XdsFailoverAdsIntegrationTest, FailoverUseAfterFailoverResponseAndDisconn // both are not responding. TEST_P(XdsFailoverAdsIntegrationTest, PrimaryAndFailoverAttemptsAfterFailoverResponseAndDisconnect) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.xds_failover_to_primary_enabled", + "true"); // These tests are not executed with GoogleGrpc because they are flaky due to // the large timeout values for retries. SKIP_IF_GRPC_CLIENT(Grpc::ClientType::GoogleGrpc); From 5691519b9430b119c9580ad57e965ed482db68e8 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 18 Oct 2024 17:01:02 +0100 Subject: [PATCH 3/6] ci/rbe: Use engflow for non-coverage checks (#36687) Signed-off-by: Ryan Northey --- .github/workflows/_check_build.yml | 7 +------ .github/workflows/_check_san.yml | 7 +------ .github/workflows/envoy-checks.yml | 4 ---- test/common/upstream/BUILD | 2 +- test/extensions/common/aws/BUILD | 2 ++ test/extensions/filters/common/ratelimit_config/BUILD | 1 + test/extensions/filters/http/ext_proc/BUILD | 2 +- .../client_side_weighted_round_robin/BUILD | 1 + test/integration/BUILD | 4 ++-- 9 files changed, 10 insertions(+), 20 deletions(-) diff --git a/.github/workflows/_check_build.yml b/.github/workflows/_check_build.yml index f3417b58ee..e4018b4d9d 100644 --- a/.github/workflows/_check_build.yml +++ b/.github/workflows/_check_build.yml @@ -5,9 +5,6 @@ permissions: on: workflow_call: - secrets: - gcp-key: - required: true inputs: request: type: string @@ -23,15 +20,13 @@ concurrency: jobs: build: - secrets: - gcp-key: ${{ secrets.gcp-key }} permissions: contents: read packages: read uses: ./.github/workflows/_run.yml name: ${{ matrix.name ||matrix.target }} with: - # bazel-extra: '--config=remote-envoy-engflow' + bazel-extra: '--config=remote-envoy-engflow' cache-build-image: ${{ fromJSON(inputs.request).request.build-image.default }} concurrency-suffix: -${{ matrix.target }} error-match: | diff --git a/.github/workflows/_check_san.yml b/.github/workflows/_check_san.yml index c4119b0b24..58d183c6a4 100644 --- a/.github/workflows/_check_san.yml +++ b/.github/workflows/_check_san.yml @@ -5,9 +5,6 @@ permissions: on: workflow_call: - secrets: - gcp-key: - required: true inputs: request: type: string @@ -23,15 +20,13 @@ concurrency: jobs: san: - secrets: - gcp-key: ${{ secrets.gcp-key }} permissions: contents: read packages: read uses: ./.github/workflows/_run.yml name: ${{ matrix.target }} with: - # bazel-extra: '--config=remote-envoy-engflow' + bazel-extra: '--config=remote-envoy-engflow' cache-build-image: ${{ fromJSON(inputs.request).request.build-image.default }} concurrency-suffix: -${{ matrix.target }} request: ${{ inputs.request }} diff --git a/.github/workflows/envoy-checks.yml b/.github/workflows/envoy-checks.yml index 08422f5ad5..9325ab8465 100644 --- a/.github/workflows/envoy-checks.yml +++ b/.github/workflows/envoy-checks.yml @@ -41,8 +41,6 @@ jobs: # head-sha: ${{ github.sha }} build: - secrets: - gcp-key: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }} permissions: actions: read contents: read @@ -75,8 +73,6 @@ jobs: trusted: ${{ fromJSON(needs.load.outputs.trusted) }} san: - secrets: - gcp-key: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }} permissions: actions: read contents: read diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index a65b0d0c26..e0c8c6ed9b 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -87,7 +87,7 @@ envoy_cc_test( name = "cluster_manager_impl_test", size = "large", srcs = ["cluster_manager_impl_test.cc"], - rbe_pool = "2core", + rbe_pool = "4core", deps = [ ":test_cluster_manager", "//source/common/router:context_lib", diff --git a/test/extensions/common/aws/BUILD b/test/extensions/common/aws/BUILD index 21dc6d7860..57cde74571 100644 --- a/test/extensions/common/aws/BUILD +++ b/test/extensions/common/aws/BUILD @@ -43,6 +43,7 @@ envoy_cc_test( name = "sigv4_signer_corpus_test", srcs = ["sigv4_signer_corpus_test.cc"], data = ["@com_github_awslabs_aws_c_auth//:sigv4_tests"], + rbe_pool = "2core", deps = [ "//source/common/buffer:buffer_lib", "//source/common/http:message_lib", @@ -58,6 +59,7 @@ envoy_cc_test( name = "sigv4a_signer_corpus_test", srcs = ["sigv4a_signer_corpus_test.cc"], data = ["@com_github_awslabs_aws_c_auth//:sigv4a_tests"], + rbe_pool = "4core", deps = [ "//source/common/buffer:buffer_lib", "//source/common/http:message_lib", diff --git a/test/extensions/filters/common/ratelimit_config/BUILD b/test/extensions/filters/common/ratelimit_config/BUILD index bffabf03a6..b1bbfd13bc 100644 --- a/test/extensions/filters/common/ratelimit_config/BUILD +++ b/test/extensions/filters/common/ratelimit_config/BUILD @@ -20,6 +20,7 @@ envoy_proto_library( envoy_cc_test( name = "ratelimit_config_test", srcs = ["ratelimit_config_test.cc"], + rbe_pool = "2core", deps = [ ":ratelimit_config_test_proto_cc_proto", "//source/common/http:header_map_lib", diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index 21a1f5678a..8c4af27574 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -170,7 +170,7 @@ envoy_extension_cc_test( # TODO(jbohanon) use a test filter here instead of production filter "envoy.filters.http.set_metadata", ], - rbe_pool = "2core", + rbe_pool = "4core", shard_count = 8, tags = [ "cpu:3", diff --git a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD index 0a6739e131..2f69fe0b90 100644 --- a/test/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD +++ b/test/extensions/load_balancing_policies/client_side_weighted_round_robin/BUILD @@ -15,6 +15,7 @@ envoy_extension_cc_test( name = "config_test", srcs = ["config_test.cc"], extension_names = ["envoy.load_balancing_policies.client_side_weighted_round_robin"], + rbe_pool = "2core", deps = [ "//source/extensions/load_balancing_policies/client_side_weighted_round_robin:config", "//test/mocks/server:factory_context_mocks", diff --git a/test/integration/BUILD b/test/integration/BUILD index 374b9f7b3f..840c463a42 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1539,7 +1539,7 @@ envoy_cc_test( name = "load_stats_integration_test", size = "large", srcs = ["load_stats_integration_test.cc"], - rbe_pool = "2core", + rbe_pool = "4core", deps = [ ":http_integration_lib", "//test/config:utility_lib", @@ -2546,7 +2546,7 @@ envoy_cc_test( "//conditions:default": ["quic_http_integration_test.cc"], }), data = ["//test/config/integration/certs"], - rbe_pool = "2core", + rbe_pool = "4core", # TODO(envoyproxy/windows-dev): Diagnose failure shown only on clang-cl build, see: # https://gist.github.com/wrowe/a152cb1d12c2f751916122aed39d8517 # TODO(envoyproxy/windows-dev): Diagnose timeout, why opt build test under Windows GCP RBE From 803aea39d6fa22eadf115279085dda56e97bb37f Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Fri, 18 Oct 2024 12:02:28 -0400 Subject: [PATCH 4/6] test: deflake an integration test (#36674) waitForAccessLog asserts the log you're waiting for is the last one. For periodic logging on slow machines there may end up being extra log entries so allow excess. https://github.com/envoyproxy/envoy/actions/runs/11371697784/job/31634429494 Signed-off-by: Alyssa Wilk --- test/integration/protocol_integration_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 8b4b84f843..a6119814f2 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -462,7 +462,7 @@ TEST_P(ProtocolIntegrationTest, PeriodicAccessLog) { {":method", "GET"}, {":path", "/test"}, {":scheme", "http"}, {":authority", "host.com"}}); waitForNextUpstreamRequest(); EXPECT_EQ(AccessLogType_Name(AccessLog::AccessLogType::DownstreamPeriodic), - waitForAccessLog(access_log_name_)); + waitForAccessLog(access_log_name_, 0, true)); upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); ASSERT_TRUE(response->waitForEndStream()); From ee6163435be32f0efd3ad068e90ea89c974991c3 Mon Sep 17 00:00:00 2001 From: birenroy Date: Fri, 18 Oct 2024 12:06:27 -0400 Subject: [PATCH 5/6] http: initializes a field of ConnectionManagerImpl::ActiveStream::State. (#36642) While debugging an issue, it became clear that this field is not properly initialized. Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: --------- Signed-off-by: Biren Roy --- source/common/http/conn_manager_impl.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index d4f73ccef4..ffa0aab7f6 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -338,7 +338,8 @@ class ConnectionManagerImpl : Logger::Loggable, : codec_saw_local_complete_(false), codec_encode_complete_(false), on_reset_stream_called_(false), is_zombie_stream_(false), successful_upgrade_(false), is_internally_destroyed_(false), is_internally_created_(false), is_tunneling_(false), - decorated_propagate_(true), deferred_to_next_io_iteration_(false) {} + decorated_propagate_(true), deferred_to_next_io_iteration_(false), + deferred_end_stream_(false) {} // It's possibly for the codec to see the completed response but not fully // encode it. From 871b3e91234d6e305d4ec653444f1d56b324a302 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Sun, 20 Oct 2024 18:17:03 -0700 Subject: [PATCH 6/6] aws_signing: support for dynamically configurable credential (#36217) Commit Message: aws_signing: support for dynamically configurable credential Additional Description: This adds a new API for AWS Signing filter that allows control planes to configure the credentials providers to sign requests. Note that the similar configuration had already been added for AWS Lambda filter, and this adds the similar but more generic configuration API for credential in the common configuration. In the future, the lambda filter can also leverage this new common configuration. Closes #36109 Risk Level: low Testing: unit Docs Changes: done Release Notes: done Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [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: Takeshi Yoneda --- api/BUILD | 1 + api/envoy/extensions/common/aws/v3/BUILD | 9 ++ .../common/aws/v3/credential_provider.proto | 54 +++++++++ .../filters/http/aws_request_signing/v3/BUILD | 1 + .../v3/aws_request_signing.proto | 7 +- api/versioning/BUILD | 1 + changelogs/current.yaml | 5 + .../common_messages/common_messages.rst | 1 + .../http_filters/_include/aws_credentials.rst | 5 +- source/extensions/common/aws/BUILD | 1 + .../common/aws/credentials_provider_impl.cc | 95 +++++++++++----- .../common/aws/credentials_provider_impl.h | 48 ++++++-- .../http/aws_request_signing/config.cc | 39 +++++-- test/extensions/common/aws/BUILD | 1 + .../aws/credentials_provider_impl_test.cc | 103 +++++++++++++++--- .../http/aws_request_signing/config_test.cc | 96 ++++++++++++++++ 16 files changed, 400 insertions(+), 67 deletions(-) create mode 100644 api/envoy/extensions/common/aws/v3/BUILD create mode 100644 api/envoy/extensions/common/aws/v3/credential_provider.proto diff --git a/api/BUILD b/api/BUILD index 096373e79f..3d0fce982f 100644 --- a/api/BUILD +++ b/api/BUILD @@ -140,6 +140,7 @@ proto_library( "//envoy/extensions/clusters/dynamic_forward_proxy/v3:pkg", "//envoy/extensions/clusters/redis/v3:pkg", "//envoy/extensions/common/async_files/v3:pkg", + "//envoy/extensions/common/aws/v3:pkg", "//envoy/extensions/common/dynamic_forward_proxy/v3:pkg", "//envoy/extensions/common/matching/v3:pkg", "//envoy/extensions/common/ratelimit/v3:pkg", diff --git a/api/envoy/extensions/common/aws/v3/BUILD b/api/envoy/extensions/common/aws/v3/BUILD new file mode 100644 index 0000000000..29ebf07414 --- /dev/null +++ b/api/envoy/extensions/common/aws/v3/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +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"], +) diff --git a/api/envoy/extensions/common/aws/v3/credential_provider.proto b/api/envoy/extensions/common/aws/v3/credential_provider.proto new file mode 100644 index 0000000000..b623a40a43 --- /dev/null +++ b/api/envoy/extensions/common/aws/v3/credential_provider.proto @@ -0,0 +1,54 @@ +syntax = "proto3"; + +package envoy.extensions.common.aws.v3; + +import "udpa/annotations/sensitive.proto"; +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.common.aws.v3"; +option java_outer_classname = "CredentialProviderProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/common/aws/v3;awsv3"; +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 +// 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. +message AwsCredentialProvider { + // The option to use `AssumeRoleWithWebIdentity `_. + // If inline_credential is set, this is ignored. + AssumeRoleWithWebIdentityCredentialProvider assume_role_with_web_identity = 1; + + // The option to use an inline credential. + // If this is set, it takes precedence over assume_role_with_web_identity. + InlineCredentialProvider inline_credential = 2; +} + +// Configuration to use an inline AWS credential. This is an equivalent to setting the well-known +// environment variables ``AWS_ACCESS_KEY_ID``, ``AWS_SECRET_ACCESS_KEY``, and the optional ``AWS_SESSION_TOKEN``. +message InlineCredentialProvider { + // The AWS access key ID. + string access_key_id = 1 [(validate.rules).string = {min_len: 1}]; + + // The AWS secret access key. + string secret_access_key = 2 + [(validate.rules).string = {min_len: 1}, (udpa.annotations.sensitive) = true]; + + // The AWS session token. This is optional. + string session_token = 3 [(udpa.annotations.sensitive) = true]; +} + +// Configuration to use `AssumeRoleWithWebIdentity `_ +// to get AWS credentials. +message AssumeRoleWithWebIdentityCredentialProvider { + // The ARN of the role to assume. + string role_arn = 1 [(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]; +} diff --git a/api/envoy/extensions/filters/http/aws_request_signing/v3/BUILD b/api/envoy/extensions/filters/http/aws_request_signing/v3/BUILD index bfc4863309..19bcd528f6 100644 --- a/api/envoy/extensions/filters/http/aws_request_signing/v3/BUILD +++ b/api/envoy/extensions/filters/http/aws_request_signing/v3/BUILD @@ -6,6 +6,7 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ + "//envoy/extensions/common/aws/v3:pkg", "//envoy/type/matcher/v3:pkg", "@com_github_cncf_xds//udpa/annotations:pkg", ], diff --git a/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto b/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto index 5729d7f503..254352e77b 100644 --- a/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto +++ b/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package envoy.extensions.filters.http.aws_request_signing.v3; +import "envoy/extensions/common/aws/v3/credential_provider.proto"; import "envoy/type/matcher/v3/string.proto"; import "google/protobuf/duration.proto"; @@ -21,7 +22,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#extension: envoy.filters.http.aws_request_signing] // Top level configuration for the AWS request signing filter. -// [#next-free-field: 8] +// [#next-free-field: 9] message AwsRequestSigning { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.aws_request_signing.v2alpha.AwsRequestSigning"; @@ -107,6 +108,10 @@ message AwsRequestSigning { // query_string: {} // QueryString query_string = 7; + + // The credential provider for signing the request. This is optional and if not set, + // it will be retrieved from the procedure described in :ref:`config_http_filters_aws_request_signing`. + common.aws.v3.AwsCredentialProvider credential_provider = 8; } message AwsRequestSigningPerRoute { diff --git a/api/versioning/BUILD b/api/versioning/BUILD index bfc572f7f3..2b9add1009 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -78,6 +78,7 @@ proto_library( "//envoy/extensions/clusters/dynamic_forward_proxy/v3:pkg", "//envoy/extensions/clusters/redis/v3:pkg", "//envoy/extensions/common/async_files/v3:pkg", + "//envoy/extensions/common/aws/v3:pkg", "//envoy/extensions/common/dynamic_forward_proxy/v3:pkg", "//envoy/extensions/common/matching/v3:pkg", "//envoy/extensions/common/ratelimit/v3:pkg", diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 259c31cecb..b48db53865 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -32,6 +32,11 @@ removed_config_or_runtime: Removed runtime flag ``envoy.restart_features.allow_client_socket_creation_failure`` and legacy code paths. new_features: +- area: aws_request_signing + change: | + Added an optional field :ref:`credential_provider + ` + to the AWS request signing filter to explicitly specify a source for AWS credentials. - area: tls change: | Added support for P-384 and P-521 curves for TLS server certificates. diff --git a/docs/root/api-v3/common_messages/common_messages.rst b/docs/root/api-v3/common_messages/common_messages.rst index 7d40c0218f..04968b2895 100644 --- a/docs/root/api-v3/common_messages/common_messages.rst +++ b/docs/root/api-v3/common_messages/common_messages.rst @@ -19,6 +19,7 @@ Common messages ../extensions/network/socket_interface/v3/default_socket_interface.proto ../extensions/matching/common_inputs/environment_variable/v3/input.proto ../config/core/v3/extension.proto + ../extensions/common/aws/v3/credential_provider.proto ../extensions/common/matching/v3/extension_matcher.proto ../extensions/filters/common/dependency/v3/dependency.proto ../extensions/regex_engines/v3/google_re2.proto 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 eac00b8926..0d945684a0 100644 --- a/docs/root/configuration/http/http_filters/_include/aws_credentials.rst +++ b/docs/root/configuration/http/http_filters/_include/aws_credentials.rst @@ -2,7 +2,7 @@ Credentials ----------- The filter uses a number of different credentials providers to obtain an AWS access key ID, AWS secret access key, and AWS session token. -It moves through the credentials providers in the order described below, stopping when one of them returns an access key ID and a +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. @@ -46,6 +46,9 @@ 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/source/extensions/common/aws/BUILD b/source/extensions/common/aws/BUILD index b07e278436..ddd5293136 100644 --- a/source/extensions/common/aws/BUILD +++ b/source/extensions/common/aws/BUILD @@ -126,6 +126,7 @@ envoy_cc_library( "//source/common/runtime:runtime_features_lib", "//source/common/tracing:http_tracer_lib", "@com_google_absl//absl/time", + "@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 07bbb2cd54..d80097fd8d 100644 --- a/source/extensions/common/aws/credentials_provider_impl.cc +++ b/source/extensions/common/aws/credentials_provider_impl.cc @@ -742,15 +742,16 @@ 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 sts_endpoint, absl::string_view role_arn, absl::string_view role_session_name, + 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 = {}) : MetadataCredentialsProviderBase( api, context, fetch_metadata_using_curl, 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), sts_endpoint_(sts_endpoint), role_arn_(role_arn), - role_session_name_(role_session_name) {} + token_file_path_(token_file_path), token_(token), sts_endpoint_(sts_endpoint), + role_arn_(role_arn), role_session_name_(role_session_name) {} bool WebIdentityCredentialsProvider::needsRefresh() { const auto now = api_.timeSource().systemTime(); @@ -772,11 +773,15 @@ void WebIdentityCredentialsProvider::refresh() { ENVOY_LOG(debug, "Getting AWS web identity credentials from STS: {}", sts_endpoint_); - 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; + 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(); } Http::RequestMessageImpl message; @@ -791,7 +796,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(web_token_file_or_error.value()))); + Envoy::Http::Utility::PercentEncoding::encode(identity_token))); // Use the Accept header to ensure that AssumeRoleWithWebIdentityResponse is returned as JSON. message.headers().setReference(Http::CustomHeaders::get().Accept, Http::Headers::get().ContentTypeValues.Json); @@ -915,6 +920,26 @@ Credentials CredentialsProviderChain::getCredentials() { return Credentials(); } +std::string sessionName(Api::Api& api) { + const auto role_session_name = absl::NullSafeStringView(std::getenv(AWS_ROLE_SESSION_NAME)); + std::string actual_session_name; + if (!role_session_name.empty()) { + actual_session_name = std::string(role_session_name); + } else { + // In practice, this value will be provided by the environment, so the placeholder value is + // not important. Some AWS SDKs use time in nanoseconds, so we'll just use that. + const auto now_nanos = std::chrono::duration_cast( + api.timeSource().systemTime().time_since_epoch()) + .count(); + actual_session_name = fmt::format("{}", now_nanos); + } + return actual_session_name; +} + +std::string stsClusterName(absl::string_view region) { + return absl::StrCat(STS_TOKEN_CLUSTER, "-", region); +} + DefaultCredentialsProviderChain::DefaultCredentialsProviderChain( Api::Api& api, ServerFactoryContextOptRef context, absl::string_view region, const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, @@ -936,31 +961,17 @@ DefaultCredentialsProviderChain::DefaultCredentialsProviderChain( 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 role_session_name = absl::NullSafeStringView(std::getenv(AWS_ROLE_SESSION_NAME)); - std::string actual_session_name; - if (!role_session_name.empty()) { - actual_session_name = std::string(role_session_name); - } else { - // In practice, this value will be provided by the environment, so the placeholder value is - // not important. Some AWS SDKs use time in nanoseconds, so we'll just use that. - const auto now_nanos = std::chrono::duration_cast( - api.timeSource().systemTime().time_since_epoch()) - .count(); - actual_session_name = fmt::format("{}", now_nanos); - } + const auto session_name = sessionName(api); const auto sts_endpoint = Utility::getSTSEndpoint(region) + ":443"; - - // Handle edge case - if two web identity request signers are configured with different - // regions. This appends the region to the cluster name to differentiate the two. - auto cluster_name_ = absl::StrCat(STS_TOKEN_CLUSTER, "-", region); + const auto cluster_name = stsClusterName(region); ENVOY_LOG( debug, "Using web identity credentials provider with STS endpoint: {} and session name: {}", - sts_endpoint, actual_session_name); + sts_endpoint, session_name); add(factories.createWebIdentityCredentialsProvider( - api, context, fetch_metadata_using_curl, MetadataFetcher::create, cluster_name_, - web_token_path, sts_endpoint, role_arn, actual_session_name, refresh_state, + api, context, fetch_metadata_using_curl, MetadataFetcher::create, cluster_name, + web_token_path, "", sts_endpoint, role_arn, session_name, refresh_state, initialization_timer)); } } @@ -1004,6 +1015,34 @@ DefaultCredentialsProviderChain::DefaultCredentialsProviderChain( } } +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 std::string cluster_name = stsClusterName(region); + 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, Extensions::Common::Aws::Utility::fetchMetadata, + 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 b5237d5ae5..d3cf8f5ce8 100644 --- a/source/extensions/common/aws/credentials_provider_impl.h +++ b/source/extensions/common/aws/credentials_provider_impl.h @@ -8,6 +8,7 @@ #include "envoy/api/api.h" #include "envoy/common/optref.h" #include "envoy/event/timer.h" +#include "envoy/extensions/common/aws/v3/credential_provider.pb.h" #include "envoy/http/message.h" #include "envoy/server/factory_context.h" @@ -320,11 +321,14 @@ class ContainerCredentialsProvider : public MetadataCredentialsProviderBase, class WebIdentityCredentialsProvider : public MetadataCredentialsProviderBase, public MetadataFetcher::MetadataReceiver { 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 sts_endpoint, - absl::string_view role_arn, absl::string_view role_session_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, MetadataFetcher::MetadataReceiver::RefreshState refresh_state, std::chrono::seconds initialization_timer, absl::string_view cluster_name); @@ -333,8 +337,14 @@ class WebIdentityCredentialsProvider : public MetadataCredentialsProviderBase, 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_; const std::string role_arn_; const std::string role_session_name_; @@ -375,8 +385,8 @@ class CredentialsProviderChainFactories { 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 sts_endpoint, absl::string_view role_arn, - absl::string_view role_session_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, MetadataFetcher::MetadataReceiver::RefreshState refresh_state, std::chrono::seconds initialization_timer) const PURE; @@ -454,17 +464,41 @@ class DefaultCredentialsProviderChain : public CredentialsProviderChain, 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 sts_endpoint, absl::string_view role_arn, - absl::string_view role_session_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, MetadataFetcher::MetadataReceiver::RefreshState refresh_state, std::chrono::seconds initialization_timer) const override { return std::make_shared( - api, context, fetch_metadata_using_curl, create_metadata_fetcher_cb, token_file_path, + 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); } }; +/** + * 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/filters/http/aws_request_signing/config.cc b/source/extensions/filters/http/aws_request_signing/config.cc index c0de8b785e..22e490b796 100644 --- a/source/extensions/filters/http/aws_request_signing/config.cc +++ b/source/extensions/filters/http/aws_request_signing/config.cc @@ -60,10 +60,18 @@ AwsRequestSigningFilterFactory::createFilterFactoryFromProtoTyped( config.query_string(), expiration_time, Extensions::Common::Aws::SignatureQueryParameterValues::DefaultExpiration); - auto credentials_provider = - std::make_shared( - server_context.api(), makeOptRef(server_context), region, - Extensions::Common::Aws::Utility::fetchMetadata); + 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), region, + Extensions::Common::Aws::Utility::fetchMetadata); + if (!credentials_provider.ok()) { + return credentials_provider.status(); + } + const auto matcher_config = Extensions::Common::Aws::AwsSigningHeaderExclusionVector( config.match_excluded_headers().begin(), config.match_excluded_headers().end()); @@ -71,7 +79,7 @@ AwsRequestSigningFilterFactory::createFilterFactoryFromProtoTyped( if (config.signing_algorithm() == AwsRequestSigning_SigningAlgorithm_AWS_SIGV4A) { signer = std::make_unique( - config.service_name(), region, credentials_provider, server_context, matcher_config, + 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 @@ -80,7 +88,7 @@ AwsRequestSigningFilterFactory::createFilterFactoryFromProtoTyped( "can be specified when using signing_algorithm: AWS_SIGV4A."); } signer = std::make_unique( - config.service_name(), region, credentials_provider, server_context, matcher_config, + config.service_name(), region, credentials_provider.value(), server_context, matcher_config, query_string, expiration_time); } @@ -121,10 +129,17 @@ AwsRequestSigningFilterFactory::createRouteSpecificFilterConfigTyped( uint16_t expiration_time = PROTOBUF_GET_SECONDS_OR_DEFAULT( per_route_config.aws_request_signing().query_string(), expiration_time, 5); - auto credentials_provider = - std::make_shared( - context.api(), makeOptRef(context), region, - Extensions::Common::Aws::Utility::fetchMetadata); + 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), region, + Extensions::Common::Aws::Utility::fetchMetadata); + if (!credentials_provider.ok()) { + throw EnvoyException(std::string(credentials_provider.status().message())); + } const auto matcher_config = Extensions::Common::Aws::AwsSigningHeaderExclusionVector( per_route_config.aws_request_signing().match_excluded_headers().begin(), @@ -134,7 +149,7 @@ AwsRequestSigningFilterFactory::createRouteSpecificFilterConfigTyped( 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, + per_route_config.aws_request_signing().service_name(), region, credentials_provider.value(), context, matcher_config, query_string, expiration_time); } else { // Verify that we have not specified a region set when using sigv4 algorithm @@ -143,7 +158,7 @@ AwsRequestSigningFilterFactory::createRouteSpecificFilterConfigTyped( "can be specified when using signing_algorithm: AWS_SIGV4A."); } signer = std::make_unique( - per_route_config.aws_request_signing().service_name(), region, credentials_provider, + per_route_config.aws_request_signing().service_name(), region, credentials_provider.value(), context, matcher_config, query_string, expiration_time); } diff --git a/test/extensions/common/aws/BUILD b/test/extensions/common/aws/BUILD index 57cde74571..5c1b872e9d 100644 --- a/test/extensions/common/aws/BUILD +++ b/test/extensions/common/aws/BUILD @@ -152,6 +152,7 @@ envoy_cc_test( "//test/test_common:environment_lib", "//test/test_common:simulated_time_system_lib", "//test/test_common:test_runtime_lib", + "@envoy_api//envoy/extensions/common/aws/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/common/aws/credentials_provider_impl_test.cc b/test/extensions/common/aws/credentials_provider_impl_test.cc index 76a8472547..49d5cd1481 100644 --- a/test/extensions/common/aws/credentials_provider_impl_test.cc +++ b/test/extensions/common/aws/credentials_provider_impl_test.cc @@ -5,6 +5,8 @@ #include #include +#include "envoy/extensions/common/aws/v3/credential_provider.pb.h" + #include "source/extensions/common/aws/credentials_provider_impl.h" #include "source/extensions/common/aws/metadata_fetcher.h" @@ -1954,6 +1956,10 @@ class WebIdentityCredentialsProviderTest : public testing::Test { MetadataFetcher::MetadataReceiver::RefreshState::Ready, std::chrono::seconds initialization_timer = std::chrono::seconds(2)) { ON_CALL(context_, clusterManager()).WillByDefault(ReturnRef(cluster_manager_)); + std::string token_file_path; + if (token_.empty()) { + token_file_path = TestEnvironment::writeStringToFileForTest("web_token_file", "web_token"); + } provider_ = std::make_shared( *api_, context_, [this](Http::RequestMessage& message) -> absl::optional { @@ -1963,9 +1969,8 @@ class WebIdentityCredentialsProviderTest : public testing::Test { metadata_fetcher_.reset(raw_metadata_fetcher_); return std::move(metadata_fetcher_); }, - TestEnvironment::writeStringToFileForTest("web_token_file", "web_token"), - "sts.region.amazonaws.com:443", "aws:iam::123456789012:role/arn", "role-session-name", - refresh_state, initialization_timer, "credentials_provider_cluster"); + token_file_path, token_, "sts.region.amazonaws.com:443", "aws:iam::123456789012:role/arn", + "role-session-name", refresh_state, initialization_timer, "credentials_provider_cluster"); } void @@ -1984,6 +1989,10 @@ class WebIdentityCredentialsProviderTest : public testing::Test { setupProviderWithLibcurl(MetadataFetcher::MetadataReceiver::RefreshState refresh_state = MetadataFetcher::MetadataReceiver::RefreshState::Ready, std::chrono::seconds initialization_timer = std::chrono::seconds(2)) { + std::string token_file_path; + if (token_.empty()) { + token_file_path = TestEnvironment::writeStringToFileForTest("web_token_file", "web_token"); + } ON_CALL(context_, clusterManager()).WillByDefault(ReturnRef(cluster_manager_)); provider_ = std::make_shared( *api_, context_, @@ -1994,21 +2003,22 @@ class WebIdentityCredentialsProviderTest : public testing::Test { metadata_fetcher_.reset(raw_metadata_fetcher_); return std::move(metadata_fetcher_); }, - TestEnvironment::writeStringToFileForTest("web_token_file", "web_token"), - "sts.region.amazonaws.com:443", "aws:iam::123456789012:role/arn", "role-session-name", - refresh_state, initialization_timer, "credentials_provider_cluster"); + token_file_path, token_, "sts.region.amazonaws.com:443", "aws:iam::123456789012:role/arn", + "role-session-name", refresh_state, initialization_timer, "credentials_provider_cluster"); } void expectDocument(const uint64_t status_code, const std::string&& document) { - Http::TestRequestHeaderMapImpl headers{{":path", - "/?Action=AssumeRoleWithWebIdentity" - "&Version=2011-06-15&RoleSessionName=role-session-name" - "&RoleArn=aws:iam::123456789012:role/arn" - "&WebIdentityToken=web_token"}, - {":authority", "sts.region.amazonaws.com"}, - {":scheme", "https"}, - {":method", "GET"}, - {"Accept", "application/json"}}; + std::string exp_token = token_.empty() ? "web_token" : token_; + Http::TestRequestHeaderMapImpl headers{ + {":path", "/?Action=AssumeRoleWithWebIdentity" + "&Version=2011-06-15&RoleSessionName=role-session-name" + "&RoleArn=aws:iam::123456789012:role/arn" + "&WebIdentityToken=" + + exp_token}, + {":authority", "sts.region.amazonaws.com"}, + {":scheme", "https"}, + {":method", "GET"}, + {"Accept", "application/json"}}; EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) .WillRepeatedly(Invoke([this, status_code, document = std::move(document)]( Http::RequestMessage&, Tracing::Span&, @@ -2049,6 +2059,7 @@ class WebIdentityCredentialsProviderTest : public testing::Test { NiceMock test_cluster{}; Init::TargetHandlePtr init_target_; NiceMock init_watcher_; + std::string token_ = ""; }; TEST_F(WebIdentityCredentialsProviderTest, FailedFetchingDocument) { @@ -2074,6 +2085,9 @@ TEST_F(WebIdentityCredentialsProviderTest, FailedFetchingDocument) { } TEST_F(WebIdentityCredentialsProviderTest, EmptyDocument) { + // Test that the static prefetched token will be used instead of the local file. + token_ = "prefetched_token"; + // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); expectDocument(200, std::move(std::string())); @@ -2468,7 +2482,7 @@ class DefaultCredentialsProviderChainTest : public testing::Test { (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, absl::string_view, absl::string_view, MetadataFetcher::MetadataReceiver::RefreshState, std::chrono::seconds), (const)); MOCK_METHOD(CredentialsProviderSharedPtr, createContainerCredentialsProvider, @@ -2564,7 +2578,7 @@ TEST_F(DefaultCredentialsProviderChainTest, NoWebIdentitySessionName) { EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); EXPECT_CALL(factories_, createWebIdentityCredentialsProvider( - Ref(*api_), _, _, _, _, "/path/to/web_token", "sts.region.amazonaws.com:443", + Ref(*api_), _, _, _, _, "/path/to/web_token", _, "sts.region.amazonaws.com:443", "aws:iam::123456789012:role/arn", "1234567890000000", _, _)); EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _, _, _)); @@ -2580,7 +2594,7 @@ TEST_F(DefaultCredentialsProviderChainTest, WebIdentityWithSessionName) { EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _, _, _)); EXPECT_CALL(factories_, createWebIdentityCredentialsProvider( - Ref(*api_), _, _, _, _, "/path/to/web_token", "sts.region.amazonaws.com:443", + Ref(*api_), _, _, _, _, "/path/to/web_token", _, "sts.region.amazonaws.com:443", "aws:iam::123456789012:role/arn", "role-session-name", _, _)); DefaultCredentialsProviderChain chain(*api_, context_, "region", DummyMetadataFetcher(), factories_); @@ -2635,6 +2649,59 @@ TEST(CredentialsProviderChainTest, getCredentials_secondProviderReturns) { EXPECT_EQ(creds, ret_creds); } +TEST(CreateCredentialsProviderFromConfig, InlineCredential) { + NiceMock context; + envoy::extensions::common::aws::v3::InlineCredentialProvider inline_credential; + inline_credential.set_access_key_id("TestAccessKey"); + inline_credential.set_secret_access_key("TestSecret"); + inline_credential.set_session_token("TestSessionToken"); + + 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(); + 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/filters/http/aws_request_signing/config_test.cc b/test/extensions/filters/http/aws_request_signing/config_test.cc index ae2b448d62..1560638dfb 100644 --- a/test/extensions/filters/http/aws_request_signing/config_test.cc +++ b/test/extensions/filters/http/aws_request_signing/config_test.cc @@ -53,6 +53,102 @@ host_rewrite: new-host cb(filter_callbacks); } +TEST(AwsRequestSigningFilterConfigTest, CredentialProvider_inline) { + const std::string yaml = R"EOF( +service_name: s3 +region: us-west-2 +credential_provider: + inline_credential: + access_key_id: access_key + secret_access_key: secret_key + session_token: session_token + )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_inline_credential(); + credential_provider->set_access_key_id("access_key"); + credential_provider->set_secret_access_key("secret_key"); + credential_provider->set_session_token("session_token"); + + 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_assume_role_web_identity) { + const std::string yaml = R"EOF( +service_name: s3 +region: us-west-2 +credential_provider: + assume_role_with_web_identity: + web_identity_token: this-is-token + role_arn: arn:aws:iam::123456789012:role/role-name + )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_assume_role_with_web_identity(); + credential_provider->set_web_identity_token("this-is-token"); + credential_provider->set_role_arn("arn:aws:iam::123456789012:role/role-name"); + + 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: {} + )EOF"; + + AwsRequestSigningProtoConfig proto_config; + TestUtility::loadFromYamlAndValidate(yaml, proto_config); + + EXPECT_TRUE(proto_config.has_credential_provider()); + + testing::NiceMock context; + AwsRequestSigningFilterFactory factory; + + // The config is invalid because the credential provider is empty. + absl::StatusOr cb = + factory.createFilterFactoryFromProto(proto_config, "", context); + EXPECT_FALSE(cb.ok()); + EXPECT_EQ(cb.status().code(), absl::StatusCode::kInvalidArgument); +} + TEST(AwsRequestSigningFilterConfigTest, SimpleConfigExplicitSigningAlgorithm) { const std::string yaml = R"EOF( service_name: s3