From c4117e4615dc962b2090685e484ece6ef187317f Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Fri, 30 Aug 2024 10:31:28 -0700 Subject: [PATCH] [xDS] implement GCP Auth filter (#37550) Final piece of gRFC A83 (https://github.com/grpc/proposal/pull/438): the GCP authentication filter itself. Infrastructure changes include: - Added a general-purpose LRU cache library that can be reused elsewhere. - Fixed the client channel code to use the channel args returned by the resolver for the dynamic filters. This was necessary so that the GCP auth filter could access the `XdsConfig` object, which is passed via a channel arg. - Unlike the other xDS HTTP filters we support, the GCP auth filter does not support config overrides, and its configuration includes a cache size parameter that we always need at the channel level, not per-call. As a result, I had to change the xDS HTTP filter API to give it the ability to set top-level fields in the service config, not just per-method fields. (We use the service config as a way of passing configuration down into xDS HTTP filters.) Note that for now, this works only on the client side, because we don't have machinery for a top-level service config on the server side. - The GCP auth filter is also the first case where the filter needs to know its instance name from the xDS config, so I changed the xDS HTTP filter API to plumb that through. - Fixed a bug in the HTTP client library that prevented the override functions from declining to override a particular request. Closes #37550 COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/37550 from markdroth:xds_gcp_auth_filter 19eaefb52f5954dcb680c04755d1e703d7f623e8 PiperOrigin-RevId: 669371249 --- CMakeLists.txt | 277 ++++++++++++- Makefile | 4 + Package.swift | 9 + build_autogenerated.yaml | 103 ++++- config.m4 | 6 + config.w32 | 6 + gRPC-C++.podspec | 10 + gRPC-Core.podspec | 14 + grpc.gemspec | 9 + package.xml | 9 + src/core/BUILD | 62 +++ src/core/client_channel/client_channel.cc | 17 +- src/core/client_channel/client_channel.h | 2 +- .../client_channel/client_channel_filter.cc | 18 +- .../client_channel/client_channel_filter.h | 2 +- .../gcp_authentication_filter.cc | 167 ++++++++ .../gcp_authentication_filter.h | 82 ++++ ...cp_authentication_service_config_parser.cc | 81 ++++ ...gcp_authentication_service_config_parser.h | 87 ++++ ...cp_service_account_identity_credentials.cc | 2 +- ...gcp_service_account_identity_credentials.h | 5 +- .../grpc_plugin_registry_extra.cc | 3 + src/core/resolver/xds/xds_resolver.cc | 35 +- src/core/server/xds_server_config_fetcher.cc | 2 +- .../service_config/service_config_call_data.h | 5 +- src/core/util/http_client/httpcli.cc | 25 +- src/core/util/http_client/httpcli.h | 4 +- src/core/util/lru_cache.h | 104 +++++ src/core/xds/grpc/xds_http_fault_filter.cc | 13 +- src/core/xds/grpc/xds_http_fault_filter.h | 6 +- src/core/xds/grpc/xds_http_filter.h | 12 +- src/core/xds/grpc/xds_http_filter_registry.cc | 7 + src/core/xds/grpc/xds_http_filter_registry.h | 9 +- .../xds/grpc/xds_http_gcp_authn_filter.cc | 142 +++++++ src/core/xds/grpc/xds_http_gcp_authn_filter.h | 61 +++ src/core/xds/grpc/xds_http_rbac_filter.cc | 10 +- src/core/xds/grpc/xds_http_rbac_filter.h | 6 +- .../grpc/xds_http_stateful_session_filter.cc | 10 +- .../grpc/xds_http_stateful_session_filter.h | 6 +- src/core/xds/grpc/xds_listener_parser.cc | 4 +- src/core/xds/grpc/xds_route_config_parser.cc | 2 +- src/core/xds/grpc/xds_routing.cc | 78 +++- src/core/xds/grpc/xds_routing.h | 12 +- src/python/grpcio/grpc_core_dependencies.py | 4 + test/core/filters/BUILD | 21 + test/core/filters/filter_test.cc | 4 +- test/core/filters/filter_test.h | 4 +- .../filters/gcp_authentication_filter_test.cc | 386 ++++++++++++++++++ test/core/util/BUILD | 12 + test/core/util/lru_cache_test.cc | 74 ++++ test/core/xds/BUILD | 1 + test/core/xds/xds_http_filters_test.cc | 290 +++++++++++-- test/cpp/end2end/xds/BUILD | 37 ++ test/cpp/end2end/xds/xds_end2end_test_lib.cc | 11 +- test/cpp/end2end/xds/xds_end2end_test_lib.h | 7 + .../end2end/xds/xds_gcp_authn_end2end_test.cc | 231 +++++++++++ tools/doxygen/Doxyfile.c++.internal | 9 + tools/doxygen/Doxyfile.core.internal | 9 + tools/run_tests/generated/tests.json | 48 +++ 59 files changed, 2545 insertions(+), 131 deletions(-) create mode 100644 src/core/ext/filters/gcp_authentication/gcp_authentication_filter.cc create mode 100644 src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h create mode 100644 src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.cc create mode 100644 src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h create mode 100644 src/core/util/lru_cache.h create mode 100644 src/core/xds/grpc/xds_http_gcp_authn_filter.cc create mode 100644 src/core/xds/grpc/xds_http_gcp_authn_filter.h create mode 100644 test/core/filters/gcp_authentication_filter_test.cc create mode 100644 test/core/util/lru_cache_test.cc create mode 100644 test/cpp/end2end/xds/xds_gcp_authn_end2end_test.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 1393b57a180..f18cd94d649 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1123,6 +1123,7 @@ if(gRPC_BUILD_TESTS) add_dependencies(buildtests_cxx fuzzing_event_engine_test) endif() add_dependencies(buildtests_cxx fuzzing_event_engine_unittest) + add_dependencies(buildtests_cxx gcp_authentication_filter_test) add_dependencies(buildtests_cxx generic_end2end_test) add_dependencies(buildtests_cxx glob_test) add_dependencies(buildtests_cxx goaway_server_test) @@ -1222,6 +1223,7 @@ if(gRPC_BUILD_TESTS) add_dependencies(buildtests_cxx log_too_many_open_files_test) endif() add_dependencies(buildtests_cxx loop_test) + add_dependencies(buildtests_cxx lru_cache_test) add_dependencies(buildtests_cxx map_pipe_test) add_dependencies(buildtests_cxx match_test) add_dependencies(buildtests_cxx matchers_test) @@ -1592,6 +1594,9 @@ if(gRPC_BUILD_TESTS) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) add_dependencies(buildtests_cxx xds_fault_injection_end2end_test) endif() + if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) + add_dependencies(buildtests_cxx xds_gcp_authn_end2end_test) + endif() add_dependencies(buildtests_cxx xds_http_filters_test) add_dependencies(buildtests_cxx xds_lb_policy_registry_test) add_dependencies(buildtests_cxx xds_listener_resource_type_test) @@ -1876,6 +1881,8 @@ add_library(grpc src/core/ext/filters/channel_idle/legacy_channel_idle_filter.cc src/core/ext/filters/fault_injection/fault_injection_filter.cc src/core/ext/filters/fault_injection/fault_injection_service_config_parser.cc + src/core/ext/filters/gcp_authentication/gcp_authentication_filter.cc + src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.cc src/core/ext/filters/http/client/http_client_filter.cc src/core/ext/filters/http/client_authority_filter.cc src/core/ext/filters/http/http_filters_plugin.cc @@ -2441,6 +2448,7 @@ add_library(grpc src/core/lib/security/credentials/external/file_external_account_credentials.cc src/core/lib/security/credentials/external/url_external_account_credentials.cc src/core/lib/security/credentials/fake/fake_credentials.cc + src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc src/core/lib/security/credentials/google_default/credentials_generic.cc src/core/lib/security/credentials/google_default/google_default_credentials.cc src/core/lib/security/credentials/iam/iam_credentials.cc @@ -2640,6 +2648,7 @@ add_library(grpc src/core/xds/grpc/xds_health_status.cc src/core/xds/grpc/xds_http_fault_filter.cc src/core/xds/grpc/xds_http_filter_registry.cc + src/core/xds/grpc/xds_http_gcp_authn_filter.cc src/core/xds/grpc/xds_http_rbac_filter.cc src/core/xds/grpc/xds_http_stateful_session_filter.cc src/core/xds/grpc/xds_lb_policy_registry.cc @@ -15335,6 +15344,55 @@ target_link_libraries(fuzzing_event_engine_unittest ) +endif() +if(gRPC_BUILD_TESTS) + +add_executable(gcp_authentication_filter_test + ${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.cc + ${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.h + ${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.grpc.pb.h + test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.cc + test/core/filters/filter_test.cc + test/core/filters/gcp_authentication_filter_test.cc +) +if(WIN32 AND MSVC) + if(BUILD_SHARED_LIBS) + target_compile_definitions(gcp_authentication_filter_test + PRIVATE + "GPR_DLL_IMPORTS" + "GRPC_DLL_IMPORTS" + ) + endif() +endif() +target_compile_features(gcp_authentication_filter_test PUBLIC cxx_std_14) +target_include_directories(gcp_authentication_filter_test + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR} + ${_gRPC_RE2_INCLUDE_DIR} + ${_gRPC_SSL_INCLUDE_DIR} + ${_gRPC_UPB_GENERATED_DIR} + ${_gRPC_UPB_GRPC_GENERATED_DIR} + ${_gRPC_UPB_INCLUDE_DIR} + ${_gRPC_XXHASH_INCLUDE_DIR} + ${_gRPC_ZLIB_INCLUDE_DIR} + third_party/googletest/googletest/include + third_party/googletest/googletest + third_party/googletest/googlemock/include + third_party/googletest/googlemock + ${_gRPC_PROTO_GENS_DIR} +) + +target_link_libraries(gcp_authentication_filter_test + ${_gRPC_ALLTARGETS_LIBRARIES} + gtest + ${_gRPC_PROTOBUF_LIBRARIES} + grpc_test_util +) + + endif() if(gRPC_BUILD_TESTS) @@ -19656,6 +19714,42 @@ target_link_libraries(loop_test ) +endif() +if(gRPC_BUILD_TESTS) + +add_executable(lru_cache_test + test/core/util/lru_cache_test.cc +) +target_compile_features(lru_cache_test PUBLIC cxx_std_14) +target_include_directories(lru_cache_test + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR} + ${_gRPC_RE2_INCLUDE_DIR} + ${_gRPC_SSL_INCLUDE_DIR} + ${_gRPC_UPB_GENERATED_DIR} + ${_gRPC_UPB_GRPC_GENERATED_DIR} + ${_gRPC_UPB_INCLUDE_DIR} + ${_gRPC_XXHASH_INCLUDE_DIR} + ${_gRPC_ZLIB_INCLUDE_DIR} + third_party/googletest/googletest/include + third_party/googletest/googletest + third_party/googletest/googlemock/include + third_party/googletest/googlemock + ${_gRPC_PROTO_GENS_DIR} +) + +target_link_libraries(lru_cache_test + ${_gRPC_ALLTARGETS_LIBRARIES} + gtest + absl::flat_hash_map + absl::any_invocable + absl::check + absl::statusor +) + + endif() if(gRPC_BUILD_TESTS) @@ -30612,7 +30706,6 @@ add_executable(test_core_security_credentials_test ${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.grpc.pb.cc ${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.h ${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.grpc.pb.h - src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc test/core/event_engine/event_engine_test_utils.cc test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.cc test/core/security/credentials_test.cc @@ -35568,6 +35661,184 @@ if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) ) +endif() +endif() +if(gRPC_BUILD_TESTS) +if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) + + add_executable(xds_gcp_authn_end2end_test + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/duplicate/echo_duplicate.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/duplicate/echo_duplicate.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/duplicate/echo_duplicate.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/duplicate/echo_duplicate.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/address.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/address.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/address.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/address.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/ads.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/ads.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/ads.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/ads.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/cluster.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/cluster.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/cluster.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/cluster.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_source.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_source.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_source.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_source.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/discovery.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/discovery.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/discovery.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/discovery.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/endpoint.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/endpoint.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/endpoint.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/endpoint.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/expr.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/expr.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/expr.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/expr.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/gcp_authn.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/gcp_authn.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/gcp_authn.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/gcp_authn.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/health_check.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/health_check.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/health_check.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/health_check.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_connection_manager.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_connection_manager.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_connection_manager.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_connection_manager.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_filter_rbac.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_filter_rbac.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_filter_rbac.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_filter_rbac.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/listener.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/listener.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/listener.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/listener.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/load_report.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/load_report.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/load_report.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/load_report.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/lrs.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/lrs.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/lrs.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/lrs.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/metadata.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/metadata.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/metadata.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/metadata.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/orca_load_report.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/orca_load_report.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/orca_load_report.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/orca_load_report.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/outlier_detection.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/outlier_detection.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/outlier_detection.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/outlier_detection.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/path.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/path.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/path.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/path.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/protocol.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/protocol.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/protocol.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/protocol.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/range.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/range.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/range.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/range.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/rbac.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/rbac.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/rbac.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/rbac.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/regex.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/regex.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/regex.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/regex.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/route.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/route.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/route.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/route.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/router.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/router.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/router.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/router.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/string.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/string.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/string.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/string.grpc.pb.h + test/cpp/end2end/test_service_impl.cc + test/cpp/end2end/xds/xds_end2end_test_lib.cc + test/cpp/end2end/xds/xds_gcp_authn_end2end_test.cc + test/cpp/end2end/xds/xds_server.cc + test/cpp/end2end/xds/xds_utils.cc + test/cpp/util/tls_test_utils.cc + ) + if(WIN32 AND MSVC) + if(BUILD_SHARED_LIBS) + target_compile_definitions(xds_gcp_authn_end2end_test + PRIVATE + "GPR_DLL_IMPORTS" + "GRPC_DLL_IMPORTS" + "GRPCXX_DLL_IMPORTS" + ) + endif() + endif() + target_compile_features(xds_gcp_authn_end2end_test PUBLIC cxx_std_14) + target_include_directories(xds_gcp_authn_end2end_test + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR} + ${_gRPC_RE2_INCLUDE_DIR} + ${_gRPC_SSL_INCLUDE_DIR} + ${_gRPC_UPB_GENERATED_DIR} + ${_gRPC_UPB_GRPC_GENERATED_DIR} + ${_gRPC_UPB_INCLUDE_DIR} + ${_gRPC_XXHASH_INCLUDE_DIR} + ${_gRPC_ZLIB_INCLUDE_DIR} + third_party/googletest/googletest/include + third_party/googletest/googletest + third_party/googletest/googlemock/include + third_party/googletest/googlemock + ${_gRPC_PROTO_GENS_DIR} + ) + + target_link_libraries(xds_gcp_authn_end2end_test + ${_gRPC_ALLTARGETS_LIBRARIES} + gtest + grpc++_test_util + ) + + endif() endif() if(gRPC_BUILD_TESTS) @@ -35605,6 +35876,10 @@ add_executable(xds_http_filters_test ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault_common.grpc.pb.cc ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault_common.pb.h ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault_common.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/gcp_authn.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/gcp_authn.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/gcp_authn.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/gcp_authn.grpc.pb.h ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_filter_rbac.pb.cc ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_filter_rbac.grpc.pb.cc ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_filter_rbac.pb.h diff --git a/Makefile b/Makefile index 87fecd8ba58..35f58665d34 100644 --- a/Makefile +++ b/Makefile @@ -694,6 +694,8 @@ LIBGRPC_SRC = \ src/core/ext/filters/channel_idle/legacy_channel_idle_filter.cc \ src/core/ext/filters/fault_injection/fault_injection_filter.cc \ src/core/ext/filters/fault_injection/fault_injection_service_config_parser.cc \ + src/core/ext/filters/gcp_authentication/gcp_authentication_filter.cc \ + src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.cc \ src/core/ext/filters/http/client/http_client_filter.cc \ src/core/ext/filters/http/client_authority_filter.cc \ src/core/ext/filters/http/http_filters_plugin.cc \ @@ -1278,6 +1280,7 @@ LIBGRPC_SRC = \ src/core/lib/security/credentials/external/file_external_account_credentials.cc \ src/core/lib/security/credentials/external/url_external_account_credentials.cc \ src/core/lib/security/credentials/fake/fake_credentials.cc \ + src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc \ src/core/lib/security/credentials/google_default/credentials_generic.cc \ src/core/lib/security/credentials/google_default/google_default_credentials.cc \ src/core/lib/security/credentials/iam/iam_credentials.cc \ @@ -1499,6 +1502,7 @@ LIBGRPC_SRC = \ src/core/xds/grpc/xds_health_status.cc \ src/core/xds/grpc/xds_http_fault_filter.cc \ src/core/xds/grpc/xds_http_filter_registry.cc \ + src/core/xds/grpc/xds_http_gcp_authn_filter.cc \ src/core/xds/grpc/xds_http_rbac_filter.cc \ src/core/xds/grpc/xds_http_stateful_session_filter.cc \ src/core/xds/grpc/xds_lb_policy_registry.cc \ diff --git a/Package.swift b/Package.swift index 719b25c4a81..a550e5f6dac 100644 --- a/Package.swift +++ b/Package.swift @@ -177,6 +177,10 @@ let package = Package( "src/core/ext/filters/fault_injection/fault_injection_filter.h", "src/core/ext/filters/fault_injection/fault_injection_service_config_parser.cc", "src/core/ext/filters/fault_injection/fault_injection_service_config_parser.h", + "src/core/ext/filters/gcp_authentication/gcp_authentication_filter.cc", + "src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h", + "src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.cc", + "src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h", "src/core/ext/filters/http/client/http_client_filter.cc", "src/core/ext/filters/http/client/http_client_filter.h", "src/core/ext/filters/http/client_authority_filter.cc", @@ -1570,6 +1574,8 @@ let package = Package( "src/core/lib/security/credentials/external/url_external_account_credentials.h", "src/core/lib/security/credentials/fake/fake_credentials.cc", "src/core/lib/security/credentials/fake/fake_credentials.h", + "src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc", + "src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h", "src/core/lib/security/credentials/google_default/credentials_generic.cc", "src/core/lib/security/credentials/google_default/google_default_credentials.cc", "src/core/lib/security/credentials/google_default/google_default_credentials.h", @@ -1940,6 +1946,7 @@ let package = Package( "src/core/util/latent_see.h", "src/core/util/linux/cpu.cc", "src/core/util/log.cc", + "src/core/util/lru_cache.h", "src/core/util/msys/tmpfile.cc", "src/core/util/posix/cpu.cc", "src/core/util/posix/string.cc", @@ -1998,6 +2005,8 @@ let package = Package( "src/core/xds/grpc/xds_http_filter.h", "src/core/xds/grpc/xds_http_filter_registry.cc", "src/core/xds/grpc/xds_http_filter_registry.h", + "src/core/xds/grpc/xds_http_gcp_authn_filter.cc", + "src/core/xds/grpc/xds_http_gcp_authn_filter.h", "src/core/xds/grpc/xds_http_rbac_filter.cc", "src/core/xds/grpc/xds_http_rbac_filter.h", "src/core/xds/grpc/xds_http_stateful_session_filter.cc", diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index 67457572413..47c8f39b36d 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -251,6 +251,8 @@ libs: - src/core/ext/filters/channel_idle/legacy_channel_idle_filter.h - src/core/ext/filters/fault_injection/fault_injection_filter.h - src/core/ext/filters/fault_injection/fault_injection_service_config_parser.h + - src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h + - src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h - src/core/ext/filters/http/client/http_client_filter.h - src/core/ext/filters/http/client_authority_filter.h - src/core/ext/filters/http/message_compress/compression_filter.h @@ -1037,6 +1039,7 @@ libs: - src/core/lib/security/credentials/external/file_external_account_credentials.h - src/core/lib/security/credentials/external/url_external_account_credentials.h - src/core/lib/security/credentials/fake/fake_credentials.h + - src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h - src/core/lib/security/credentials/google_default/google_default_credentials.h - src/core/lib/security/credentials/iam/iam_credentials.h - src/core/lib/security/credentials/insecure/insecure_credentials.h @@ -1218,6 +1221,7 @@ libs: - src/core/util/json/json_util.h - src/core/util/json/json_writer.h - src/core/util/latent_see.h + - src/core/util/lru_cache.h - src/core/util/ring_buffer.h - src/core/util/spinlock.h - src/core/util/unique_ptr_with_bitset.h @@ -1239,6 +1243,7 @@ libs: - src/core/xds/grpc/xds_http_fault_filter.h - src/core/xds/grpc/xds_http_filter.h - src/core/xds/grpc/xds_http_filter_registry.h + - src/core/xds/grpc/xds_http_gcp_authn_filter.h - src/core/xds/grpc/xds_http_rbac_filter.h - src/core/xds/grpc/xds_http_stateful_session_filter.h - src/core/xds/grpc/xds_lb_policy_registry.h @@ -1291,6 +1296,8 @@ libs: - src/core/ext/filters/channel_idle/legacy_channel_idle_filter.cc - src/core/ext/filters/fault_injection/fault_injection_filter.cc - src/core/ext/filters/fault_injection/fault_injection_service_config_parser.cc + - src/core/ext/filters/gcp_authentication/gcp_authentication_filter.cc + - src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.cc - src/core/ext/filters/http/client/http_client_filter.cc - src/core/ext/filters/http/client_authority_filter.cc - src/core/ext/filters/http/http_filters_plugin.cc @@ -1856,6 +1863,7 @@ libs: - src/core/lib/security/credentials/external/file_external_account_credentials.cc - src/core/lib/security/credentials/external/url_external_account_credentials.cc - src/core/lib/security/credentials/fake/fake_credentials.cc + - src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc - src/core/lib/security/credentials/google_default/credentials_generic.cc - src/core/lib/security/credentials/google_default/google_default_credentials.cc - src/core/lib/security/credentials/iam/iam_credentials.cc @@ -2055,6 +2063,7 @@ libs: - src/core/xds/grpc/xds_health_status.cc - src/core/xds/grpc/xds_http_fault_filter.cc - src/core/xds/grpc/xds_http_filter_registry.cc + - src/core/xds/grpc/xds_http_gcp_authn_filter.cc - src/core/xds/grpc/xds_http_rbac_filter.cc - src/core/xds/grpc/xds_http_stateful_session_filter.cc - src/core/xds/grpc/xds_lb_policy_registry.cc @@ -10882,6 +10891,23 @@ targets: - gtest - protobuf - grpc_test_util +- name: gcp_authentication_filter_test + gtest: true + build: test + language: c++ + headers: + - test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h + - test/core/filters/filter_test.h + src: + - test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.proto + - test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.cc + - test/core/filters/filter_test.cc + - test/core/filters/gcp_authentication_filter_test.cc + deps: + - gtest + - protobuf + - grpc_test_util + uses_polling: false - name: generic_end2end_test gtest: true build: test @@ -12976,6 +13002,21 @@ targets: - absl/status:statusor - gpr uses_polling: false +- name: lru_cache_test + gtest: true + build: test + language: c++ + headers: + - src/core/util/lru_cache.h + src: + - test/core/util/lru_cache_test.cc + deps: + - gtest + - absl/container:flat_hash_map + - absl/functional:any_invocable + - absl/log:check + - absl/status:statusor + uses_polling: false - name: map_pipe_test gtest: true build: test @@ -19660,7 +19701,6 @@ targets: build: test language: c++ headers: - - src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h - test/core/event_engine/event_engine_test_utils.h - test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h - test/core/test_util/cmdline.h @@ -19675,7 +19715,6 @@ targets: - test/core/test_util/tracer_util.h src: - test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.proto - - src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc - test/core/event_engine/event_engine_test_utils.cc - test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.cc - test/core/security/credentials_test.cc @@ -21834,6 +21873,65 @@ targets: - linux - posix - mac +- name: xds_gcp_authn_end2end_test + gtest: true + build: test + run: false + language: c++ + headers: + - test/core/test_util/scoped_env_var.h + - test/cpp/end2end/counted_service.h + - test/cpp/end2end/test_service_impl.h + - test/cpp/end2end/xds/xds_end2end_test_lib.h + - test/cpp/end2end/xds/xds_server.h + - test/cpp/end2end/xds/xds_utils.h + - test/cpp/util/tls_test_utils.h + src: + - src/proto/grpc/testing/duplicate/echo_duplicate.proto + - src/proto/grpc/testing/echo.proto + - src/proto/grpc/testing/echo_messages.proto + - src/proto/grpc/testing/simple_messages.proto + - src/proto/grpc/testing/xds/v3/address.proto + - src/proto/grpc/testing/xds/v3/ads.proto + - src/proto/grpc/testing/xds/v3/base.proto + - src/proto/grpc/testing/xds/v3/cluster.proto + - src/proto/grpc/testing/xds/v3/config_source.proto + - src/proto/grpc/testing/xds/v3/discovery.proto + - src/proto/grpc/testing/xds/v3/endpoint.proto + - src/proto/grpc/testing/xds/v3/expr.proto + - src/proto/grpc/testing/xds/v3/extension.proto + - src/proto/grpc/testing/xds/v3/gcp_authn.proto + - src/proto/grpc/testing/xds/v3/health_check.proto + - src/proto/grpc/testing/xds/v3/http_connection_manager.proto + - src/proto/grpc/testing/xds/v3/http_filter_rbac.proto + - src/proto/grpc/testing/xds/v3/listener.proto + - src/proto/grpc/testing/xds/v3/load_report.proto + - src/proto/grpc/testing/xds/v3/lrs.proto + - src/proto/grpc/testing/xds/v3/metadata.proto + - src/proto/grpc/testing/xds/v3/orca_load_report.proto + - src/proto/grpc/testing/xds/v3/outlier_detection.proto + - src/proto/grpc/testing/xds/v3/path.proto + - src/proto/grpc/testing/xds/v3/percent.proto + - src/proto/grpc/testing/xds/v3/protocol.proto + - src/proto/grpc/testing/xds/v3/range.proto + - src/proto/grpc/testing/xds/v3/rbac.proto + - src/proto/grpc/testing/xds/v3/regex.proto + - src/proto/grpc/testing/xds/v3/route.proto + - src/proto/grpc/testing/xds/v3/router.proto + - src/proto/grpc/testing/xds/v3/string.proto + - test/cpp/end2end/test_service_impl.cc + - test/cpp/end2end/xds/xds_end2end_test_lib.cc + - test/cpp/end2end/xds/xds_gcp_authn_end2end_test.cc + - test/cpp/end2end/xds/xds_server.cc + - test/cpp/end2end/xds/xds_utils.cc + - test/cpp/util/tls_test_utils.cc + deps: + - gtest + - grpc++_test_util + platforms: + - linux + - posix + - mac - name: xds_http_filters_test gtest: true build: test @@ -21855,6 +21953,7 @@ targets: - src/proto/grpc/testing/xds/v3/extension.proto - src/proto/grpc/testing/xds/v3/fault.proto - src/proto/grpc/testing/xds/v3/fault_common.proto + - src/proto/grpc/testing/xds/v3/gcp_authn.proto - src/proto/grpc/testing/xds/v3/http_filter_rbac.proto - src/proto/grpc/testing/xds/v3/metadata.proto - src/proto/grpc/testing/xds/v3/path.proto diff --git a/config.m4 b/config.m4 index 17886c07dd5..903e5727bbb 100644 --- a/config.m4 +++ b/config.m4 @@ -69,6 +69,8 @@ if test "$PHP_GRPC" != "no"; then src/core/ext/filters/channel_idle/legacy_channel_idle_filter.cc \ src/core/ext/filters/fault_injection/fault_injection_filter.cc \ src/core/ext/filters/fault_injection/fault_injection_service_config_parser.cc \ + src/core/ext/filters/gcp_authentication/gcp_authentication_filter.cc \ + src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.cc \ src/core/ext/filters/http/client/http_client_filter.cc \ src/core/ext/filters/http/client_authority_filter.cc \ src/core/ext/filters/http/http_filters_plugin.cc \ @@ -653,6 +655,7 @@ if test "$PHP_GRPC" != "no"; then src/core/lib/security/credentials/external/file_external_account_credentials.cc \ src/core/lib/security/credentials/external/url_external_account_credentials.cc \ src/core/lib/security/credentials/fake/fake_credentials.cc \ + src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc \ src/core/lib/security/credentials/google_default/credentials_generic.cc \ src/core/lib/security/credentials/google_default/google_default_credentials.cc \ src/core/lib/security/credentials/iam/iam_credentials.cc \ @@ -874,6 +877,7 @@ if test "$PHP_GRPC" != "no"; then src/core/xds/grpc/xds_health_status.cc \ src/core/xds/grpc/xds_http_fault_filter.cc \ src/core/xds/grpc/xds_http_filter_registry.cc \ + src/core/xds/grpc/xds_http_gcp_authn_filter.cc \ src/core/xds/grpc/xds_http_rbac_filter.cc \ src/core/xds/grpc/xds_http_stateful_session_filter.cc \ src/core/xds/grpc/xds_lb_policy_registry.cc \ @@ -1400,6 +1404,7 @@ if test "$PHP_GRPC" != "no"; then PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/census) PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/channel_idle) PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/fault_injection) + PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/gcp_authentication) PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/http) PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/http/client) PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/http/message_compress) @@ -1552,6 +1557,7 @@ if test "$PHP_GRPC" != "no"; then PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/composite) PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/external) PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/fake) + PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/gcp_service_account_identity) PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/google_default) PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/iam) PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/insecure) diff --git a/config.w32 b/config.w32 index 2f087a6943b..0734ce7cfaf 100644 --- a/config.w32 +++ b/config.w32 @@ -34,6 +34,8 @@ if (PHP_GRPC != "no") { "src\\core\\ext\\filters\\channel_idle\\legacy_channel_idle_filter.cc " + "src\\core\\ext\\filters\\fault_injection\\fault_injection_filter.cc " + "src\\core\\ext\\filters\\fault_injection\\fault_injection_service_config_parser.cc " + + "src\\core\\ext\\filters\\gcp_authentication\\gcp_authentication_filter.cc " + + "src\\core\\ext\\filters\\gcp_authentication\\gcp_authentication_service_config_parser.cc " + "src\\core\\ext\\filters\\http\\client\\http_client_filter.cc " + "src\\core\\ext\\filters\\http\\client_authority_filter.cc " + "src\\core\\ext\\filters\\http\\http_filters_plugin.cc " + @@ -618,6 +620,7 @@ if (PHP_GRPC != "no") { "src\\core\\lib\\security\\credentials\\external\\file_external_account_credentials.cc " + "src\\core\\lib\\security\\credentials\\external\\url_external_account_credentials.cc " + "src\\core\\lib\\security\\credentials\\fake\\fake_credentials.cc " + + "src\\core\\lib\\security\\credentials\\gcp_service_account_identity\\gcp_service_account_identity_credentials.cc " + "src\\core\\lib\\security\\credentials\\google_default\\credentials_generic.cc " + "src\\core\\lib\\security\\credentials\\google_default\\google_default_credentials.cc " + "src\\core\\lib\\security\\credentials\\iam\\iam_credentials.cc " + @@ -839,6 +842,7 @@ if (PHP_GRPC != "no") { "src\\core\\xds\\grpc\\xds_health_status.cc " + "src\\core\\xds\\grpc\\xds_http_fault_filter.cc " + "src\\core\\xds\\grpc\\xds_http_filter_registry.cc " + + "src\\core\\xds\\grpc\\xds_http_gcp_authn_filter.cc " + "src\\core\\xds\\grpc\\xds_http_rbac_filter.cc " + "src\\core\\xds\\grpc\\xds_http_stateful_session_filter.cc " + "src\\core\\xds\\grpc\\xds_lb_policy_registry.cc " + @@ -1394,6 +1398,7 @@ if (PHP_GRPC != "no") { FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\census"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\channel_idle"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\fault_injection"); + FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\gcp_authentication"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\http"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\http\\client"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\ext\\filters\\http\\message_compress"); @@ -1692,6 +1697,7 @@ if (PHP_GRPC != "no") { FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\composite"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\external"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\fake"); + FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\gcp_service_account_identity"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\google_default"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\iam"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\insecure"); diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec index e88102ca5d4..388db1822d3 100644 --- a/gRPC-C++.podspec +++ b/gRPC-C++.podspec @@ -302,6 +302,8 @@ Pod::Spec.new do |s| 'src/core/ext/filters/channel_idle/legacy_channel_idle_filter.h', 'src/core/ext/filters/fault_injection/fault_injection_filter.h', 'src/core/ext/filters/fault_injection/fault_injection_service_config_parser.h', + 'src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h', + 'src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h', 'src/core/ext/filters/http/client/http_client_filter.h', 'src/core/ext/filters/http/client_authority_filter.h', 'src/core/ext/filters/http/message_compress/compression_filter.h', @@ -1145,6 +1147,7 @@ Pod::Spec.new do |s| 'src/core/lib/security/credentials/external/file_external_account_credentials.h', 'src/core/lib/security/credentials/external/url_external_account_credentials.h', 'src/core/lib/security/credentials/fake/fake_credentials.h', + 'src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h', 'src/core/lib/security/credentials/google_default/google_default_credentials.h', 'src/core/lib/security/credentials/iam/iam_credentials.h', 'src/core/lib/security/credentials/insecure/insecure_credentials.h', @@ -1327,6 +1330,7 @@ Pod::Spec.new do |s| 'src/core/util/json/json_util.h', 'src/core/util/json/json_writer.h', 'src/core/util/latent_see.h', + 'src/core/util/lru_cache.h', 'src/core/util/ring_buffer.h', 'src/core/util/spinlock.h', 'src/core/util/string.h', @@ -1353,6 +1357,7 @@ Pod::Spec.new do |s| 'src/core/xds/grpc/xds_http_fault_filter.h', 'src/core/xds/grpc/xds_http_filter.h', 'src/core/xds/grpc/xds_http_filter_registry.h', + 'src/core/xds/grpc/xds_http_gcp_authn_filter.h', 'src/core/xds/grpc/xds_http_rbac_filter.h', 'src/core/xds/grpc/xds_http_stateful_session_filter.h', 'src/core/xds/grpc/xds_lb_policy_registry.h', @@ -1612,6 +1617,8 @@ Pod::Spec.new do |s| 'src/core/ext/filters/channel_idle/legacy_channel_idle_filter.h', 'src/core/ext/filters/fault_injection/fault_injection_filter.h', 'src/core/ext/filters/fault_injection/fault_injection_service_config_parser.h', + 'src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h', + 'src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h', 'src/core/ext/filters/http/client/http_client_filter.h', 'src/core/ext/filters/http/client_authority_filter.h', 'src/core/ext/filters/http/message_compress/compression_filter.h', @@ -2437,6 +2444,7 @@ Pod::Spec.new do |s| 'src/core/lib/security/credentials/external/file_external_account_credentials.h', 'src/core/lib/security/credentials/external/url_external_account_credentials.h', 'src/core/lib/security/credentials/fake/fake_credentials.h', + 'src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h', 'src/core/lib/security/credentials/google_default/google_default_credentials.h', 'src/core/lib/security/credentials/iam/iam_credentials.h', 'src/core/lib/security/credentials/insecure/insecure_credentials.h', @@ -2619,6 +2627,7 @@ Pod::Spec.new do |s| 'src/core/util/json/json_util.h', 'src/core/util/json/json_writer.h', 'src/core/util/latent_see.h', + 'src/core/util/lru_cache.h', 'src/core/util/ring_buffer.h', 'src/core/util/spinlock.h', 'src/core/util/string.h', @@ -2645,6 +2654,7 @@ Pod::Spec.new do |s| 'src/core/xds/grpc/xds_http_fault_filter.h', 'src/core/xds/grpc/xds_http_filter.h', 'src/core/xds/grpc/xds_http_filter_registry.h', + 'src/core/xds/grpc/xds_http_gcp_authn_filter.h', 'src/core/xds/grpc/xds_http_rbac_filter.h', 'src/core/xds/grpc/xds_http_stateful_session_filter.h', 'src/core/xds/grpc/xds_lb_policy_registry.h', diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec index a4751ea0e1b..3427505b1c3 100644 --- a/gRPC-Core.podspec +++ b/gRPC-Core.podspec @@ -297,6 +297,10 @@ Pod::Spec.new do |s| 'src/core/ext/filters/fault_injection/fault_injection_filter.h', 'src/core/ext/filters/fault_injection/fault_injection_service_config_parser.cc', 'src/core/ext/filters/fault_injection/fault_injection_service_config_parser.h', + 'src/core/ext/filters/gcp_authentication/gcp_authentication_filter.cc', + 'src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h', + 'src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.cc', + 'src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h', 'src/core/ext/filters/http/client/http_client_filter.cc', 'src/core/ext/filters/http/client/http_client_filter.h', 'src/core/ext/filters/http/client_authority_filter.cc', @@ -1686,6 +1690,8 @@ Pod::Spec.new do |s| 'src/core/lib/security/credentials/external/url_external_account_credentials.h', 'src/core/lib/security/credentials/fake/fake_credentials.cc', 'src/core/lib/security/credentials/fake/fake_credentials.h', + 'src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc', + 'src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h', 'src/core/lib/security/credentials/google_default/credentials_generic.cc', 'src/core/lib/security/credentials/google_default/google_default_credentials.cc', 'src/core/lib/security/credentials/google_default/google_default_credentials.h', @@ -2056,6 +2062,7 @@ Pod::Spec.new do |s| 'src/core/util/latent_see.h', 'src/core/util/linux/cpu.cc', 'src/core/util/log.cc', + 'src/core/util/lru_cache.h', 'src/core/util/msys/tmpfile.cc', 'src/core/util/posix/cpu.cc', 'src/core/util/posix/string.cc', @@ -2114,6 +2121,8 @@ Pod::Spec.new do |s| 'src/core/xds/grpc/xds_http_filter.h', 'src/core/xds/grpc/xds_http_filter_registry.cc', 'src/core/xds/grpc/xds_http_filter_registry.h', + 'src/core/xds/grpc/xds_http_gcp_authn_filter.cc', + 'src/core/xds/grpc/xds_http_gcp_authn_filter.h', 'src/core/xds/grpc/xds_http_rbac_filter.cc', 'src/core/xds/grpc/xds_http_rbac_filter.h', 'src/core/xds/grpc/xds_http_stateful_session_filter.cc', @@ -2412,6 +2421,8 @@ Pod::Spec.new do |s| 'src/core/ext/filters/channel_idle/legacy_channel_idle_filter.h', 'src/core/ext/filters/fault_injection/fault_injection_filter.h', 'src/core/ext/filters/fault_injection/fault_injection_service_config_parser.h', + 'src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h', + 'src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h', 'src/core/ext/filters/http/client/http_client_filter.h', 'src/core/ext/filters/http/client_authority_filter.h', 'src/core/ext/filters/http/message_compress/compression_filter.h', @@ -3217,6 +3228,7 @@ Pod::Spec.new do |s| 'src/core/lib/security/credentials/external/file_external_account_credentials.h', 'src/core/lib/security/credentials/external/url_external_account_credentials.h', 'src/core/lib/security/credentials/fake/fake_credentials.h', + 'src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h', 'src/core/lib/security/credentials/google_default/google_default_credentials.h', 'src/core/lib/security/credentials/iam/iam_credentials.h', 'src/core/lib/security/credentials/insecure/insecure_credentials.h', @@ -3399,6 +3411,7 @@ Pod::Spec.new do |s| 'src/core/util/json/json_util.h', 'src/core/util/json/json_writer.h', 'src/core/util/latent_see.h', + 'src/core/util/lru_cache.h', 'src/core/util/ring_buffer.h', 'src/core/util/spinlock.h', 'src/core/util/string.h', @@ -3424,6 +3437,7 @@ Pod::Spec.new do |s| 'src/core/xds/grpc/xds_http_fault_filter.h', 'src/core/xds/grpc/xds_http_filter.h', 'src/core/xds/grpc/xds_http_filter_registry.h', + 'src/core/xds/grpc/xds_http_gcp_authn_filter.h', 'src/core/xds/grpc/xds_http_rbac_filter.h', 'src/core/xds/grpc/xds_http_stateful_session_filter.h', 'src/core/xds/grpc/xds_lb_policy_registry.h', diff --git a/grpc.gemspec b/grpc.gemspec index f8c9988b45a..b56dffe06e6 100644 --- a/grpc.gemspec +++ b/grpc.gemspec @@ -183,6 +183,10 @@ Gem::Specification.new do |s| s.files += %w( src/core/ext/filters/fault_injection/fault_injection_filter.h ) s.files += %w( src/core/ext/filters/fault_injection/fault_injection_service_config_parser.cc ) s.files += %w( src/core/ext/filters/fault_injection/fault_injection_service_config_parser.h ) + s.files += %w( src/core/ext/filters/gcp_authentication/gcp_authentication_filter.cc ) + s.files += %w( src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h ) + s.files += %w( src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.cc ) + s.files += %w( src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h ) s.files += %w( src/core/ext/filters/http/client/http_client_filter.cc ) s.files += %w( src/core/ext/filters/http/client/http_client_filter.h ) s.files += %w( src/core/ext/filters/http/client_authority_filter.cc ) @@ -1572,6 +1576,8 @@ Gem::Specification.new do |s| s.files += %w( src/core/lib/security/credentials/external/url_external_account_credentials.h ) s.files += %w( src/core/lib/security/credentials/fake/fake_credentials.cc ) s.files += %w( src/core/lib/security/credentials/fake/fake_credentials.h ) + s.files += %w( src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc ) + s.files += %w( src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h ) s.files += %w( src/core/lib/security/credentials/google_default/credentials_generic.cc ) s.files += %w( src/core/lib/security/credentials/google_default/google_default_credentials.cc ) s.files += %w( src/core/lib/security/credentials/google_default/google_default_credentials.h ) @@ -1942,6 +1948,7 @@ Gem::Specification.new do |s| s.files += %w( src/core/util/latent_see.h ) s.files += %w( src/core/util/linux/cpu.cc ) s.files += %w( src/core/util/log.cc ) + s.files += %w( src/core/util/lru_cache.h ) s.files += %w( src/core/util/msys/tmpfile.cc ) s.files += %w( src/core/util/posix/cpu.cc ) s.files += %w( src/core/util/posix/string.cc ) @@ -2000,6 +2007,8 @@ Gem::Specification.new do |s| s.files += %w( src/core/xds/grpc/xds_http_filter.h ) s.files += %w( src/core/xds/grpc/xds_http_filter_registry.cc ) s.files += %w( src/core/xds/grpc/xds_http_filter_registry.h ) + s.files += %w( src/core/xds/grpc/xds_http_gcp_authn_filter.cc ) + s.files += %w( src/core/xds/grpc/xds_http_gcp_authn_filter.h ) s.files += %w( src/core/xds/grpc/xds_http_rbac_filter.cc ) s.files += %w( src/core/xds/grpc/xds_http_rbac_filter.h ) s.files += %w( src/core/xds/grpc/xds_http_stateful_session_filter.cc ) diff --git a/package.xml b/package.xml index de24f221508..6a05e868d26 100644 --- a/package.xml +++ b/package.xml @@ -165,6 +165,10 @@ + + + + @@ -1554,6 +1558,8 @@ + + @@ -1924,6 +1930,7 @@ + @@ -1982,6 +1989,8 @@ + + diff --git a/src/core/BUILD b/src/core/BUILD index 07dec2bba1d..dee69ed3bd7 100644 --- a/src/core/BUILD +++ b/src/core/BUILD @@ -4993,6 +4993,48 @@ grpc_cc_library( ], ) +grpc_cc_library( + name = "gcp_authentication_filter", + srcs = [ + "ext/filters/gcp_authentication/gcp_authentication_filter.cc", + "ext/filters/gcp_authentication/gcp_authentication_service_config_parser.cc", + ], + hdrs = [ + "ext/filters/gcp_authentication/gcp_authentication_filter.h", + "ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h", + ], + external_deps = [ + "absl/log:check", + "absl/status", + "absl/status:statusor", + "absl/strings", + "absl/types:optional", + ], + language = "c++", + deps = [ + "arena", + "channel_args", + "channel_fwd", + "context", + "gcp_service_account_identity_credentials", + "grpc_resolver_xds_attributes", + "grpc_service_config", + "json", + "json_args", + "json_object_loader", + "lru_cache", + "service_config_parser", + "validation_errors", + "xds_config", + "//:config", + "//:gpr", + "//:grpc_base", + "//:grpc_security_base", + "//:grpc_trace", + "//:ref_counted_ptr", + ], +) + grpc_cc_library( name = "grpc_lb_policy_grpclb", srcs = [ @@ -5194,6 +5236,23 @@ grpc_cc_library( ], ) +grpc_cc_library( + name = "lru_cache", + hdrs = [ + "util/lru_cache.h", + ], + external_deps = [ + "absl/container:flat_hash_map", + "absl/functional:any_invocable", + "absl/log:check", + "absl/types:optional", + ], + language = "c++", + # TODO(roth): Remove this unnecessary dependency once + # yaqs/eng/q/6510477728410501120 is resolved. + deps = ["//:grpc_public_hdrs"], +) + grpc_cc_library( name = "upb_utils", hdrs = [ @@ -5584,6 +5643,7 @@ grpc_cc_library( "xds/grpc/xds_endpoint_parser.cc", "xds/grpc/xds_http_fault_filter.cc", "xds/grpc/xds_http_filter_registry.cc", + "xds/grpc/xds_http_gcp_authn_filter.cc", "xds/grpc/xds_http_rbac_filter.cc", "xds/grpc/xds_http_stateful_session_filter.cc", "xds/grpc/xds_lb_policy_registry.cc", @@ -5603,6 +5663,7 @@ grpc_cc_library( "xds/grpc/xds_endpoint_parser.h", "xds/grpc/xds_http_fault_filter.h", "xds/grpc/xds_http_filter_registry.h", + "xds/grpc/xds_http_gcp_authn_filter.h", "xds/grpc/xds_http_rbac_filter.h", "xds/grpc/xds_http_stateful_session_filter.h", "xds/grpc/xds_lb_policy_registry.h", @@ -5696,6 +5757,7 @@ grpc_cc_library( "envoy_type_upb", "error", "error_utils", + "gcp_authentication_filter", "google_rpc_status_upb", "grpc_audit_logging", "grpc_fake_credentials", diff --git a/src/core/client_channel/client_channel.cc b/src/core/client_channel/client_channel.cc index 5e1e2ec8eb3..f700b39463e 100644 --- a/src/core/client_channel/client_channel.cc +++ b/src/core/client_channel/client_channel.cc @@ -1105,6 +1105,10 @@ void ClientChannel::OnResolverResultChangedLocked(Resolver::Result result) { service_config = std::move(*result.service_config); config_selector = result.args.GetObjectRef(); } + // Remove the config selector from channel args so that we're not holding + // unnecessary refs that cause it to be destroyed somewhere other than in + // the WorkSerializer. + result.args = result.args.Remove(GRPC_ARG_CONFIG_SELECTOR); // Note: The only case in which service_config is null here is if the // resolver returned a service config error and we don't have a previous // service config to fall back to. @@ -1138,6 +1142,7 @@ void ClientChannel::OnResolverResultChangedLocked(Resolver::Result result) { << "client_channel=" << this << ": service config not changed"; } // Create or update LB policy, as needed. + ChannelArgs new_args = result.args; resolver_result_status = CreateOrUpdateLbPolicyLocked( std::move(lb_policy_config), parsed_service_config->health_check_service_name(), std::move(result)); @@ -1146,7 +1151,7 @@ void ClientChannel::OnResolverResultChangedLocked(Resolver::Result result) { // the ConfigSelector may need the LB policy to know about new // destinations before it can send RPCs to those destinations. if (service_config_changed || config_selector_changed) { - UpdateServiceConfigInDataPlaneLocked(); + UpdateServiceConfigInDataPlaneLocked(new_args); } } // Invoke resolver callback if needed. @@ -1196,10 +1201,7 @@ absl::Status ClientChannel::CreateOrUpdateLbPolicyLocked( } update_args.config = std::move(lb_policy_config); update_args.resolution_note = std::move(result.resolution_note); - // Remove the config selector from channel args so that we're not holding - // unnecessary refs that cause it to be destroyed somewhere other than in - // the WorkSerializer. - update_args.args = result.args.Remove(GRPC_ARG_CONFIG_SELECTOR); + update_args.args = std::move(result.args); // Add health check service name to channel args. if (health_check_service_name.has_value()) { update_args.args = update_args.args.Set(GRPC_ARG_HEALTH_CHECK_SERVICE_NAME, @@ -1264,7 +1266,8 @@ void ClientChannel::UpdateServiceConfigInControlPlaneLocked( } } -void ClientChannel::UpdateServiceConfigInDataPlaneLocked() { +void ClientChannel::UpdateServiceConfigInDataPlaneLocked( + const ChannelArgs& args) { GRPC_TRACE_LOG(client_channel, INFO) << "client_channel=" << this << ": switching to ConfigSelector " << saved_config_selector_.get(); @@ -1275,7 +1278,7 @@ void ClientChannel::UpdateServiceConfigInDataPlaneLocked() { MakeRefCounted(saved_service_config_); } // Construct filter stack. - InterceptionChainBuilder builder(channel_args_.SetObject(this)); + InterceptionChainBuilder builder(args.SetObject(this)); if (idle_timeout_ != Duration::Zero()) { builder.AddOnServerTrailingMetadata([this](ServerMetadata&) { if (idle_state_.DecreaseCallCount()) StartIdleTimer(); diff --git a/src/core/client_channel/client_channel.h b/src/core/client_channel/client_channel.h index 13809c04471..7a73a2259d0 100644 --- a/src/core/client_channel/client_channel.h +++ b/src/core/client_channel/client_channel.h @@ -149,7 +149,7 @@ class ClientChannel : public Channel { RefCountedPtr config_selector, std::string lb_policy_name) ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_); - void UpdateServiceConfigInDataPlaneLocked() + void UpdateServiceConfigInDataPlaneLocked(const ChannelArgs& args) ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_); void UpdateStateLocked(grpc_connectivity_state state, diff --git a/src/core/client_channel/client_channel_filter.cc b/src/core/client_channel/client_channel_filter.cc index 0443002c520..f80f20cc88d 100644 --- a/src/core/client_channel/client_channel_filter.cc +++ b/src/core/client_channel/client_channel_filter.cc @@ -1319,6 +1319,10 @@ void ClientChannelFilter::OnResolverResultChangedLocked( service_config = std::move(*result.service_config); config_selector = result.args.GetObjectRef(); } + // Remove the config selector from channel args so that we're not holding + // unnecessary refs that cause it to be destroyed somewhere other than in the + // WorkSerializer. + result.args = result.args.Remove(GRPC_ARG_CONFIG_SELECTOR); // Note: The only case in which service_config is null here is if the resolver // returned a service config error and we don't have a previous service // config to fall back to. @@ -1349,6 +1353,7 @@ void ClientChannelFilter::OnResolverResultChangedLocked( << "chand=" << this << ": service config not changed"; } // Create or update LB policy, as needed. + ChannelArgs new_args = result.args; resolver_result_status = CreateOrUpdateLbPolicyLocked( std::move(lb_policy_config), parsed_service_config->health_check_service_name(), std::move(result)); @@ -1357,7 +1362,7 @@ void ClientChannelFilter::OnResolverResultChangedLocked( // This needs to happen after the LB policy has been updated, since // the ConfigSelector may need the LB policy to know about new // destinations before it can send RPCs to those destinations. - UpdateServiceConfigInDataPlaneLocked(); + UpdateServiceConfigInDataPlaneLocked(new_args); // TODO(ncteisen): might be worth somehow including a snippet of the // config in the trace, at the risk of bloating the trace logs. trace_strings.push_back("Service config changed"); @@ -1413,10 +1418,7 @@ absl::Status ClientChannelFilter::CreateOrUpdateLbPolicyLocked( } update_args.config = std::move(lb_policy_config); update_args.resolution_note = std::move(result.resolution_note); - // Remove the config selector from channel args so that we're not holding - // unnecessary refs that cause it to be destroyed somewhere other than in the - // WorkSerializer. - update_args.args = result.args.Remove(GRPC_ARG_CONFIG_SELECTOR); + update_args.args = std::move(result.args); // Add health check service name to channel args. if (health_check_service_name.has_value()) { update_args.args = update_args.args.Set(GRPC_ARG_HEALTH_CHECK_SERVICE_NAME, @@ -1480,7 +1482,8 @@ void ClientChannelFilter::UpdateServiceConfigInControlPlaneLocked( << saved_config_selector_.get(); } -void ClientChannelFilter::UpdateServiceConfigInDataPlaneLocked() { +void ClientChannelFilter::UpdateServiceConfigInDataPlaneLocked( + const ChannelArgs& args) { // Grab ref to service config. RefCountedPtr service_config = saved_service_config_; // Grab ref to config selector. Use default if resolver didn't supply one. @@ -1492,8 +1495,7 @@ void ClientChannelFilter::UpdateServiceConfigInDataPlaneLocked() { config_selector = MakeRefCounted(saved_service_config_); } - ChannelArgs new_args = - channel_args_.SetObject(this).SetObject(service_config); + ChannelArgs new_args = args.SetObject(this).SetObject(service_config); bool enable_retries = !new_args.WantMinimalStack() && new_args.GetBool(GRPC_ARG_ENABLE_RETRIES).value_or(true); diff --git a/src/core/client_channel/client_channel_filter.h b/src/core/client_channel/client_channel_filter.h index 9b119b4e021..7ecf787ca74 100644 --- a/src/core/client_channel/client_channel_filter.h +++ b/src/core/client_channel/client_channel_filter.h @@ -247,7 +247,7 @@ class ClientChannelFilter final { RefCountedPtr config_selector, std::string lb_policy_name) ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_); - void UpdateServiceConfigInDataPlaneLocked() + void UpdateServiceConfigInDataPlaneLocked(const ChannelArgs& args) ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_); void CreateResolverLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_); diff --git a/src/core/ext/filters/gcp_authentication/gcp_authentication_filter.cc b/src/core/ext/filters/gcp_authentication/gcp_authentication_filter.cc new file mode 100644 index 00000000000..c3a6ab3e71b --- /dev/null +++ b/src/core/ext/filters/gcp_authentication/gcp_authentication_filter.cc @@ -0,0 +1,167 @@ +// +// Copyright 2024 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h" + +#include +#include +#include + +#include "absl/log/check.h" +#include "absl/strings/str_cat.h" + +#include "src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h" +#include "src/core/lib/channel/channel_stack.h" +#include "src/core/lib/config/core_configuration.h" +#include "src/core/lib/promise/context.h" +#include "src/core/lib/resource_quota/arena.h" +#include "src/core/lib/security/context/security_context.h" +#include "src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h" +#include "src/core/lib/transport/transport.h" +#include "src/core/resolver/xds/xds_resolver_attributes.h" +#include "src/core/service_config/service_config.h" +#include "src/core/service_config/service_config_call_data.h" + +namespace grpc_core { + +const NoInterceptor GcpAuthenticationFilter::Call::OnClientToServerMessage; +const NoInterceptor GcpAuthenticationFilter::Call::OnClientToServerHalfClose; +const NoInterceptor GcpAuthenticationFilter::Call::OnServerInitialMetadata; +const NoInterceptor GcpAuthenticationFilter::Call::OnServerToClientMessage; +const NoInterceptor GcpAuthenticationFilter::Call::OnServerTrailingMetadata; +const NoInterceptor GcpAuthenticationFilter::Call::OnFinalize; + +absl::Status GcpAuthenticationFilter::Call::OnClientInitialMetadata( + ClientMetadata& /*md*/, GcpAuthenticationFilter* filter) { + // Get the cluster name chosen for this RPC. + auto* service_config_call_data = GetContext(); + auto cluster_attribute = + service_config_call_data->GetCallAttribute(); + if (cluster_attribute == nullptr) { + // Can't happen, but be defensive. + return absl::InternalError( + "GCP authentication filter: call has no xDS cluster attribute"); + } + absl::string_view cluster_name = cluster_attribute->cluster(); + if (!absl::ConsumePrefix(&cluster_name, "cluster:")) { + return absl::OkStatus(); // Cluster specifier plugin. + } + // Look up the CDS resource for the cluster. + auto it = filter->xds_config_->clusters.find(cluster_name); + if (it == filter->xds_config_->clusters.end()) { + // Can't happen, but be defensive. + return absl::InternalError( + absl::StrCat("GCP authentication filter: xDS cluster ", cluster_name, + " not found in XdsConfig")); + } + if (!it->second.ok()) { + // Cluster resource had an error, so fail the call. + // Note: For wait_for_ready calls, this does the wrong thing by + // failing the call instead of queuing it, but there's no easy + // way to queue the call here until we get a valid CDS resource, + // because once that happens, a new instance of this filter will be + // swapped in for subsequent calls, but *this* call is already tied + // to this filter instance, which will never see the update. + return absl::UnavailableError( + absl::StrCat("GCP authentication filter: CDS resource unavailable for ", + cluster_name)); + } + if (it->second->cluster == nullptr) { + // Can't happen, but be defensive. + return absl::InternalError(absl::StrCat( + "GCP authentication filter: CDS resource not present for cluster ", + cluster_name)); + } + auto& metadata_map = it->second->cluster->metadata; + const XdsMetadataValue* metadata_value = + metadata_map.Find(filter->filter_config_->filter_instance_name); + // If no audience in the cluster, then no need to add call creds. + if (metadata_value == nullptr) return absl::OkStatus(); + // If the entry is present but the wrong type, fail the RPC. + if (metadata_value->type() != XdsGcpAuthnAudienceMetadataValue::Type()) { + return absl::UnavailableError(absl::StrCat( + "GCP authentication filter: audience metadata in wrong format for " + "cluster ", + cluster_name)); + } + // Get the call creds instance. + auto creds = filter->GetCallCredentials( + DownCast(metadata_value)->url()); + // Add the call creds instance to the call. + auto* arena = GetContext(); + auto* security_ctx = DownCast( + arena->GetContext()); + if (security_ctx == nullptr) { + security_ctx = arena->New(std::move(creds)); + arena->SetContext(security_ctx); + } else { + security_ctx->creds = std::move(creds); + } + return absl::OkStatus(); +} + +const grpc_channel_filter GcpAuthenticationFilter::kFilter = + MakePromiseBasedFilter(); + +absl::StatusOr> +GcpAuthenticationFilter::Create(const ChannelArgs& args, + ChannelFilter::Args filter_args) { + auto* service_config = args.GetObject(); + if (service_config == nullptr) { + return absl::InvalidArgumentError( + "gcp_auth: no service config in channel args"); + } + auto* config = static_cast( + service_config->GetGlobalParsedConfig( + GcpAuthenticationServiceConfigParser::ParserIndex())); + if (config == nullptr) { + return absl::InvalidArgumentError("gcp_auth: parsed config not found"); + } + auto* filter_config = config->GetConfig(filter_args.instance_id()); + if (filter_config == nullptr) { + return absl::InvalidArgumentError( + "gcp_auth: filter instance ID not found in filter config"); + } + auto xds_config = args.GetObjectRef(); + if (xds_config == nullptr) { + return absl::InvalidArgumentError( + "gcp_auth: xds config not found in channel args"); + } + return std::make_unique(filter_config, + std::move(xds_config)); +} + +GcpAuthenticationFilter::GcpAuthenticationFilter( + const GcpAuthenticationParsedConfig::Config* filter_config, + RefCountedPtr xds_config) + : filter_config_(filter_config), + xds_config_(std::move(xds_config)), + cache_(filter_config->cache_size) {} + +RefCountedPtr +GcpAuthenticationFilter::GetCallCredentials(const std::string& audience) { + MutexLock lock(&mu_); + return cache_.GetOrInsert(audience, [](const std::string& audience) { + return MakeRefCounted(audience); + }); +} + +void GcpAuthenticationFilterRegister(CoreConfiguration::Builder* builder) { + GcpAuthenticationServiceConfigParser::Register(builder); +} + +} // namespace grpc_core diff --git a/src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h b/src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h new file mode 100644 index 00000000000..927e2de05ef --- /dev/null +++ b/src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h @@ -0,0 +1,82 @@ +// +// Copyright 2024 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef GRPC_SRC_CORE_EXT_FILTERS_GCP_AUTHENTICATION_GCP_AUTHENTICATION_FILTER_H +#define GRPC_SRC_CORE_EXT_FILTERS_GCP_AUTHENTICATION_GCP_AUTHENTICATION_FILTER_H + +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" + +#include "src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h" +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/channel_fwd.h" +#include "src/core/lib/channel/promise_based_filter.h" +#include "src/core/lib/gprpp/ref_counted_ptr.h" +#include "src/core/lib/gprpp/sync.h" +#include "src/core/lib/security/credentials/credentials.h" +#include "src/core/lib/transport/transport.h" +#include "src/core/resolver/xds/xds_config.h" +#include "src/core/util/lru_cache.h" + +namespace grpc_core { + +// xDS GCP Authentication filter. +// https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/gcp_authn_filter +class GcpAuthenticationFilter + : public ImplementChannelFilter { + public: + static const grpc_channel_filter kFilter; + + static absl::string_view TypeName() { return "gcp_authentication_filter"; } + + static absl::StatusOr> Create( + const ChannelArgs& args, ChannelFilter::Args filter_args); + + GcpAuthenticationFilter( + const GcpAuthenticationParsedConfig::Config* filter_config, + RefCountedPtr xds_config); + + class Call { + public: + absl::Status OnClientInitialMetadata(ClientMetadata& /*md*/, + GcpAuthenticationFilter* filter); + static const NoInterceptor OnClientToServerMessage; + static const NoInterceptor OnClientToServerHalfClose; + static const NoInterceptor OnServerInitialMetadata; + static const NoInterceptor OnServerToClientMessage; + static const NoInterceptor OnServerTrailingMetadata; + static const NoInterceptor OnFinalize; + }; + + private: + RefCountedPtr GetCallCredentials( + const std::string& audience); + + const GcpAuthenticationParsedConfig::Config* filter_config_; + const RefCountedPtr xds_config_; + + Mutex mu_; + LruCache> + cache_ ABSL_GUARDED_BY(&mu_); +}; + +} // namespace grpc_core + +#endif // GRPC_SRC_CORE_EXT_FILTERS_GCP_AUTHENTICATION_GCP_AUTHENTICATION_FILTER_H diff --git a/src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.cc b/src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.cc new file mode 100644 index 00000000000..4af33e528f7 --- /dev/null +++ b/src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.cc @@ -0,0 +1,81 @@ +// +// Copyright 2024 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include + +#include "src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h" + +#include + +#include "absl/types/optional.h" + +#include "src/core/lib/channel/channel_args.h" + +namespace grpc_core { + +const JsonLoaderInterface* GcpAuthenticationParsedConfig::Config::JsonLoader( + const JsonArgs&) { + static const auto* loader = + JsonObjectLoader() + .Field("filter_instance_name", &Config::filter_instance_name) + .OptionalField("cache_size", &Config::cache_size) + .Finish(); + return loader; +} + +void GcpAuthenticationParsedConfig::Config::JsonPostLoad( + const Json&, const JsonArgs&, ValidationErrors* errors) { + if (cache_size == 0) { + ValidationErrors::ScopedField field(errors, ".cache_size"); + errors->AddError("must be non-zero"); + } +} + +const JsonLoaderInterface* GcpAuthenticationParsedConfig::JsonLoader( + const JsonArgs&) { + static const auto* loader = + JsonObjectLoader() + .OptionalField("gcp_authentication", + &GcpAuthenticationParsedConfig::configs_) + .Finish(); + return loader; +} + +std::unique_ptr +GcpAuthenticationServiceConfigParser::ParseGlobalParams( + const ChannelArgs& args, const Json& json, ValidationErrors* errors) { + // Only parse config if the following channel arg is enabled. + if (!args.GetBool(GRPC_ARG_PARSE_GCP_AUTHENTICATION_METHOD_CONFIG) + .value_or(false)) { + return nullptr; + } + // Parse config from json. + return LoadFromJson>( + json, JsonArgs(), errors); +} + +void GcpAuthenticationServiceConfigParser::Register( + CoreConfiguration::Builder* builder) { + builder->service_config_parser()->RegisterParser( + std::make_unique()); +} + +size_t GcpAuthenticationServiceConfigParser::ParserIndex() { + return CoreConfiguration::Get().service_config_parser().GetParserIndex( + parser_name()); +} + +} // namespace grpc_core diff --git a/src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h b/src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h new file mode 100644 index 00000000000..97df008e968 --- /dev/null +++ b/src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h @@ -0,0 +1,87 @@ +// +// Copyright 2024 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef GRPC_SRC_CORE_EXT_FILTERS_GCP_AUTHENTICATION_GCP_AUTHENTICATION_SERVICE_CONFIG_PARSER_H +#define GRPC_SRC_CORE_EXT_FILTERS_GCP_AUTHENTICATION_GCP_AUTHENTICATION_SERVICE_CONFIG_PARSER_H + +#include + +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" + +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/config/core_configuration.h" +#include "src/core/lib/gprpp/validation_errors.h" +#include "src/core/service_config/service_config_parser.h" +#include "src/core/util/json/json.h" +#include "src/core/util/json/json_args.h" +#include "src/core/util/json/json_object_loader.h" + +// Channel arg key for enabling parsing fault injection via method config. +#define GRPC_ARG_PARSE_GCP_AUTHENTICATION_METHOD_CONFIG \ + "grpc.internal.parse_gcp_authentication_method_config" + +namespace grpc_core { + +class GcpAuthenticationParsedConfig : public ServiceConfigParser::ParsedConfig { + public: + struct Config { + std::string filter_instance_name; + uint64_t cache_size = 10; + + static const JsonLoaderInterface* JsonLoader(const JsonArgs&); + void JsonPostLoad(const Json&, const JsonArgs&, ValidationErrors* errors); + }; + + // Returns the config at the specified index. There might be multiple + // GCP auth filters in the list of HTTP filters at the same time. + // The order of the list is stable, and an index is used to keep track of + // their relative positions. Each filter instance uses this method to + // access the appropriate parsed config for that instance. + const Config* GetConfig(size_t index) const { + if (index >= configs_.size()) return nullptr; + return &configs_[index]; + } + + static const JsonLoaderInterface* JsonLoader(const JsonArgs&); + + private: + std::vector configs_; +}; + +class GcpAuthenticationServiceConfigParser final + : public ServiceConfigParser::Parser { + public: + absl::string_view name() const override { return parser_name(); } + std::unique_ptr ParseGlobalParams( + const ChannelArgs& args, const Json& json, + ValidationErrors* errors) override; + // Returns the parser index for the parser. + static size_t ParserIndex(); + // Registers the parser. + static void Register(CoreConfiguration::Builder* builder); + + private: + static absl::string_view parser_name() { return "gcp_auth"; } +}; + +} // namespace grpc_core + +#endif // GRPC_SRC_CORE_EXT_FILTERS_GCP_AUTHENTICATION_GCP_AUTHENTICATION_SERVICE_CONFIG_PARSER_H diff --git a/src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc b/src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc index dba35759431..11aa4e158d7 100644 --- a/src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc +++ b/src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc @@ -161,7 +161,7 @@ std::string GcpServiceAccountIdentityCallCredentials::debug_string() { ")"); } -UniqueTypeName GcpServiceAccountIdentityCallCredentials::type() const { +UniqueTypeName GcpServiceAccountIdentityCallCredentials::Type() { static UniqueTypeName::Factory kFactory("GcpServiceAccountIdentity"); return kFactory.Create(); } diff --git a/src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h b/src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h index 1581b5a499f..231e9bbccc6 100644 --- a/src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h +++ b/src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h @@ -57,6 +57,7 @@ class JwtTokenFetcherCallCredentials : public TokenFetcherCredentials { }; // GCP service account identity call credentials. +// See gRFC A83 (https://github.com/grpc/proposal/pull/438). class GcpServiceAccountIdentityCallCredentials : public JwtTokenFetcherCallCredentials { public: @@ -65,7 +66,9 @@ class GcpServiceAccountIdentityCallCredentials std::string debug_string() override; - UniqueTypeName type() const override; + static UniqueTypeName Type(); + + UniqueTypeName type() const override { return Type(); } absl::string_view audience() const { return audience_; } diff --git a/src/core/plugin_registry/grpc_plugin_registry_extra.cc b/src/core/plugin_registry/grpc_plugin_registry_extra.cc index a6013c24965..5b9dde4cf1e 100644 --- a/src/core/plugin_registry/grpc_plugin_registry_extra.cc +++ b/src/core/plugin_registry/grpc_plugin_registry_extra.cc @@ -23,6 +23,8 @@ namespace grpc_core { #ifndef GRPC_NO_XDS extern void RbacFilterRegister(CoreConfiguration::Builder* builder); extern void StatefulSessionFilterRegister(CoreConfiguration::Builder* builder); +extern void GcpAuthenticationFilterRegister( + CoreConfiguration::Builder* builder); extern void RegisterXdsChannelStackModifier( CoreConfiguration::Builder* builder); extern void RegisterChannelDefaultCreds(CoreConfiguration::Builder* builder); @@ -47,6 +49,7 @@ void RegisterExtraFilters(CoreConfiguration::Builder* builder) { // re2 library by default RbacFilterRegister(builder); StatefulSessionFilterRegister(builder); + GcpAuthenticationFilterRegister(builder); RegisterXdsChannelStackModifier(builder); RegisterChannelDefaultCreds(builder); RegisterXdsResolver(builder); diff --git a/src/core/resolver/xds/xds_resolver.cc b/src/core/resolver/xds/xds_resolver.cc index 9cb49098534..9013474cca7 100644 --- a/src/core/resolver/xds/xds_resolver.cc +++ b/src/core/resolver/xds/xds_resolver.cc @@ -510,7 +510,7 @@ XdsResolver::RouteConfigData::CreateMethodConfig( // Handle xDS HTTP filters. const auto& hcm = absl::get( resolver->current_config_->listener->listener); - auto result = XdsRouting::GeneratePerHTTPFilterConfigs( + auto result = XdsRouting::GeneratePerHTTPFilterConfigsForMethodConfig( static_cast(resolver->xds_client_->bootstrap()) .http_filter_registry(), hcm.http_filters, *resolver->current_config_->virtual_host, route, @@ -1041,18 +1041,27 @@ XdsResolver::CreateServiceConfig() { } std::vector config_parts; config_parts.push_back( - "{\n" - " \"loadBalancingConfig\":[\n" - " { \"xds_cluster_manager_experimental\":{\n" - " \"children\":{\n"); - config_parts.push_back(absl::StrJoin(clusters, ",\n")); - config_parts.push_back( - " }\n" - " } }\n" - " ]\n" - "}"); - std::string json = absl::StrJoin(config_parts, ""); - return ServiceConfigImpl::Create(args_, json.c_str()); + absl::StrCat(" \"loadBalancingConfig\":[\n" + " { \"xds_cluster_manager_experimental\":{\n" + " \"children\":{\n", + absl::StrJoin(clusters, ",\n"), + " }\n" + " } }\n" + " ]")); + auto& hcm = absl::get( + current_config_->listener->listener); + auto filter_configs = + XdsRouting::GeneratePerHTTPFilterConfigsForServiceConfig( + static_cast(xds_client_->bootstrap()) + .http_filter_registry(), + hcm.http_filters, args_); + if (!filter_configs.ok()) return filter_configs.status(); + for (const auto& p : filter_configs->per_filter_configs) { + config_parts.emplace_back(absl::StrCat( + " \"", p.first, "\": [\n", absl::StrJoin(p.second, ",\n"), "\n ]")); + } + std::string json = absl::StrCat("{", absl::StrJoin(config_parts, ",\n"), "}"); + return ServiceConfigImpl::Create(filter_configs->args, json.c_str()); } void XdsResolver::GenerateResult() { diff --git a/src/core/server/xds_server_config_fetcher.cc b/src/core/server/xds_server_config_fetcher.cc index 52a37031712..1bd258bd3d3 100644 --- a/src/core/server/xds_server_config_fetcher.cc +++ b/src/core/server/xds_server_config_fetcher.cc @@ -1176,7 +1176,7 @@ XdsServerConfigFetcher::ListenerWatcher::FilterChainMatchManager:: config_selector_route.unsupported_action = absl::get_if( &route.action) == nullptr; - auto result = XdsRouting::GeneratePerHTTPFilterConfigs( + auto result = XdsRouting::GeneratePerHTTPFilterConfigsForMethodConfig( http_filter_registry, http_filters, vhost, route, nullptr, ChannelArgs()); if (!result.ok()) return result.status(); diff --git a/src/core/service_config/service_config_call_data.h b/src/core/service_config/service_config_call_data.h index a376b751bdb..6d11caedab0 100644 --- a/src/core/service_config/service_config_call_data.h +++ b/src/core/service_config/service_config_call_data.h @@ -35,9 +35,8 @@ namespace grpc_core { /// Stores the service config data associated with an individual call. -/// A pointer to this object is stored in the call_context -/// GRPC_CONTEXT_SERVICE_CONFIG_CALL_DATA element, so that filters can -/// easily access method and global parameters for the call. +/// A pointer to this object is stored in the call context, so that +/// filters can easily access method and global parameters for the call. /// /// Must be accessed when holding the call combiner (legacy filter) or from /// inside the activity (promise-based filter). diff --git a/src/core/util/http_client/httpcli.cc b/src/core/util/http_client/httpcli.cc index 4220cb58b65..e28f9b8b758 100644 --- a/src/core/util/http_client/httpcli.cc +++ b/src/core/util/http_client/httpcli.cc @@ -70,14 +70,14 @@ OrphanablePtr HttpRequest::Get( grpc_polling_entity* pollent, const grpc_http_request* request, Timestamp deadline, grpc_closure* on_done, grpc_http_response* response, RefCountedPtr channel_creds) { - absl::optional> test_only_generate_response; + absl::optional> test_only_generate_response; if (g_get_override != nullptr) { test_only_generate_response = [request, uri, deadline, on_done, response]() { // Note that capturing request here assumes it will remain alive // until after Start is called. This avoids making a copy as this // code path is only used for test mocks. - g_get_override(request, uri, deadline, on_done, response); + return g_get_override(request, uri, deadline, on_done, response); }; } std::string name = @@ -95,13 +95,13 @@ OrphanablePtr HttpRequest::Post( grpc_polling_entity* pollent, const grpc_http_request* request, Timestamp deadline, grpc_closure* on_done, grpc_http_response* response, RefCountedPtr channel_creds) { - absl::optional> test_only_generate_response; + absl::optional> test_only_generate_response; if (g_post_override != nullptr) { test_only_generate_response = [request, uri, deadline, on_done, response]() { - g_post_override(request, uri, - absl::string_view(request->body, request->body_length), - deadline, on_done, response); + return g_post_override( + request, uri, absl::string_view(request->body, request->body_length), + deadline, on_done, response); }; } std::string name = @@ -119,13 +119,13 @@ OrphanablePtr HttpRequest::Put( grpc_polling_entity* pollent, const grpc_http_request* request, Timestamp deadline, grpc_closure* on_done, grpc_http_response* response, RefCountedPtr channel_creds) { - absl::optional> test_only_generate_response; + absl::optional> test_only_generate_response; if (g_put_override != nullptr) { test_only_generate_response = [request, uri, deadline, on_done, response]() { - g_put_override(request, uri, - absl::string_view(request->body, request->body_length), - deadline, on_done, response); + return g_put_override( + request, uri, absl::string_view(request->body, request->body_length), + deadline, on_done, response); }; } std::string name = @@ -155,7 +155,7 @@ HttpRequest::HttpRequest( URI uri, const grpc_slice& request_text, grpc_http_response* response, Timestamp deadline, const grpc_channel_args* channel_args, grpc_closure* on_done, grpc_polling_entity* pollent, const char* name, - absl::optional> test_only_generate_response, + absl::optional> test_only_generate_response, RefCountedPtr channel_creds) : uri_(std::move(uri)), request_text_(request_text), @@ -202,8 +202,7 @@ HttpRequest::~HttpRequest() { void HttpRequest::Start() { MutexLock lock(&mu_); if (test_only_generate_response_.has_value()) { - test_only_generate_response_.value()(); - return; + if (test_only_generate_response_.value()()) return; } Ref().release(); // ref held by pending DNS resolution dns_request_handle_ = resolver_->LookupHostname( diff --git a/src/core/util/http_client/httpcli.h b/src/core/util/http_client/httpcli.h index dd676a5b1c9..c5939da61fa 100644 --- a/src/core/util/http_client/httpcli.h +++ b/src/core/util/http_client/httpcli.h @@ -165,7 +165,7 @@ class HttpRequest : public InternallyRefCounted { grpc_http_response* response, Timestamp deadline, const grpc_channel_args* channel_args, grpc_closure* on_done, grpc_polling_entity* pollent, const char* name, - absl::optional> test_only_generate_response, + absl::optional> test_only_generate_response, RefCountedPtr channel_creds); ~HttpRequest() override; @@ -250,7 +250,7 @@ class HttpRequest : public InternallyRefCounted { ResourceQuotaRefPtr resource_quota_; grpc_polling_entity* pollent_; grpc_pollset_set* pollset_set_; - const absl::optional> test_only_generate_response_; + const absl::optional> test_only_generate_response_; Mutex mu_; RefCountedPtr handshake_mgr_ ABSL_GUARDED_BY(mu_); bool cancelled_ ABSL_GUARDED_BY(mu_) = false; diff --git a/src/core/util/lru_cache.h b/src/core/util/lru_cache.h new file mode 100644 index 00000000000..d0bf5934f4b --- /dev/null +++ b/src/core/util/lru_cache.h @@ -0,0 +1,104 @@ +// +// Copyright 2024 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef GRPC_SRC_CORE_UTIL_LRU_CACHE_H +#define GRPC_SRC_CORE_UTIL_LRU_CACHE_H + +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/functional/any_invocable.h" +#include "absl/log/check.h" +#include "absl/types/optional.h" + +namespace grpc_core { + +// A simple LRU cache. Retains at most max_size entries. +// Caller is responsible for synchronization. +// TODO(roth): Support heterogenous lookups. +template +class LruCache { + public: + explicit LruCache(size_t max_size) : max_size_(max_size) { + CHECK_GT(max_size, 0UL); + } + + // Returns the value for key, or nullopt if not present. + absl::optional Get(Key key); + + // If key is present in the cache, returns the corresponding value. + // Otherwise, inserts a new entry in the map, calling create() to + // construct the new value. If inserting a new entry causes the cache + // to be too large, removes the least recently used entry. + Value GetOrInsert(Key key, absl::AnyInvocable create); + + private: + struct CacheEntry { + Value value; + typename std::list::iterator lru_iterator; + + explicit CacheEntry(Value v) : value(std::move(v)) {} + }; + + const size_t max_size_; + absl::flat_hash_map cache_; + std::list lru_list_; +}; + +// +// implementation -- no user-serviceable parts below +// + +template +absl::optional LruCache::Get(Key key) { + auto it = cache_.find(key); + if (it == cache_.end()) return absl::nullopt; + // Found the entry. Move the entry to the end of the LRU list. + auto new_lru_it = lru_list_.insert(lru_list_.end(), *it->second.lru_iterator); + lru_list_.erase(it->second.lru_iterator); + it->second.lru_iterator = new_lru_it; + return it->second.value; +} + +template +Value LruCache::GetOrInsert( + Key key, absl::AnyInvocable create) { + auto value = Get(key); + if (value.has_value()) return std::move(*value); + // Entry not found. We'll need to insert a new entry. + // If the cache is at max size, remove the least recently used entry. + if (cache_.size() == max_size_) { + auto lru_it = lru_list_.begin(); + CHECK(lru_it != lru_list_.end()); + auto cache_it = cache_.find(*lru_it); + CHECK(cache_it != cache_.end()); + cache_.erase(cache_it); + lru_list_.pop_front(); + } + // Create a new entry, insert it, and return it. + auto it = cache_ + .emplace(std::piecewise_construct, std::forward_as_tuple(key), + std::forward_as_tuple(create(key))) + .first; + it->second.lru_iterator = lru_list_.insert(lru_list_.end(), std::move(key)); + return it->second.value; +} + +} // namespace grpc_core + +#endif // GRPC_SRC_CORE_UTIL_LRU_CACHE_H diff --git a/src/core/xds/grpc/xds_http_fault_filter.cc b/src/core/xds/grpc/xds_http_fault_filter.cc index e46eeb79f1b..6bc74df8203 100644 --- a/src/core/xds/grpc/xds_http_fault_filter.cc +++ b/src/core/xds/grpc/xds_http_fault_filter.cc @@ -87,6 +87,7 @@ void XdsHttpFaultFilter::PopulateSymtab(upb_DefPool* symtab) const { absl::optional XdsHttpFaultFilter::GenerateFilterConfig( + absl::string_view /*instance_name*/, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const { absl::string_view* serialized_filter_config = @@ -208,11 +209,13 @@ XdsHttpFaultFilter::GenerateFilterConfig( absl::optional XdsHttpFaultFilter::GenerateFilterConfigOverride( + absl::string_view instance_name, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const { // HTTPFault filter has the same message type in HTTP connection manager's // filter config and in overriding filter config field. - return GenerateFilterConfig(context, std::move(extension), errors); + return GenerateFilterConfig(instance_name, context, std::move(extension), + errors); } void XdsHttpFaultFilter::AddFilter(InterceptionChainBuilder& builder) const { @@ -229,7 +232,7 @@ ChannelArgs XdsHttpFaultFilter::ModifyChannelArgs( } absl::StatusOr -XdsHttpFaultFilter::GenerateServiceConfig( +XdsHttpFaultFilter::GenerateMethodConfig( const FilterConfig& hcm_filter_config, const FilterConfig* filter_config_override) const { Json policy_json = filter_config_override != nullptr @@ -239,4 +242,10 @@ XdsHttpFaultFilter::GenerateServiceConfig( return ServiceConfigJsonEntry{"faultInjectionPolicy", JsonDump(policy_json)}; } +absl::StatusOr +XdsHttpFaultFilter::GenerateServiceConfig( + const FilterConfig& /*hcm_filter_config*/) const { + return ServiceConfigJsonEntry{"", ""}; +} + } // namespace grpc_core diff --git a/src/core/xds/grpc/xds_http_fault_filter.h b/src/core/xds/grpc/xds_http_fault_filter.h index b99b1d30085..c269ff17f05 100644 --- a/src/core/xds/grpc/xds_http_fault_filter.h +++ b/src/core/xds/grpc/xds_http_fault_filter.h @@ -39,17 +39,21 @@ class XdsHttpFaultFilter final : public XdsHttpFilterImpl { absl::string_view OverrideConfigProtoName() const override; void PopulateSymtab(upb_DefPool* symtab) const override; absl::optional GenerateFilterConfig( + absl::string_view /*instance_name*/, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const override; absl::optional GenerateFilterConfigOverride( + absl::string_view /*instance_name*/, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const override; void AddFilter(InterceptionChainBuilder& builder) const override; const grpc_channel_filter* channel_filter() const override; ChannelArgs ModifyChannelArgs(const ChannelArgs& args) const override; - absl::StatusOr GenerateServiceConfig( + absl::StatusOr GenerateMethodConfig( const FilterConfig& hcm_filter_config, const FilterConfig* filter_config_override) const override; + absl::StatusOr GenerateServiceConfig( + const FilterConfig& hcm_filter_config) const override; bool IsSupportedOnClients() const override { return true; } bool IsSupportedOnServers() const override { return false; } }; diff --git a/src/core/xds/grpc/xds_http_filter.h b/src/core/xds/grpc/xds_http_filter.h index 6e8c79d1284..8a9c54c6ef8 100644 --- a/src/core/xds/grpc/xds_http_filter.h +++ b/src/core/xds/grpc/xds_http_filter.h @@ -60,6 +60,7 @@ class XdsHttpFilterImpl { // The value of this field in the method config will be a JSON array, // which will be populated with the elements returned by each filter // instance. + // Entry will be skipped if this field is empty. std::string service_config_field_name; // The element to add to the JSON array. std::string element; @@ -80,12 +81,14 @@ class XdsHttpFilterImpl { // Generates a Config from the xDS filter config proto. // Used for the top-level config in the HCM HTTP filter list. virtual absl::optional GenerateFilterConfig( + absl::string_view instance_name, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const = 0; // Generates a Config from the xDS filter config proto. // Used for the typed_per_filter_config override in VirtualHost and Route. virtual absl::optional GenerateFilterConfigOverride( + absl::string_view instance_name, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const = 0; @@ -106,10 +109,17 @@ class XdsHttpFilterImpl { // The filter_config_override comes from the first of the ClusterWeight, // Route, or VirtualHost entries that it is found in, or null if // there is no override in any of those locations. - virtual absl::StatusOr GenerateServiceConfig( + virtual absl::StatusOr GenerateMethodConfig( const FilterConfig& hcm_filter_config, const FilterConfig* filter_config_override) const = 0; + // Function to convert the Configs into a JSON string to be added to the + // top level of the service config. + // The hcm_filter_config comes from the HttpConnectionManager config. + // Currently used only on the client side. + virtual absl::StatusOr GenerateServiceConfig( + const FilterConfig& hcm_filter_config) const = 0; + // Returns true if the filter is supported on clients; false otherwise virtual bool IsSupportedOnClients() const = 0; diff --git a/src/core/xds/grpc/xds_http_filter_registry.cc b/src/core/xds/grpc/xds_http_filter_registry.cc index 9a6f18e226b..9decfded32d 100644 --- a/src/core/xds/grpc/xds_http_filter_registry.cc +++ b/src/core/xds/grpc/xds_http_filter_registry.cc @@ -29,8 +29,10 @@ #include "src/core/util/json/json.h" #include "src/core/xds/grpc/xds_http_fault_filter.h" +#include "src/core/xds/grpc/xds_http_gcp_authn_filter.h" #include "src/core/xds/grpc/xds_http_rbac_filter.h" #include "src/core/xds/grpc/xds_http_stateful_session_filter.h" +#include "src/core/xds/grpc/xds_metadata_parser.h" namespace grpc_core { @@ -52,6 +54,7 @@ void XdsHttpRouterFilter::PopulateSymtab(upb_DefPool* symtab) const { absl::optional XdsHttpRouterFilter::GenerateFilterConfig( + absl::string_view /*instance_name*/, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const { absl::string_view* serialized_filter_config = @@ -71,6 +74,7 @@ XdsHttpRouterFilter::GenerateFilterConfig( absl::optional XdsHttpRouterFilter::GenerateFilterConfigOverride( + absl::string_view /*instance_name*/, const XdsResourceType::DecodeContext& /*context*/, XdsExtension /*extension*/, ValidationErrors* errors) const { errors->AddError("router filter does not support config override"); @@ -87,6 +91,9 @@ XdsHttpFilterRegistry::XdsHttpFilterRegistry(bool register_builtins) { RegisterFilter(std::make_unique()); RegisterFilter(std::make_unique()); RegisterFilter(std::make_unique()); + if (XdsGcpAuthFilterEnabled()) { + RegisterFilter(std::make_unique()); + } } } diff --git a/src/core/xds/grpc/xds_http_filter_registry.h b/src/core/xds/grpc/xds_http_filter_registry.h index d25dc378b8b..37950d01f15 100644 --- a/src/core/xds/grpc/xds_http_filter_registry.h +++ b/src/core/xds/grpc/xds_http_filter_registry.h @@ -45,19 +45,26 @@ class XdsHttpRouterFilter final : public XdsHttpFilterImpl { absl::string_view OverrideConfigProtoName() const override; void PopulateSymtab(upb_DefPool* symtab) const override; absl::optional GenerateFilterConfig( + absl::string_view /*instance_name*/, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const override; absl::optional GenerateFilterConfigOverride( + absl::string_view /*instance_name*/, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const override; void AddFilter(InterceptionChainBuilder& /*builder*/) const override {} const grpc_channel_filter* channel_filter() const override { return nullptr; } - absl::StatusOr GenerateServiceConfig( + absl::StatusOr GenerateMethodConfig( const FilterConfig& /*hcm_filter_config*/, const FilterConfig* /*filter_config_override*/) const override { // This will never be called, since channel_filter() returns null. return absl::UnimplementedError("router filter should never be called"); } + absl::StatusOr GenerateServiceConfig( + const FilterConfig& /*hcm_filter_config*/) const override { + // This will never be called, since channel_filter() returns null. + return absl::UnimplementedError("router filter should never be called"); + } bool IsSupportedOnClients() const override { return true; } bool IsSupportedOnServers() const override { return true; } bool IsTerminalFilter() const override { return true; } diff --git a/src/core/xds/grpc/xds_http_gcp_authn_filter.cc b/src/core/xds/grpc/xds_http_gcp_authn_filter.cc new file mode 100644 index 00000000000..3378284c9c2 --- /dev/null +++ b/src/core/xds/grpc/xds_http_gcp_authn_filter.cc @@ -0,0 +1,142 @@ +// +// Copyright 2024 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "src/core/xds/grpc/xds_http_gcp_authn_filter.h" + +#include +#include + +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/types/variant.h" +#include "envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.upb.h" +#include "envoy/extensions/filters/http/gcp_authn/v3/gcp_authn.upbdefs.h" + +#include + +#include "src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h" +#include "src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h" +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/gprpp/validation_errors.h" +#include "src/core/util/json/json.h" +#include "src/core/util/json/json_writer.h" +#include "src/core/xds/grpc/xds_common_types.h" +#include "src/core/xds/grpc/xds_common_types_parser.h" +#include "src/core/xds/grpc/xds_http_filter.h" + +namespace grpc_core { + +absl::string_view XdsHttpGcpAuthnFilter::ConfigProtoName() const { + return "envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig"; +} + +absl::string_view XdsHttpGcpAuthnFilter::OverrideConfigProtoName() const { + return ""; +} + +void XdsHttpGcpAuthnFilter::PopulateSymtab(upb_DefPool* symtab) const { + envoy_extensions_filters_http_gcp_authn_v3_GcpAuthnFilterConfig_getmsgdef( + symtab); +} + +namespace { + +Json::Object ValidateFilterConfig( + absl::string_view instance_name, + const envoy_extensions_filters_http_gcp_authn_v3_GcpAuthnFilterConfig* + gcp_auth, + ValidationErrors* errors) { + Json::Object config = { + {"filter_instance_name", Json::FromString(std::string(instance_name))}}; + const auto* cache_config = + envoy_extensions_filters_http_gcp_authn_v3_GcpAuthnFilterConfig_cache_config( + gcp_auth); + if (cache_config == nullptr) return config; + uint64_t cache_size = + ParseUInt64Value( + envoy_extensions_filters_http_gcp_authn_v3_TokenCacheConfig_cache_size( + cache_config)) + .value_or(10); + if (cache_size == 0 || cache_size >= INT64_MAX) { + ValidationErrors::ScopedField field(errors, ".cache_config.cache_size"); + errors->AddError("must be in the range (0, INT64_MAX)"); + } + config["cache_size"] = Json::FromNumber(cache_size); + return config; +} + +} // namespace + +absl::optional +XdsHttpGcpAuthnFilter::GenerateFilterConfig( + absl::string_view instance_name, + const XdsResourceType::DecodeContext& context, XdsExtension extension, + ValidationErrors* errors) const { + absl::string_view* serialized_filter_config = + absl::get_if(&extension.value); + if (serialized_filter_config == nullptr) { + errors->AddError("could not parse GCP auth filter config"); + return absl::nullopt; + } + auto* gcp_auth = + envoy_extensions_filters_http_gcp_authn_v3_GcpAuthnFilterConfig_parse( + serialized_filter_config->data(), serialized_filter_config->size(), + context.arena); + if (gcp_auth == nullptr) { + errors->AddError("could not parse GCP auth filter config"); + return absl::nullopt; + } + return FilterConfig{ConfigProtoName(), Json::FromObject(ValidateFilterConfig( + instance_name, gcp_auth, errors))}; +} + +absl::optional +XdsHttpGcpAuthnFilter::GenerateFilterConfigOverride( + absl::string_view /*instance_name*/, + const XdsResourceType::DecodeContext& /*context*/, + XdsExtension /*extension*/, ValidationErrors* errors) const { + errors->AddError("GCP auth filter does not support config override"); + return absl::nullopt; +} + +void XdsHttpGcpAuthnFilter::AddFilter(InterceptionChainBuilder& builder) const { + builder.Add(); +} + +const grpc_channel_filter* XdsHttpGcpAuthnFilter::channel_filter() const { + return &GcpAuthenticationFilter::kFilter; +} + +ChannelArgs XdsHttpGcpAuthnFilter::ModifyChannelArgs( + const ChannelArgs& args) const { + return args.Set(GRPC_ARG_PARSE_GCP_AUTHENTICATION_METHOD_CONFIG, 1); +} + +absl::StatusOr +XdsHttpGcpAuthnFilter::GenerateMethodConfig( + const FilterConfig& /*hcm_filter_config*/, + const FilterConfig* /*filter_config_override*/) const { + return ServiceConfigJsonEntry{"", ""}; +} + +absl::StatusOr +XdsHttpGcpAuthnFilter::GenerateServiceConfig( + const FilterConfig& hcm_filter_config) const { + return ServiceConfigJsonEntry{"gcp_authentication", + JsonDump(hcm_filter_config.config)}; +} + +} // namespace grpc_core diff --git a/src/core/xds/grpc/xds_http_gcp_authn_filter.h b/src/core/xds/grpc/xds_http_gcp_authn_filter.h new file mode 100644 index 00000000000..662e2c456a7 --- /dev/null +++ b/src/core/xds/grpc/xds_http_gcp_authn_filter.h @@ -0,0 +1,61 @@ +// +// Copyright 2024 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_GCP_AUTHN_FILTER_H +#define GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_GCP_AUTHN_FILTER_H + +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "upb/reflection/def.h" + +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/channel_fwd.h" +#include "src/core/lib/gprpp/validation_errors.h" +#include "src/core/xds/grpc/xds_common_types.h" +#include "src/core/xds/grpc/xds_http_filter.h" +#include "src/core/xds/xds_client/xds_resource_type.h" + +namespace grpc_core { + +class XdsHttpGcpAuthnFilter final : public XdsHttpFilterImpl { + public: + absl::string_view ConfigProtoName() const override; + absl::string_view OverrideConfigProtoName() const override; + void PopulateSymtab(upb_DefPool* symtab) const override; + absl::optional GenerateFilterConfig( + absl::string_view instance_name, + const XdsResourceType::DecodeContext& context, XdsExtension extension, + ValidationErrors* errors) const override; + absl::optional GenerateFilterConfigOverride( + absl::string_view instance_name, + const XdsResourceType::DecodeContext& context, XdsExtension extension, + ValidationErrors* errors) const override; + void AddFilter(InterceptionChainBuilder& builder) const override; + const grpc_channel_filter* channel_filter() const override; + ChannelArgs ModifyChannelArgs(const ChannelArgs& args) const override; + absl::StatusOr GenerateMethodConfig( + const FilterConfig& hcm_filter_config, + const FilterConfig* filter_config_override) const override; + absl::StatusOr GenerateServiceConfig( + const FilterConfig& hcm_filter_config) const override; + bool IsSupportedOnClients() const override { return true; } + bool IsSupportedOnServers() const override { return false; } +}; + +} // namespace grpc_core + +#endif // GRPC_SRC_CORE_XDS_GRPC_XDS_HTTP_GCP_AUTHN_FILTER_H diff --git a/src/core/xds/grpc/xds_http_rbac_filter.cc b/src/core/xds/grpc/xds_http_rbac_filter.cc index 64a793abad7..4508ae41b9b 100644 --- a/src/core/xds/grpc/xds_http_rbac_filter.cc +++ b/src/core/xds/grpc/xds_http_rbac_filter.cc @@ -515,6 +515,7 @@ void XdsHttpRbacFilter::PopulateSymtab(upb_DefPool* symtab) const { absl::optional XdsHttpRbacFilter::GenerateFilterConfig( + absl::string_view /*instance_name*/, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const { absl::string_view* serialized_filter_config = @@ -536,6 +537,7 @@ XdsHttpRbacFilter::GenerateFilterConfig( absl::optional XdsHttpRbacFilter::GenerateFilterConfigOverride( + absl::string_view /*instance_name*/, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const { absl::string_view* serialized_filter_config = @@ -578,7 +580,7 @@ ChannelArgs XdsHttpRbacFilter::ModifyChannelArgs( } absl::StatusOr -XdsHttpRbacFilter::GenerateServiceConfig( +XdsHttpRbacFilter::GenerateMethodConfig( const FilterConfig& hcm_filter_config, const FilterConfig* filter_config_override) const { const Json& policy_json = filter_config_override != nullptr @@ -588,4 +590,10 @@ XdsHttpRbacFilter::GenerateServiceConfig( return ServiceConfigJsonEntry{"rbacPolicy", JsonDump(policy_json)}; } +absl::StatusOr +XdsHttpRbacFilter::GenerateServiceConfig( + const FilterConfig& /*hcm_filter_config*/) const { + return ServiceConfigJsonEntry{"", ""}; +} + } // namespace grpc_core diff --git a/src/core/xds/grpc/xds_http_rbac_filter.h b/src/core/xds/grpc/xds_http_rbac_filter.h index 8aac5bf1d63..c7550575b72 100644 --- a/src/core/xds/grpc/xds_http_rbac_filter.h +++ b/src/core/xds/grpc/xds_http_rbac_filter.h @@ -39,17 +39,21 @@ class XdsHttpRbacFilter final : public XdsHttpFilterImpl { absl::string_view OverrideConfigProtoName() const override; void PopulateSymtab(upb_DefPool* symtab) const override; absl::optional GenerateFilterConfig( + absl::string_view /*instance_name*/, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const override; absl::optional GenerateFilterConfigOverride( + absl::string_view /*instance_name*/, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const override; void AddFilter(InterceptionChainBuilder& builder) const override; const grpc_channel_filter* channel_filter() const override; ChannelArgs ModifyChannelArgs(const ChannelArgs& args) const override; - absl::StatusOr GenerateServiceConfig( + absl::StatusOr GenerateMethodConfig( const FilterConfig& hcm_filter_config, const FilterConfig* filter_config_override) const override; + absl::StatusOr GenerateServiceConfig( + const FilterConfig& hcm_filter_config) const override; bool IsSupportedOnClients() const override { return false; } bool IsSupportedOnServers() const override { return true; } }; diff --git a/src/core/xds/grpc/xds_http_stateful_session_filter.cc b/src/core/xds/grpc/xds_http_stateful_session_filter.cc index dd239bc0094..8216d51ad06 100644 --- a/src/core/xds/grpc/xds_http_stateful_session_filter.cc +++ b/src/core/xds/grpc/xds_http_stateful_session_filter.cc @@ -141,6 +141,7 @@ Json::Object ValidateStatefulSession( absl::optional XdsHttpStatefulSessionFilter::GenerateFilterConfig( + absl::string_view /*instance_name*/, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const { absl::string_view* serialized_filter_config = @@ -164,6 +165,7 @@ XdsHttpStatefulSessionFilter::GenerateFilterConfig( absl::optional XdsHttpStatefulSessionFilter::GenerateFilterConfigOverride( + absl::string_view /*instance_name*/, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const { absl::string_view* serialized_filter_config = @@ -211,7 +213,7 @@ ChannelArgs XdsHttpStatefulSessionFilter::ModifyChannelArgs( } absl::StatusOr -XdsHttpStatefulSessionFilter::GenerateServiceConfig( +XdsHttpStatefulSessionFilter::GenerateMethodConfig( const FilterConfig& hcm_filter_config, const FilterConfig* filter_config_override) const { const Json& config = filter_config_override != nullptr @@ -220,4 +222,10 @@ XdsHttpStatefulSessionFilter::GenerateServiceConfig( return ServiceConfigJsonEntry{"stateful_session", JsonDump(config)}; } +absl::StatusOr +XdsHttpStatefulSessionFilter::GenerateServiceConfig( + const FilterConfig& /*hcm_filter_config*/) const { + return ServiceConfigJsonEntry{"", ""}; +} + } // namespace grpc_core diff --git a/src/core/xds/grpc/xds_http_stateful_session_filter.h b/src/core/xds/grpc/xds_http_stateful_session_filter.h index fb3ce72b59f..28df742b6c2 100644 --- a/src/core/xds/grpc/xds_http_stateful_session_filter.h +++ b/src/core/xds/grpc/xds_http_stateful_session_filter.h @@ -39,17 +39,21 @@ class XdsHttpStatefulSessionFilter final : public XdsHttpFilterImpl { absl::string_view OverrideConfigProtoName() const override; void PopulateSymtab(upb_DefPool* symtab) const override; absl::optional GenerateFilterConfig( + absl::string_view /*instance_name*/, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const override; absl::optional GenerateFilterConfigOverride( + absl::string_view /*instance_name*/, const XdsResourceType::DecodeContext& context, XdsExtension extension, ValidationErrors* errors) const override; void AddFilter(InterceptionChainBuilder& builder) const override; const grpc_channel_filter* channel_filter() const override; ChannelArgs ModifyChannelArgs(const ChannelArgs& args) const override; - absl::StatusOr GenerateServiceConfig( + absl::StatusOr GenerateMethodConfig( const FilterConfig& hcm_filter_config, const FilterConfig* filter_config_override) const override; + absl::StatusOr GenerateServiceConfig( + const FilterConfig& hcm_filter_config) const override; bool IsSupportedOnClients() const override { return true; } bool IsSupportedOnServers() const override { return false; } }; diff --git a/src/core/xds/grpc/xds_listener_parser.cc b/src/core/xds/grpc/xds_listener_parser.cc index b536472a80f..e16efa43fae 100644 --- a/src/core/xds/grpc/xds_listener_parser.cc +++ b/src/core/xds/grpc/xds_listener_parser.cc @@ -268,8 +268,8 @@ XdsListenerResource::HttpConnectionManager HttpConnectionManagerParse( continue; } absl::optional filter_config = - filter_impl->GenerateFilterConfig(context, std::move(*extension), - errors); + filter_impl->GenerateFilterConfig(name, context, + std::move(*extension), errors); if (filter_config.has_value()) { http_connection_manager.http_filters.emplace_back( XdsListenerResource::HttpConnectionManager::HttpFilter{ diff --git a/src/core/xds/grpc/xds_route_config_parser.cc b/src/core/xds/grpc/xds_route_config_parser.cc index d6ac0f858a7..d23f162456a 100644 --- a/src/core/xds/grpc/xds_route_config_parser.cc +++ b/src/core/xds/grpc/xds_route_config_parser.cc @@ -441,7 +441,7 @@ XdsRouteConfigResource::TypedPerFilterConfig ParseTypedPerFilterConfig( } absl::optional filter_config = filter_impl->GenerateFilterConfigOverride( - context, std::move(*extension_to_use), errors); + key, context, std::move(*extension_to_use), errors); if (filter_config.has_value()) { typed_per_filter_config[std::string(key)] = std::move(*filter_config); } diff --git a/src/core/xds/grpc/xds_routing.cc b/src/core/xds/grpc/xds_routing.cc index 83c9d620e48..2a3a29088d8 100644 --- a/src/core/xds/grpc/xds_routing.cc +++ b/src/core/xds/grpc/xds_routing.cc @@ -215,23 +215,21 @@ const XdsHttpFilterImpl::FilterConfig* FindFilterConfigOverride( return nullptr; } -} // namespace - absl::StatusOr -XdsRouting::GeneratePerHTTPFilterConfigs( +GeneratePerHTTPFilterConfigs( const XdsHttpFilterRegistry& http_filter_registry, const std::vector& http_filters, - const XdsRouteConfigResource::VirtualHost& vhost, - const XdsRouteConfigResource::Route& route, - const XdsRouteConfigResource::Route::RouteAction::ClusterWeight* - cluster_weight, - const ChannelArgs& args) { - GeneratePerHttpFilterConfigsResult result; + const ChannelArgs& args, + absl::FunctionRef( + const XdsHttpFilterImpl&, + const XdsListenerResource::HttpConnectionManager::HttpFilter&)> + generate_service_config) { + XdsRouting::GeneratePerHttpFilterConfigsResult result; result.args = args; for (const auto& http_filter : http_filters) { // Find filter. This is guaranteed to succeed, because it's checked - // at config validation time in the XdsApi code. + // at config validation time in the listener parsing code. const XdsHttpFilterImpl* filter_impl = http_filter_registry.GetFilterForType( http_filter.config.config_proto_type_name); @@ -242,22 +240,60 @@ XdsRouting::GeneratePerHTTPFilterConfigs( // Allow filter to add channel args that may affect service config // parsing. result.args = filter_impl->ModifyChannelArgs(result.args); - // Find config override, if any. - const XdsHttpFilterImpl::FilterConfig* config_override = - FindFilterConfigOverride(http_filter.name, vhost, route, - cluster_weight); // Generate service config for filter. - auto method_config_field = - filter_impl->GenerateServiceConfig(http_filter.config, config_override); - if (!method_config_field.ok()) { + auto service_config_field = + generate_service_config(*filter_impl, http_filter); + if (!service_config_field.ok()) { return absl::FailedPreconditionError(absl::StrCat( - "failed to generate method config for HTTP filter ", http_filter.name, - ": ", method_config_field.status().ToString())); + "failed to generate service config for HTTP filter ", + http_filter.name, ": ", service_config_field.status().ToString())); } - result.per_filter_configs[method_config_field->service_config_field_name] - .push_back(method_config_field->element); + if (service_config_field->service_config_field_name.empty()) continue; + result.per_filter_configs[service_config_field->service_config_field_name] + .push_back(service_config_field->element); } return result; } +} // namespace + +absl::StatusOr +XdsRouting::GeneratePerHTTPFilterConfigsForMethodConfig( + const XdsHttpFilterRegistry& http_filter_registry, + const std::vector& + http_filters, + const XdsRouteConfigResource::VirtualHost& vhost, + const XdsRouteConfigResource::Route& route, + const XdsRouteConfigResource::Route::RouteAction::ClusterWeight* + cluster_weight, + const ChannelArgs& args) { + return GeneratePerHTTPFilterConfigs( + http_filter_registry, http_filters, args, + [&](const XdsHttpFilterImpl& filter_impl, + const XdsListenerResource::HttpConnectionManager::HttpFilter& + http_filter) { + const XdsHttpFilterImpl::FilterConfig* config_override = + FindFilterConfigOverride(http_filter.name, vhost, route, + cluster_weight); + // Generate service config for filter. + return filter_impl.GenerateMethodConfig(http_filter.config, + config_override); + }); +} + +absl::StatusOr +XdsRouting::GeneratePerHTTPFilterConfigsForServiceConfig( + const XdsHttpFilterRegistry& http_filter_registry, + const std::vector& + http_filters, + const ChannelArgs& args) { + return GeneratePerHTTPFilterConfigs( + http_filter_registry, http_filters, args, + [&](const XdsHttpFilterImpl& filter_impl, + const XdsListenerResource::HttpConnectionManager::HttpFilter& + http_filter) { + return filter_impl.GenerateServiceConfig(http_filter.config); + }); +} + } // namespace grpc_core diff --git a/src/core/xds/grpc/xds_routing.h b/src/core/xds/grpc/xds_routing.h index 1d4966961d1..696599d8463 100644 --- a/src/core/xds/grpc/xds_routing.h +++ b/src/core/xds/grpc/xds_routing.h @@ -88,9 +88,9 @@ class XdsRouting final { ChannelArgs args; }; - // Generates a map of per_filter_configs. \a args is consumed. + // Generates per-HTTP filter configs for a method config. static absl::StatusOr - GeneratePerHTTPFilterConfigs( + GeneratePerHTTPFilterConfigsForMethodConfig( const XdsHttpFilterRegistry& http_filter_registry, const std::vector& http_filters, @@ -99,6 +99,14 @@ class XdsRouting final { const XdsRouteConfigResource::Route::RouteAction::ClusterWeight* cluster_weight, const ChannelArgs& args); + + // Generates per-HTTP filter configs for the top-level service config. + static absl::StatusOr + GeneratePerHTTPFilterConfigsForServiceConfig( + const XdsHttpFilterRegistry& http_filter_registry, + const std::vector& + http_filters, + const ChannelArgs& args); }; } // namespace grpc_core diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py index c27a3690636..08aadfbab32 100644 --- a/src/python/grpcio/grpc_core_dependencies.py +++ b/src/python/grpcio/grpc_core_dependencies.py @@ -43,6 +43,8 @@ CORE_SOURCE_FILES = [ 'src/core/ext/filters/channel_idle/legacy_channel_idle_filter.cc', 'src/core/ext/filters/fault_injection/fault_injection_filter.cc', 'src/core/ext/filters/fault_injection/fault_injection_service_config_parser.cc', + 'src/core/ext/filters/gcp_authentication/gcp_authentication_filter.cc', + 'src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.cc', 'src/core/ext/filters/http/client/http_client_filter.cc', 'src/core/ext/filters/http/client_authority_filter.cc', 'src/core/ext/filters/http/http_filters_plugin.cc', @@ -627,6 +629,7 @@ CORE_SOURCE_FILES = [ 'src/core/lib/security/credentials/external/file_external_account_credentials.cc', 'src/core/lib/security/credentials/external/url_external_account_credentials.cc', 'src/core/lib/security/credentials/fake/fake_credentials.cc', + 'src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc', 'src/core/lib/security/credentials/google_default/credentials_generic.cc', 'src/core/lib/security/credentials/google_default/google_default_credentials.cc', 'src/core/lib/security/credentials/iam/iam_credentials.cc', @@ -848,6 +851,7 @@ CORE_SOURCE_FILES = [ 'src/core/xds/grpc/xds_health_status.cc', 'src/core/xds/grpc/xds_http_fault_filter.cc', 'src/core/xds/grpc/xds_http_filter_registry.cc', + 'src/core/xds/grpc/xds_http_gcp_authn_filter.cc', 'src/core/xds/grpc/xds_http_rbac_filter.cc', 'src/core/xds/grpc/xds_http_stateful_session_filter.cc', 'src/core/xds/grpc/xds_lb_policy_registry.cc', diff --git a/test/core/filters/BUILD b/test/core/filters/BUILD index 33a7c41ea6e..c14d98de853 100644 --- a/test/core/filters/BUILD +++ b/test/core/filters/BUILD @@ -120,6 +120,27 @@ grpc_cc_test( ], ) +grpc_cc_test( + name = "gcp_authentication_filter_test", + srcs = ["gcp_authentication_filter_test.cc"], + external_deps = [ + "absl/status", + "absl/status:statusor", + "absl/strings", + "gtest", + ], + language = "c++", + uses_event_engine = False, + uses_polling = False, + deps = [ + "filter_test", + "//:grpc", + "//:grpc_security_base", + "//:ref_counted_ptr", + "//src/core:channel_args", + ], +) + grpc_cc_benchmark( name = "bm_http_client_filter", srcs = ["bm_http_client_filter.cc"], diff --git a/test/core/filters/filter_test.cc b/test/core/filters/filter_test.cc index 1c5151ee5d0..694c26350b9 100644 --- a/test/core/filters/filter_test.cc +++ b/test/core/filters/filter_test.cc @@ -56,7 +56,7 @@ class FilterTestBase::Call::Impl : call_(call), channel_(std::move(channel)) {} ~Impl(); - Arena* arena() { return arena_.get(); } + Arena* arena() const { return arena_.get(); } const std::shared_ptr& channel() const { return channel_; } CallFinalization* call_finalization() { return &call_finalization_; } @@ -336,7 +336,7 @@ FilterTestBase::Call::Call(const Channel& channel) FilterTestBase::Call::~Call() { ScopedContext x(std::move(impl_)); } -Arena* FilterTestBase::Call::arena() { return impl_->arena(); } +Arena* FilterTestBase::Call::arena() const { return impl_->arena(); } ClientMetadataHandle FilterTestBase::Call::NewClientMetadata( std::initializer_list> diff --git a/test/core/filters/filter_test.h b/test/core/filters/filter_test.h index f48890f2a58..e905b84c8c6 100644 --- a/test/core/filters/filter_test.h +++ b/test/core/filters/filter_test.h @@ -163,7 +163,7 @@ class FilterTestBase : public ::testing::Test { // metadata. void FinishNextFilter(ServerMetadataHandle md); - Arena* arena(); + Arena* arena() const; private: friend class Channel; @@ -222,7 +222,7 @@ class FilterTest : public FilterTestBase { }; absl::StatusOr MakeChannel(const ChannelArgs& args) { - auto filter = Filter::Create(args, ChannelFilter::Args()); + auto filter = Filter::Create(args, ChannelFilter::Args(/*instance_id=*/0)); if (!filter.ok()) return filter.status(); return Channel(std::move(*filter), this); } diff --git a/test/core/filters/gcp_authentication_filter_test.cc b/test/core/filters/gcp_authentication_filter_test.cc new file mode 100644 index 00000000000..4c677df2b45 --- /dev/null +++ b/test/core/filters/gcp_authentication_filter_test.cc @@ -0,0 +1,386 @@ +// Copyright 2024 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h" + +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/promise_based_filter.h" +#include "src/core/lib/gprpp/ref_counted_ptr.h" +#include "src/core/lib/gprpp/unique_type_name.h" +#include "src/core/lib/security/context/security_context.h" +#include "src/core/lib/security/credentials/credentials.h" +#include "src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h" +#include "src/core/resolver/xds/xds_config.h" +#include "src/core/resolver/xds/xds_resolver_attributes.h" +#include "src/core/service_config/service_config_call_data.h" +#include "src/core/service_config/service_config_impl.h" +#include "test/core/filters/filter_test.h" + +namespace grpc_core { +namespace { + +class GcpAuthenticationFilterTest : public FilterTest { + protected: + static RefCountedPtr MakeServiceConfig( + absl::string_view service_config_json) { + auto service_config = ServiceConfigImpl::Create( + ChannelArgs().Set(GRPC_ARG_PARSE_GCP_AUTHENTICATION_METHOD_CONFIG, + true), + service_config_json); + CHECK(service_config.ok()) << service_config.status(); + return *service_config; + } + + static RefCountedPtr MakeXdsConfig( + absl::string_view cluster, absl::string_view filter_instance_name, + std::unique_ptr audience_metadata) { + auto xds_config = MakeRefCounted(); + if (!cluster.empty()) { + auto cluster_resource = std::make_shared(); + if (audience_metadata != nullptr) { + cluster_resource->metadata.Insert(filter_instance_name, + std::move(audience_metadata)); + } + xds_config->clusters[cluster].emplace(std::move(cluster_resource), + nullptr, ""); + } + return xds_config; + } + + static RefCountedPtr MakeXdsConfigWithCluster( + absl::string_view cluster, + absl::StatusOr cluster_config) { + auto xds_config = MakeRefCounted(); + xds_config->clusters[cluster] = std::move(cluster_config); + return xds_config; + } + + ChannelArgs MakeChannelArgs( + absl::string_view service_config_json, absl::string_view cluster, + absl::string_view filter_instance_name, + std::unique_ptr audience_metadata) { + auto service_config = MakeServiceConfig(service_config_json); + auto xds_config = MakeXdsConfig(cluster, filter_instance_name, + std::move(audience_metadata)); + return ChannelArgs() + .SetObject(std::move(service_config)) + .SetObject(std::move(xds_config)); + } + + static RefCountedPtr GetCallCreds(const Call& call) { + auto* security_ctx = DownCast( + call.arena()->GetContext()); + if (security_ctx == nullptr) return nullptr; + return security_ctx->creds; + } +}; + +TEST_F(GcpAuthenticationFilterTest, CreateSucceeds) { + constexpr absl::string_view kClusterName = "foo"; + constexpr absl::string_view kFilterInstanceName = "gcp_authn_filter"; + constexpr absl::string_view kServiceConfigJson = + "{\n" + " \"gcp_authentication\": [\n" + " {\"filter_instance_name\": \"gcp_authn_filter\"}\n" + " ]\n" + "}"; + auto channel_args = MakeChannelArgs(kServiceConfigJson, kClusterName, + kFilterInstanceName, nullptr); + auto filter = GcpAuthenticationFilter::Create( + channel_args, ChannelFilter::Args(/*instance_id=*/0)); + EXPECT_TRUE(filter.ok()) << filter.status(); +} + +TEST_F(GcpAuthenticationFilterTest, CreateFailsWithoutServiceConfig) { + constexpr absl::string_view kClusterName = "foo"; + constexpr absl::string_view kFilterInstanceName = "gcp_authn_filter"; + auto channel_args = ChannelArgs().SetObject( + MakeXdsConfig(kClusterName, kFilterInstanceName, nullptr)); + auto filter = GcpAuthenticationFilter::Create( + channel_args, ChannelFilter::Args(/*instance_id=*/0)); + EXPECT_EQ(filter.status(), + absl::InvalidArgumentError( + "gcp_auth: no service config in channel args")); +} + +TEST_F(GcpAuthenticationFilterTest, + CreateFailsFilterConfigMissingFromServiceConfig) { + constexpr absl::string_view kClusterName = "foo"; + constexpr absl::string_view kFilterInstanceName = "gcp_authn_filter"; + constexpr absl::string_view kServiceConfigJson = "{}"; + auto channel_args = MakeChannelArgs(kServiceConfigJson, kClusterName, + kFilterInstanceName, nullptr); + auto filter = GcpAuthenticationFilter::Create( + channel_args, ChannelFilter::Args(/*instance_id=*/0)); + EXPECT_EQ(filter.status(), + absl::InvalidArgumentError( + "gcp_auth: filter instance ID not found in filter config")); +} + +TEST_F(GcpAuthenticationFilterTest, CreateFailsXdsConfigNotFoundInChannelArgs) { + constexpr absl::string_view kServiceConfigJson = + "{\n" + " \"gcp_authentication\": [\n" + " {\"filter_instance_name\": \"gcp_authn_filter\"}\n" + " ]\n" + "}"; + auto channel_args = + ChannelArgs().SetObject(MakeServiceConfig(kServiceConfigJson)); + auto filter = GcpAuthenticationFilter::Create( + channel_args, ChannelFilter::Args(/*instance_id=*/0)); + EXPECT_EQ(filter.status(), + absl::InvalidArgumentError( + "gcp_auth: xds config not found in channel args")); +} + +TEST_F(GcpAuthenticationFilterTest, FailsCallIfNoXdsClusterAttribute) { + constexpr absl::string_view kClusterName = "foo"; + constexpr absl::string_view kFilterInstanceName = "gcp_authn_filter"; + constexpr absl::string_view kServiceConfigJson = + "{\n" + " \"gcp_authentication\": [\n" + " {\"filter_instance_name\": \"gcp_authn_filter\"}\n" + " ]\n" + "}"; + auto channel_args = MakeChannelArgs(kServiceConfigJson, kClusterName, + kFilterInstanceName, nullptr); + Call call(MakeChannel(channel_args).value()); + call.arena()->New(call.arena()); + call.Start(call.NewClientMetadata()); + EXPECT_EVENT(Finished( + &call, + HasMetadataResult(absl::InternalError( + "GCP authentication filter: call has no xDS cluster attribute")))); + Step(); + // Call creds were not set. + EXPECT_EQ(GetCallCreds(call), nullptr); +} + +TEST_F(GcpAuthenticationFilterTest, NoOpIfClusterAttributeHasWrongPrefix) { + constexpr absl::string_view kClusterName = "foo"; + constexpr absl::string_view kFilterInstanceName = "gcp_authn_filter"; + constexpr absl::string_view kServiceConfigJson = + "{\n" + " \"gcp_authentication\": [\n" + " {\"filter_instance_name\": \"gcp_authn_filter\"}\n" + " ]\n" + "}"; + constexpr absl::string_view kAudience = "bar"; + auto channel_args = MakeChannelArgs( + kServiceConfigJson, kClusterName, kFilterInstanceName, + std::make_unique(kAudience)); + Call call(MakeChannel(channel_args).value()); + auto* service_config_call_data = + call.arena()->New(call.arena()); + XdsClusterAttribute xds_cluster_attribute(kClusterName); + service_config_call_data->SetCallAttribute(&xds_cluster_attribute); + EXPECT_EVENT(Started(&call, ::testing::_)); + call.Start(call.NewClientMetadata()); + call.FinishNextFilter(call.NewServerMetadata({{"grpc-status", "0"}})); + EXPECT_EVENT(Finished(&call, HasMetadataResult(absl::OkStatus()))); + Step(); + // Call creds were not set. + EXPECT_EQ(GetCallCreds(call), nullptr); +} + +TEST_F(GcpAuthenticationFilterTest, FailsCallIfClusterNotPresentInXdsConfig) { + constexpr absl::string_view kClusterName = "foo"; + constexpr absl::string_view kServiceConfigJson = + "{\n" + " \"gcp_authentication\": [\n" + " {\"filter_instance_name\": \"gcp_authn_filter\"}\n" + " ]\n" + "}"; + auto channel_args = MakeChannelArgs(kServiceConfigJson, /*cluster=*/"", + /*filter_instance_name=*/"", nullptr); + Call call(MakeChannel(channel_args).value()); + auto* service_config_call_data = + call.arena()->New(call.arena()); + std::string cluster_name_with_prefix = absl::StrCat("cluster:", kClusterName); + XdsClusterAttribute xds_cluster_attribute(cluster_name_with_prefix); + service_config_call_data->SetCallAttribute(&xds_cluster_attribute); + call.Start(call.NewClientMetadata()); + EXPECT_EVENT( + Finished(&call, HasMetadataResult(absl::InternalError(absl::StrCat( + "GCP authentication filter: xDS cluster ", + kClusterName, " not found in XdsConfig"))))); + Step(); + // Call creds were not set. + EXPECT_EQ(GetCallCreds(call), nullptr); +} + +TEST_F(GcpAuthenticationFilterTest, FailsCallIfClusterNotOkayInXdsConfig) { + constexpr absl::string_view kClusterName = "foo"; + constexpr absl::string_view kServiceConfigJson = + "{\n" + " \"gcp_authentication\": [\n" + " {\"filter_instance_name\": \"gcp_authn_filter\"}\n" + " ]\n" + "}"; + auto channel_args = ChannelArgs() + .SetObject(MakeServiceConfig(kServiceConfigJson)) + .SetObject(MakeXdsConfigWithCluster( + kClusterName, absl::UnavailableError("nope"))); + Call call(MakeChannel(channel_args).value()); + auto* service_config_call_data = + call.arena()->New(call.arena()); + std::string cluster_name_with_prefix = absl::StrCat("cluster:", kClusterName); + XdsClusterAttribute xds_cluster_attribute(cluster_name_with_prefix); + service_config_call_data->SetCallAttribute(&xds_cluster_attribute); + call.Start(call.NewClientMetadata()); + EXPECT_EVENT(Finished( + &call, HasMetadataResult(absl::UnavailableError(absl::StrCat( + "GCP authentication filter: CDS resource unavailable for ", + kClusterName))))); + Step(); + // Call creds were not set. + EXPECT_EQ(GetCallCreds(call), nullptr); +} + +TEST_F(GcpAuthenticationFilterTest, + FailsCallIfClusterResourceMissingInXdsConfig) { + constexpr absl::string_view kClusterName = "foo"; + constexpr absl::string_view kServiceConfigJson = + "{\n" + " \"gcp_authentication\": [\n" + " {\"filter_instance_name\": \"gcp_authn_filter\"}\n" + " ]\n" + "}"; + auto channel_args = + ChannelArgs() + .SetObject(MakeServiceConfig(kServiceConfigJson)) + .SetObject(MakeXdsConfigWithCluster( + kClusterName, XdsConfig::ClusterConfig(nullptr, nullptr, ""))); + Call call(MakeChannel(channel_args).value()); + auto* service_config_call_data = + call.arena()->New(call.arena()); + std::string cluster_name_with_prefix = absl::StrCat("cluster:", kClusterName); + XdsClusterAttribute xds_cluster_attribute(cluster_name_with_prefix); + service_config_call_data->SetCallAttribute(&xds_cluster_attribute); + call.Start(call.NewClientMetadata()); + EXPECT_EVENT(Finished( + &call, + HasMetadataResult(absl::InternalError(absl::StrCat( + "GCP authentication filter: CDS resource not present for cluster ", + kClusterName))))); + Step(); + // Call creds were not set. + EXPECT_EQ(GetCallCreds(call), nullptr); +} + +TEST_F(GcpAuthenticationFilterTest, NoOpIfClusterHasNoAudience) { + constexpr absl::string_view kClusterName = "foo"; + constexpr absl::string_view kFilterInstanceName = "gcp_authn_filter"; + constexpr absl::string_view kServiceConfigJson = + "{\n" + " \"gcp_authentication\": [\n" + " {\"filter_instance_name\": \"gcp_authn_filter\"}\n" + " ]\n" + "}"; + auto channel_args = MakeChannelArgs(kServiceConfigJson, kClusterName, + kFilterInstanceName, nullptr); + Call call(MakeChannel(channel_args).value()); + auto* service_config_call_data = + call.arena()->New(call.arena()); + std::string cluster_name_with_prefix = absl::StrCat("cluster:", kClusterName); + XdsClusterAttribute xds_cluster_attribute(cluster_name_with_prefix); + service_config_call_data->SetCallAttribute(&xds_cluster_attribute); + EXPECT_EVENT(Started(&call, ::testing::_)); + call.Start(call.NewClientMetadata()); + call.FinishNextFilter(call.NewServerMetadata({{"grpc-status", "0"}})); + EXPECT_EVENT(Finished(&call, HasMetadataResult(absl::OkStatus()))); + Step(); + // Call creds were not set. + EXPECT_EQ(GetCallCreds(call), nullptr); +} + +TEST_F(GcpAuthenticationFilterTest, FailsCallIfAudienceMetadataWrongType) { + constexpr absl::string_view kClusterName = "foo"; + constexpr absl::string_view kFilterInstanceName = "gcp_authn_filter"; + constexpr absl::string_view kServiceConfigJson = + "{\n" + " \"gcp_authentication\": [\n" + " {\"filter_instance_name\": \"gcp_authn_filter\"}\n" + " ]\n" + "}"; + auto channel_args = + MakeChannelArgs(kServiceConfigJson, kClusterName, kFilterInstanceName, + std::make_unique(Json())); + Call call(MakeChannel(channel_args).value()); + auto* service_config_call_data = + call.arena()->New(call.arena()); + std::string cluster_name_with_prefix = absl::StrCat("cluster:", kClusterName); + XdsClusterAttribute xds_cluster_attribute(cluster_name_with_prefix); + service_config_call_data->SetCallAttribute(&xds_cluster_attribute); + call.Start(call.NewClientMetadata()); + EXPECT_EVENT(Finished( + &call, HasMetadataResult(absl::UnavailableError(absl::StrCat( + "GCP authentication filter: audience metadata in wrong format " + "for cluster ", + kClusterName))))); + Step(); + // Call creds were not set. + EXPECT_EQ(GetCallCreds(call), nullptr); +} + +TEST_F(GcpAuthenticationFilterTest, SetsCallCredsIfClusterHasAudience) { + constexpr absl::string_view kClusterName = "foo"; + constexpr absl::string_view kFilterInstanceName = "gcp_authn_filter"; + constexpr absl::string_view kServiceConfigJson = + "{\n" + " \"gcp_authentication\": [\n" + " {\"filter_instance_name\": \"gcp_authn_filter\"}\n" + " ]\n" + "}"; + constexpr absl::string_view kAudience = "bar"; + auto channel_args = MakeChannelArgs( + kServiceConfigJson, kClusterName, kFilterInstanceName, + std::make_unique(kAudience)); + Call call(MakeChannel(channel_args).value()); + auto* service_config_call_data = + call.arena()->New(call.arena()); + std::string cluster_name_with_prefix = absl::StrCat("cluster:", kClusterName); + XdsClusterAttribute xds_cluster_attribute(cluster_name_with_prefix); + service_config_call_data->SetCallAttribute(&xds_cluster_attribute); + EXPECT_EVENT(Started(&call, ::testing::_)); + call.Start(call.NewClientMetadata()); + call.FinishNextFilter(call.NewServerMetadata({{"grpc-status", "0"}})); + EXPECT_EVENT(Finished(&call, HasMetadataResult(absl::OkStatus()))); + Step(); + // Call creds were set with the right audience. + auto call_creds = GetCallCreds(call); + ASSERT_NE(call_creds, nullptr); + EXPECT_EQ(call_creds->type(), + GcpServiceAccountIdentityCallCredentials::Type()); + EXPECT_EQ(call_creds->debug_string(), + absl::StrCat("GcpServiceAccountIdentityCallCredentials(", kAudience, + ")")); +} + +} // namespace +} // namespace grpc_core + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/core/util/BUILD b/test/core/util/BUILD index 41f7fc72549..869a9b96672 100644 --- a/test/core/util/BUILD +++ b/test/core/util/BUILD @@ -150,3 +150,15 @@ grpc_cc_test( "//src/core:ring_buffer", ], ) + +grpc_cc_test( + name = "lru_cache_test", + srcs = ["lru_cache_test.cc"], + external_deps = ["gtest"], + language = "C++", + uses_event_engine = False, + uses_polling = False, + deps = [ + "//src/core:lru_cache", + ], +) diff --git a/test/core/util/lru_cache_test.cc b/test/core/util/lru_cache_test.cc new file mode 100644 index 00000000000..fbb9ce1f37f --- /dev/null +++ b/test/core/util/lru_cache_test.cc @@ -0,0 +1,74 @@ +// +// Copyright 2024 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "src/core/util/lru_cache.h" + +#include "absl/log/check.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace grpc_core { + +TEST(LruCache, Basic) { + std::vector created_list; + auto create = [&](const std::string& key) { + int value; + CHECK(absl::SimpleAtoi(key, &value)); + created_list.push_back(value); + return value; + }; + // Create a cache with max size 5. + LruCache cache(5); + // Insert 5 values. + const std::array kOrder = {3, 1, 2, 0, 4}; + for (int i : kOrder) { + std::string key = absl::StrCat(i); + EXPECT_EQ(absl::nullopt, cache.Get(key)); + EXPECT_EQ(i, cache.GetOrInsert(key, create)); + EXPECT_EQ(i, cache.Get(key)); + } + EXPECT_THAT(created_list, ::testing::ElementsAreArray(kOrder)); + created_list.clear(); + // Get those same 5 values. This should not trigger any more insertions. + for (int i : kOrder) { + std::string key = absl::StrCat(i); + EXPECT_EQ(i, cache.GetOrInsert(key, create)); + } + EXPECT_THAT(created_list, ::testing::ElementsAre()); + // Now insert new elements. + // Each insertion should remove the least recently used element. + const std::array kOrder2 = {7, 6, 8, 5, 9}; + for (size_t i = 0; i < kOrder2.size(); ++i) { + int value2 = kOrder2[i]; + std::string key2 = absl::StrCat(value2); + EXPECT_EQ(absl::nullopt, cache.Get(key2)); + EXPECT_EQ(value2, cache.GetOrInsert(key2, create)); + EXPECT_EQ(value2, cache.Get(key2)); + int value1 = kOrder[i]; + std::string key1 = absl::StrCat(value1); + EXPECT_EQ(absl::nullopt, cache.Get(key1)); + } + EXPECT_THAT(created_list, ::testing::ElementsAreArray(kOrder2)); +} + +} // namespace grpc_core + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/core/xds/BUILD b/test/core/xds/BUILD index f91ac8e1009..85652f14e99 100644 --- a/test/core/xds/BUILD +++ b/test/core/xds/BUILD @@ -262,6 +262,7 @@ grpc_cc_test( "//:gpr", "//:grpc", "//src/proto/grpc/testing/xds/v3:fault_proto", + "//src/proto/grpc/testing/xds/v3:gcp_authn_proto", "//src/proto/grpc/testing/xds/v3:http_filter_rbac_proto", "//src/proto/grpc/testing/xds/v3:router_proto", "//src/proto/grpc/testing/xds/v3:stateful_session_cookie_proto", diff --git a/test/core/xds/xds_http_filters_test.cc b/test/core/xds/xds_http_filters_test.cc index d57d5f5650c..f1811d7c6ad 100644 --- a/test/core/xds/xds_http_filters_test.cc +++ b/test/core/xds/xds_http_filters_test.cc @@ -38,6 +38,8 @@ #include "src/core/ext/filters/fault_injection/fault_injection_filter.h" #include "src/core/ext/filters/fault_injection/fault_injection_service_config_parser.h" +#include "src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h" +#include "src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h" #include "src/core/ext/filters/rbac/rbac_filter.h" #include "src/core/ext/filters/rbac/rbac_service_config_parser.h" #include "src/core/ext/filters/stateful_session/stateful_session_filter.h" @@ -55,6 +57,7 @@ #include "src/proto/grpc/testing/xds/v3/extension.pb.h" #include "src/proto/grpc/testing/xds/v3/fault.pb.h" #include "src/proto/grpc/testing/xds/v3/fault_common.pb.h" +#include "src/proto/grpc/testing/xds/v3/gcp_authn.pb.h" #include "src/proto/grpc/testing/xds/v3/http_filter_rbac.pb.h" #include "src/proto/grpc/testing/xds/v3/metadata.pb.h" #include "src/proto/grpc/testing/xds/v3/path.pb.h" @@ -78,13 +81,14 @@ namespace testing { namespace { using ::envoy::extensions::filters::http::fault::v3::HTTPFault; +using ::envoy::extensions::filters::http::gcp_authn::v3::GcpAuthnFilterConfig; using ::envoy::extensions::filters::http::rbac::v3::RBAC; using ::envoy::extensions::filters::http::rbac::v3::RBACPerRoute; using ::envoy::extensions::filters::http::router::v3::Router; using ::envoy::extensions::filters::http::stateful_session::v3::StatefulSession; -using ::envoy::extensions::filters::http::stateful_session::v3 :: +using ::envoy::extensions::filters::http::stateful_session::v3:: StatefulSessionPerRoute; -using ::envoy::extensions::http::stateful_session::cookie::v3 :: +using ::envoy::extensions::http::stateful_session::cookie::v3:: CookieBasedSessionState; // @@ -209,7 +213,7 @@ TEST_F(XdsRouterFilterTest, Accessors) { TEST_F(XdsRouterFilterTest, GenerateFilterConfig) { XdsExtension extension = MakeXdsExtension(Router()); - auto config = filter_->GenerateFilterConfig(decode_context_, + auto config = filter_->GenerateFilterConfig("", decode_context_, std::move(extension), &errors_); ASSERT_TRUE(errors_.ok()) << errors_.status( absl::StatusCode::kInvalidArgument, "unexpected errors"); @@ -221,7 +225,7 @@ TEST_F(XdsRouterFilterTest, GenerateFilterConfig) { TEST_F(XdsRouterFilterTest, GenerateFilterConfigTypedStruct) { XdsExtension extension = MakeXdsExtension(Router()); extension.value = Json(); - auto config = filter_->GenerateFilterConfig(decode_context_, + auto config = filter_->GenerateFilterConfig("", decode_context_, std::move(extension), &errors_); absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, "errors validating filter config"); @@ -238,7 +242,7 @@ TEST_F(XdsRouterFilterTest, GenerateFilterConfigUnparseable) { XdsExtension extension = MakeXdsExtension(Router()); std::string serialized_resource("\0", 1); extension.value = absl::string_view(serialized_resource); - auto config = filter_->GenerateFilterConfig(decode_context_, + auto config = filter_->GenerateFilterConfig("", decode_context_, std::move(extension), &errors_); absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, "errors validating filter config"); @@ -254,7 +258,7 @@ TEST_F(XdsRouterFilterTest, GenerateFilterConfigUnparseable) { TEST_F(XdsRouterFilterTest, GenerateFilterConfigOverride) { XdsExtension extension = MakeXdsExtension(Router()); auto config = filter_->GenerateFilterConfigOverride( - decode_context_, std::move(extension), &errors_); + "", decode_context_, std::move(extension), &errors_); absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, "errors validating filter config"); EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); @@ -298,28 +302,37 @@ TEST_F(XdsFaultInjectionFilterTest, ModifyChannelArgs) { EXPECT_EQ(*value, 1); } -TEST_F(XdsFaultInjectionFilterTest, GenerateServiceConfigTopLevelConfig) { +TEST_F(XdsFaultInjectionFilterTest, GenerateMethodConfigTopLevelConfig) { XdsHttpFilterImpl::FilterConfig config; config.config = Json::FromObject({{"foo", Json::FromString("bar")}}); - auto service_config = filter_->GenerateServiceConfig(config, nullptr); + auto service_config = filter_->GenerateMethodConfig(config, nullptr); ASSERT_TRUE(service_config.ok()) << service_config.status(); EXPECT_EQ(service_config->service_config_field_name, "faultInjectionPolicy"); EXPECT_EQ(service_config->element, "{\"foo\":\"bar\"}"); } -TEST_F(XdsFaultInjectionFilterTest, GenerateServiceConfigOverrideConfig) { +TEST_F(XdsFaultInjectionFilterTest, GenerateMethodConfigOverrideConfig) { XdsHttpFilterImpl::FilterConfig top_config; top_config.config = Json::FromObject({{"foo", Json::FromString("bar")}}); XdsHttpFilterImpl::FilterConfig override_config; override_config.config = Json::FromObject({{"baz", Json::FromString("quux")}}); auto service_config = - filter_->GenerateServiceConfig(top_config, &override_config); + filter_->GenerateMethodConfig(top_config, &override_config); ASSERT_TRUE(service_config.ok()) << service_config.status(); EXPECT_EQ(service_config->service_config_field_name, "faultInjectionPolicy"); EXPECT_EQ(service_config->element, "{\"baz\":\"quux\"}"); } +TEST_F(XdsFaultInjectionFilterTest, GenerateServiceConfig) { + XdsHttpFilterImpl::FilterConfig config; + config.config = Json::FromObject({{"foo", Json::FromString("bar")}}); + auto service_config = filter_->GenerateServiceConfig(config); + ASSERT_TRUE(service_config.ok()) << service_config.status(); + EXPECT_EQ(service_config->service_config_field_name, ""); + EXPECT_EQ(service_config->element, ""); +} + // For the fault injection filter, GenerateFilterConfig() and // GenerateFilterConfigOverride() accept the same input, so we want to // run all tests for both. @@ -331,10 +344,10 @@ class XdsFaultInjectionFilterConfigTest XdsExtension extension) { if (GetParam()) { return filter_->GenerateFilterConfigOverride( - decode_context_, std::move(extension), &errors_); + "", decode_context_, std::move(extension), &errors_); } - return filter_->GenerateFilterConfig(decode_context_, std::move(extension), - &errors_); + return filter_->GenerateFilterConfig("", decode_context_, + std::move(extension), &errors_); } }; @@ -506,7 +519,7 @@ TEST_F(XdsRbacFilterTest, ModifyChannelArgs) { TEST_F(XdsRbacFilterTest, GenerateFilterConfig) { XdsExtension extension = MakeXdsExtension(RBAC()); - auto config = filter_->GenerateFilterConfig(decode_context_, + auto config = filter_->GenerateFilterConfig("", decode_context_, std::move(extension), &errors_); ASSERT_TRUE(errors_.ok()) << errors_.status( absl::StatusCode::kInvalidArgument, "unexpected errors"); @@ -518,7 +531,7 @@ TEST_F(XdsRbacFilterTest, GenerateFilterConfig) { TEST_F(XdsRbacFilterTest, GenerateFilterConfigTypedStruct) { XdsExtension extension = MakeXdsExtension(RBAC()); extension.value = Json(); - auto config = filter_->GenerateFilterConfig(decode_context_, + auto config = filter_->GenerateFilterConfig("", decode_context_, std::move(extension), &errors_); absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, "errors validating filter config"); @@ -535,7 +548,7 @@ TEST_F(XdsRbacFilterTest, GenerateFilterConfigUnparseable) { XdsExtension extension = MakeXdsExtension(RBAC()); std::string serialized_resource("\0", 1); extension.value = absl::string_view(serialized_resource); - auto config = filter_->GenerateFilterConfig(decode_context_, + auto config = filter_->GenerateFilterConfig("", decode_context_, std::move(extension), &errors_); absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, "errors validating filter config"); @@ -551,7 +564,7 @@ TEST_F(XdsRbacFilterTest, GenerateFilterConfigUnparseable) { TEST_F(XdsRbacFilterTest, GenerateFilterConfigOverride) { XdsExtension extension = MakeXdsExtension(RBACPerRoute()); auto config = filter_->GenerateFilterConfigOverride( - decode_context_, std::move(extension), &errors_); + "", decode_context_, std::move(extension), &errors_); ASSERT_TRUE(errors_.ok()) << errors_.status( absl::StatusCode::kInvalidArgument, "unexpected errors"); ASSERT_TRUE(config.has_value()); @@ -563,7 +576,7 @@ TEST_F(XdsRbacFilterTest, GenerateFilterConfigOverrideTypedStruct) { XdsExtension extension = MakeXdsExtension(RBACPerRoute()); extension.value = Json(); auto config = filter_->GenerateFilterConfigOverride( - decode_context_, std::move(extension), &errors_); + "", decode_context_, std::move(extension), &errors_); absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, "errors validating filter config"); EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); @@ -579,7 +592,7 @@ TEST_F(XdsRbacFilterTest, GenerateFilterConfigOverrideUnparseable) { std::string serialized_resource("\0", 1); extension.value = absl::string_view(serialized_resource); auto config = filter_->GenerateFilterConfigOverride( - decode_context_, std::move(extension), &errors_); + "", decode_context_, std::move(extension), &errors_); absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, "errors validating filter config"); EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); @@ -590,17 +603,26 @@ TEST_F(XdsRbacFilterTest, GenerateFilterConfigOverrideUnparseable) { << status; } -TEST_F(XdsRbacFilterTest, GenerateServiceConfig) { +TEST_F(XdsRbacFilterTest, GenerateMethodConfig) { XdsHttpFilterImpl::FilterConfig hcm_config = { filter_->ConfigProtoName(), Json::FromObject({{"name", Json::FromString("foo")}})}; - auto config = filter_->GenerateServiceConfig(hcm_config, nullptr); + auto config = filter_->GenerateMethodConfig(hcm_config, nullptr); ASSERT_TRUE(config.ok()) << config.status(); EXPECT_EQ(config->service_config_field_name, "rbacPolicy"); EXPECT_EQ(config->element, JsonDump(Json::FromObject({{"name", Json::FromString("foo")}}))); } +TEST_F(XdsRbacFilterTest, GenerateServiceConfig) { + XdsHttpFilterImpl::FilterConfig config; + config.config = Json::FromObject({{"foo", Json::FromString("bar")}}); + auto service_config = filter_->GenerateServiceConfig(config); + ASSERT_TRUE(service_config.ok()) << service_config.status(); + EXPECT_EQ(service_config->service_config_field_name, ""); + EXPECT_EQ(service_config->element, ""); +} + // For the RBAC filter, the override config is a superset of the // top-level config, so we test all of the common fields as input for // both GenerateFilterConfig() and GenerateFilterConfigOverride(). @@ -613,11 +635,11 @@ class XdsRbacFilterConfigTest : public XdsRbacFilterTest, *rbac_per_route.mutable_rbac() = rbac; XdsExtension extension = MakeXdsExtension(rbac_per_route); return filter_->GenerateFilterConfigOverride( - decode_context_, std::move(extension), &errors_); + "", decode_context_, std::move(extension), &errors_); } XdsExtension extension = MakeXdsExtension(rbac); - return filter_->GenerateFilterConfig(decode_context_, std::move(extension), - &errors_); + return filter_->GenerateFilterConfig("", decode_context_, + std::move(extension), &errors_); } std::string FieldPrefix() { @@ -1138,7 +1160,7 @@ TEST_F(XdsStatefulSessionFilterTest, OverrideConfigDisabled) { stateful_session_per_route.set_disabled(true); XdsExtension extension = MakeXdsExtension(stateful_session_per_route); auto config = filter_->GenerateFilterConfigOverride( - decode_context_, std::move(extension), &errors_); + "", decode_context_, std::move(extension), &errors_); ASSERT_TRUE(errors_.ok()) << errors_.status( absl::StatusCode::kInvalidArgument, "unexpected errors"); ASSERT_TRUE(config.has_value()); @@ -1146,35 +1168,44 @@ TEST_F(XdsStatefulSessionFilterTest, OverrideConfigDisabled) { EXPECT_EQ(config->config, Json::FromObject({})) << JsonDump(config->config); } -TEST_F(XdsStatefulSessionFilterTest, GenerateServiceConfigNoOverride) { +TEST_F(XdsStatefulSessionFilterTest, GenerateMethodConfigNoOverride) { XdsHttpFilterImpl::FilterConfig hcm_config = { filter_->ConfigProtoName(), Json::FromObject({{"name", Json::FromString("foo")}})}; - auto config = filter_->GenerateServiceConfig(hcm_config, nullptr); + auto config = filter_->GenerateMethodConfig(hcm_config, nullptr); ASSERT_TRUE(config.ok()) << config.status(); EXPECT_EQ(config->service_config_field_name, "stateful_session"); EXPECT_EQ(config->element, JsonDump(Json::FromObject({{"name", Json::FromString("foo")}}))); } -TEST_F(XdsStatefulSessionFilterTest, GenerateServiceConfigWithOverride) { +TEST_F(XdsStatefulSessionFilterTest, GenerateMethodConfigWithOverride) { XdsHttpFilterImpl::FilterConfig hcm_config = { filter_->ConfigProtoName(), Json::FromObject({{"name", Json::FromString("foo")}})}; XdsHttpFilterImpl::FilterConfig override_config = { filter_->OverrideConfigProtoName(), Json::FromObject({{"name", Json::FromString("bar")}})}; - auto config = filter_->GenerateServiceConfig(hcm_config, &override_config); + auto config = filter_->GenerateMethodConfig(hcm_config, &override_config); ASSERT_TRUE(config.ok()) << config.status(); EXPECT_EQ(config->service_config_field_name, "stateful_session"); EXPECT_EQ(config->element, JsonDump(Json::FromObject({{"name", Json::FromString("bar")}}))); } +TEST_F(XdsStatefulSessionFilterTest, GenerateServiceConfig) { + XdsHttpFilterImpl::FilterConfig config; + config.config = Json::FromObject({{"foo", Json::FromString("bar")}}); + auto service_config = filter_->GenerateServiceConfig(config); + ASSERT_TRUE(service_config.ok()) << service_config.status(); + EXPECT_EQ(service_config->service_config_field_name, ""); + EXPECT_EQ(service_config->element, ""); +} + TEST_F(XdsStatefulSessionFilterTest, GenerateFilterConfigTypedStruct) { XdsExtension extension = MakeXdsExtension(StatefulSession()); extension.value = Json(); - auto config = filter_->GenerateFilterConfig(decode_context_, + auto config = filter_->GenerateFilterConfig("", decode_context_, std::move(extension), &errors_); absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, "errors validating filter config"); @@ -1192,7 +1223,7 @@ TEST_F(XdsStatefulSessionFilterTest, GenerateFilterConfigUnparseable) { XdsExtension extension = MakeXdsExtension(StatefulSession()); std::string serialized_resource("\0", 1); extension.value = absl::string_view(serialized_resource); - auto config = filter_->GenerateFilterConfig(decode_context_, + auto config = filter_->GenerateFilterConfig("", decode_context_, std::move(extension), &errors_); absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, "errors validating filter config"); @@ -1210,7 +1241,7 @@ TEST_F(XdsStatefulSessionFilterTest, GenerateFilterConfigOverrideTypedStruct) { XdsExtension extension = MakeXdsExtension(StatefulSessionPerRoute()); extension.value = Json(); auto config = filter_->GenerateFilterConfigOverride( - decode_context_, std::move(extension), &errors_); + "", decode_context_, std::move(extension), &errors_); absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, "errors validating filter config"); EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); @@ -1228,7 +1259,7 @@ TEST_F(XdsStatefulSessionFilterTest, GenerateFilterConfigOverrideUnparseable) { std::string serialized_resource("\0", 1); extension.value = absl::string_view(serialized_resource); auto config = filter_->GenerateFilterConfigOverride( - decode_context_, std::move(extension), &errors_); + "", decode_context_, std::move(extension), &errors_); absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, "errors validating filter config"); EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); @@ -1255,11 +1286,11 @@ class XdsStatefulSessionFilterConfigTest *stateful_session_per_route.mutable_stateful_session() = stateful_session; XdsExtension extension = MakeXdsExtension(stateful_session_per_route); return filter_->GenerateFilterConfigOverride( - decode_context_, std::move(extension), &errors_); + "", decode_context_, std::move(extension), &errors_); } XdsExtension extension = MakeXdsExtension(stateful_session); - return filter_->GenerateFilterConfig(decode_context_, std::move(extension), - &errors_); + return filter_->GenerateFilterConfig("", decode_context_, + std::move(extension), &errors_); } std::string FieldPrefix() { @@ -1446,6 +1477,193 @@ TEST_P(XdsStatefulSessionFilterConfigTest, UnparseableSessionState) { << status; } +// +// GCP auth filter tests +// + +using XdsGcpAuthnFilterNotRegisteredTest = XdsHttpFilterTest; + +TEST_F(XdsGcpAuthnFilterNotRegisteredTest, NotPresentWithoutEnvVar) { + XdsExtension extension = MakeXdsExtension(GcpAuthnFilterConfig()); + EXPECT_EQ(GetFilter(extension.type), nullptr); +} + +class XdsGcpAuthnFilterTest : public XdsHttpFilterTest { + protected: + XdsGcpAuthnFilterTest() { + // Re-initialize registry with env var set. + ScopedExperimentalEnvVar env_var( + "GRPC_EXPERIMENTAL_XDS_GCP_AUTHENTICATION_FILTER"); + registry_ = XdsHttpFilterRegistry(); + // Now the filter will be found in the registry. + XdsExtension extension = MakeXdsExtension(GcpAuthnFilterConfig()); + filter_ = GetFilter(extension.type); + CHECK_NE(filter_, nullptr) << extension.type; + } + + const XdsHttpFilterImpl* filter_; +}; + +TEST_F(XdsGcpAuthnFilterTest, Accessors) { + EXPECT_EQ(filter_->ConfigProtoName(), + "envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig"); + EXPECT_EQ(filter_->OverrideConfigProtoName(), ""); + EXPECT_EQ(filter_->channel_filter(), &GcpAuthenticationFilter::kFilter); + EXPECT_TRUE(filter_->IsSupportedOnClients()); + EXPECT_FALSE(filter_->IsSupportedOnServers()); + EXPECT_FALSE(filter_->IsTerminalFilter()); +} + +TEST_F(XdsGcpAuthnFilterTest, GenerateFilterConfigEmpty) { + XdsExtension extension = MakeXdsExtension(GcpAuthnFilterConfig()); + auto config = filter_->GenerateFilterConfig("enterprise", decode_context_, + std::move(extension), &errors_); + ASSERT_TRUE(errors_.ok()) << errors_.status( + absl::StatusCode::kInvalidArgument, "unexpected errors"); + ASSERT_TRUE(config.has_value()); + EXPECT_EQ(config->config_proto_type_name, filter_->ConfigProtoName()); + EXPECT_EQ(config->config, + Json::FromObject( + {{"filter_instance_name", Json::FromString("enterprise")}})) + << JsonDump(config->config); +} + +TEST_F(XdsGcpAuthnFilterTest, GenerateFilterConfigCacheSizeDefault) { + GcpAuthnFilterConfig proto; + proto.mutable_cache_config(); + XdsExtension extension = MakeXdsExtension(proto); + auto config = filter_->GenerateFilterConfig("yorktown", decode_context_, + std::move(extension), &errors_); + ASSERT_TRUE(errors_.ok()) << errors_.status( + absl::StatusCode::kInvalidArgument, "unexpected errors"); + ASSERT_TRUE(config.has_value()); + EXPECT_EQ(config->config_proto_type_name, filter_->ConfigProtoName()); + EXPECT_EQ( + config->config, + Json::FromObject({{"filter_instance_name", Json::FromString("yorktown")}, + {"cache_size", Json::FromNumber(10)}})) + << JsonDump(config->config); +} + +TEST_F(XdsGcpAuthnFilterTest, GenerateFilterConfigCacheSize) { + GcpAuthnFilterConfig proto; + proto.mutable_cache_config()->mutable_cache_size()->set_value(6); + XdsExtension extension = MakeXdsExtension(proto); + auto config = filter_->GenerateFilterConfig("hornet", decode_context_, + std::move(extension), &errors_); + ASSERT_TRUE(errors_.ok()) << errors_.status( + absl::StatusCode::kInvalidArgument, "unexpected errors"); + ASSERT_TRUE(config.has_value()); + EXPECT_EQ(config->config_proto_type_name, filter_->ConfigProtoName()); + EXPECT_EQ( + config->config, + Json::FromObject({{"filter_instance_name", Json::FromString("hornet")}, + {"cache_size", Json::FromNumber(6)}})) + << JsonDump(config->config); +} + +TEST_F(XdsGcpAuthnFilterTest, GenerateFilterConfigCacheSizeZero) { + GcpAuthnFilterConfig proto; + proto.mutable_cache_config()->mutable_cache_size()->set_value(0); + XdsExtension extension = MakeXdsExtension(proto); + auto config = filter_->GenerateFilterConfig("ranger", decode_context_, + std::move(extension), &errors_); + absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, + "errors validating filter config"); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_EQ(status.message(), + "errors validating filter config: [" + "field:http_filter.value[" + "envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig]" + ".cache_config.cache_size " + "error:must be in the range (0, INT64_MAX)]") + << status; +} + +TEST_F(XdsGcpAuthnFilterTest, GenerateFilterConfigCacheSizeTooBig) { + GcpAuthnFilterConfig proto; + proto.mutable_cache_config()->mutable_cache_size()->set_value(INT64_MAX); + XdsExtension extension = MakeXdsExtension(proto); + auto config = filter_->GenerateFilterConfig("langley", decode_context_, + std::move(extension), &errors_); + absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, + "errors validating filter config"); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_EQ(status.message(), + "errors validating filter config: [" + "field:http_filter.value[" + "envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig]" + ".cache_config.cache_size " + "error:must be in the range (0, INT64_MAX)]") + << status; +} + +TEST_F(XdsGcpAuthnFilterTest, GenerateFilterConfigTypedStruct) { + XdsExtension extension = MakeXdsExtension(GcpAuthnFilterConfig()); + extension.value = Json(); + auto config = filter_->GenerateFilterConfig("lexington", decode_context_, + std::move(extension), &errors_); + absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, + "errors validating filter config"); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_EQ(status.message(), + "errors validating filter config: [" + "field:http_filter.value[" + "envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig] " + "error:could not parse GCP auth filter config]") + << status; +} + +TEST_F(XdsGcpAuthnFilterTest, GenerateFilterConfigUnparseable) { + XdsExtension extension = MakeXdsExtension(GcpAuthnFilterConfig()); + std::string serialized_resource("\0", 1); + extension.value = absl::string_view(serialized_resource); + auto config = filter_->GenerateFilterConfig("saratoga", decode_context_, + std::move(extension), &errors_); + absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, + "errors validating filter config"); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_EQ(status.message(), + "errors validating filter config: [" + "field:http_filter.value[" + "envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig] " + "error:could not parse GCP auth filter config]") + << status; +} + +TEST_F(XdsGcpAuthnFilterTest, GenerateFilterConfigOverride) { + XdsExtension extension = MakeXdsExtension(GcpAuthnFilterConfig()); + auto config = filter_->GenerateFilterConfigOverride( + "wasp", decode_context_, std::move(extension), &errors_); + absl::Status status = errors_.status(absl::StatusCode::kInvalidArgument, + "errors validating filter config"); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_EQ(status.message(), + "errors validating filter config: [" + "field:http_filter.value[" + "envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig] " + "error:GCP auth filter does not support config override]") + << status; +} + +TEST_F(XdsGcpAuthnFilterTest, GenerateMethodConfig) { + XdsHttpFilterImpl::FilterConfig config; + config.config = Json::FromObject({{"foo", Json::FromString("bar")}}); + auto service_config = filter_->GenerateMethodConfig(config, nullptr); + ASSERT_TRUE(service_config.ok()) << service_config.status(); + EXPECT_EQ(service_config->service_config_field_name, ""); + EXPECT_EQ(service_config->element, ""); +} + +TEST_F(XdsGcpAuthnFilterTest, GenerateServiceConfig) { + XdsHttpFilterImpl::FilterConfig config; + config.config = Json::FromObject({{"foo", Json::FromString("bar")}}); + auto service_config = filter_->GenerateServiceConfig(config); + ASSERT_TRUE(service_config.ok()) << service_config.status(); + EXPECT_EQ(service_config->service_config_field_name, "gcp_authentication"); + EXPECT_EQ(service_config->element, "{\"foo\":\"bar\"}"); +} + } // namespace } // namespace testing } // namespace grpc_core diff --git a/test/cpp/end2end/xds/BUILD b/test/cpp/end2end/xds/BUILD index 2a66bca252c..07e486e6b62 100644 --- a/test/cpp/end2end/xds/BUILD +++ b/test/cpp/end2end/xds/BUILD @@ -298,6 +298,43 @@ grpc_cc_test( ], ) +grpc_cc_test( + name = "xds_gcp_authn_end2end_test", + size = "large", + srcs = ["xds_gcp_authn_end2end_test.cc"], + data = [ + "//src/core/tsi/test_creds:badclient.key", + "//src/core/tsi/test_creds:badclient.pem", + "//src/core/tsi/test_creds:ca.pem", + "//src/core/tsi/test_creds:client.key", + "//src/core/tsi/test_creds:client.pem", + "//src/core/tsi/test_creds:server1.key", + "//src/core/tsi/test_creds:server1.pem", + ], + external_deps = [ + "gtest", + ], + flaky = True, + linkstatic = True, # Fixes dyld error on MacOS + tags = [ + "no_test_ios", + "no_windows", + "xds_end2end_test", + ], # TODO(jtattermusch): fix test on windows + deps = [ + ":xds_end2end_test_lib", + "//:gpr", + "//:grpc", + "//:grpc++", + "//src/proto/grpc/testing/xds/v3:cluster_proto", + "//src/proto/grpc/testing/xds/v3:gcp_authn_proto", + "//src/proto/grpc/testing/xds/v3:http_connection_manager_proto", + "//src/proto/grpc/testing/xds/v3:router_proto", + "//test/core/test_util:grpc_test_util", + "//test/core/test_util:scoped_env_var", + ], +) + grpc_cc_test( name = "xds_outlier_detection_end2end_test", size = "large", diff --git a/test/cpp/end2end/xds/xds_end2end_test_lib.cc b/test/cpp/end2end/xds/xds_end2end_test_lib.cc index 4e67467896a..88dd6af93c6 100644 --- a/test/cpp/end2end/xds/xds_end2end_test_lib.cc +++ b/test/cpp/end2end/xds/xds_end2end_test_lib.cc @@ -353,6 +353,9 @@ void XdsEnd2endTest::RpcOptions::SetupRpc(ClientContext* context, if (echo_host_from_authority_header) { request->mutable_param()->set_echo_host_from_authority_header(true); } + if (echo_metadata_initially) { + request->mutable_param()->set_echo_metadata_initially(true); + } } // @@ -847,6 +850,11 @@ XdsEnd2endTest::MakeIdentityKeyCertPairForTlsCreds() { std::shared_ptr XdsEnd2endTest::CreateXdsChannelCredentials() { + return XdsCredentials(CreateTlsChannelCredentials()); +} + +std::shared_ptr +XdsEnd2endTest::CreateTlsChannelCredentials() { auto certificate_provider = std::make_shared( grpc_core::testing::GetFileContents(kCaCertPath), MakeIdentityKeyCertPairForTlsCreds()); @@ -859,8 +867,7 @@ XdsEnd2endTest::CreateXdsChannelCredentials() { options.set_certificate_verifier(std::move(verifier)); options.set_verify_server_certs(true); options.set_check_call_host(false); - auto tls_creds = grpc::experimental::TlsCredentials(options); - return XdsCredentials(tls_creds); + return grpc::experimental::TlsCredentials(options); } std::shared_ptr diff --git a/test/cpp/end2end/xds/xds_end2end_test_lib.h b/test/cpp/end2end/xds/xds_end2end_test_lib.h index 7bbddfd1217..68991fc881c 100644 --- a/test/cpp/end2end/xds/xds_end2end_test_lib.h +++ b/test/cpp/end2end/xds/xds_end2end_test_lib.h @@ -613,6 +613,7 @@ class XdsEnd2endTest : public ::testing::TestWithParam, absl::optional backend_metrics; bool server_notify_client_when_started = false; bool echo_host_from_authority_header = false; + bool echo_metadata_initially = false; RpcOptions() {} @@ -689,6 +690,11 @@ class XdsEnd2endTest : public ::testing::TestWithParam, return *this; } + RpcOptions& set_echo_metadata_initially(bool value) { + echo_metadata_initially = value; + return *this; + } + // Populates context and request. void SetupRpc(ClientContext* context, EchoRequest* request) const; }; @@ -970,6 +976,7 @@ class XdsEnd2endTest : public ::testing::TestWithParam, // Returns XdsCredentials with mTLS fallback creds. static std::shared_ptr CreateXdsChannelCredentials(); + static std::shared_ptr CreateTlsChannelCredentials(); // Creates various types of server credentials. static std::shared_ptr CreateFakeServerCredentials(); diff --git a/test/cpp/end2end/xds/xds_gcp_authn_end2end_test.cc b/test/cpp/end2end/xds/xds_gcp_authn_end2end_test.cc new file mode 100644 index 00000000000..d30c22e7605 --- /dev/null +++ b/test/cpp/end2end/xds/xds_gcp_authn_end2end_test.cc @@ -0,0 +1,231 @@ +// +// Copyright 2024 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include + +#include "src/core/client_channel/backup_poller.h" +#include "src/core/lib/config/config_vars.h" +#include "src/core/util/http_client/httpcli.h" +#include "src/proto/grpc/testing/xds/v3/cluster.grpc.pb.h" +#include "src/proto/grpc/testing/xds/v3/gcp_authn.grpc.pb.h" +#include "src/proto/grpc/testing/xds/v3/http_connection_manager.grpc.pb.h" +#include "src/proto/grpc/testing/xds/v3/router.grpc.pb.h" +#include "test/core/test_util/scoped_env_var.h" +#include "test/core/test_util/test_config.h" +#include "test/cpp/end2end/xds/xds_end2end_test_lib.h" + +namespace grpc { +namespace testing { +namespace { + +using ::envoy::extensions::filters::http::gcp_authn::v3::Audience; +using ::envoy::extensions::filters::http::gcp_authn::v3::GcpAuthnFilterConfig; +using ::envoy::extensions::filters::network::http_connection_manager::v3:: + HttpFilter; + +constexpr absl::string_view kFilterInstanceName = "gcp_authn_instance"; +constexpr absl::string_view kAudience = "audience"; + +class XdsGcpAuthnEnd2endTest : public XdsEnd2endTest { + public: + void SetUp() override { + g_audience = ""; + g_token = nullptr; + grpc_core::HttpRequest::SetOverride(HttpGetOverride, nullptr, nullptr); + InitClient(MakeBootstrapBuilder(), /*lb_expected_authority=*/"", + /*xds_resource_does_not_exist_timeout_ms=*/0, + /*balancer_authority_override=*/"", /*args=*/nullptr, + CreateTlsChannelCredentials()); + } + + void TearDown() override { + XdsEnd2endTest::TearDown(); + grpc_core::HttpRequest::SetOverride(nullptr, nullptr, nullptr); + } + + static void ValidateHttpRequest(const grpc_http_request* request, + const grpc_core::URI& uri) { + EXPECT_THAT( + uri.query_parameter_map(), + ::testing::ElementsAre(::testing::Pair("audience", g_audience))); + ASSERT_EQ(request->hdr_count, 1); + EXPECT_EQ(absl::string_view(request->hdrs[0].key), "Metadata-Flavor"); + EXPECT_EQ(absl::string_view(request->hdrs[0].value), "Google"); + } + + static int HttpGetOverride(const grpc_http_request* request, + const grpc_core::URI& uri, + grpc_core::Timestamp /*deadline*/, + grpc_closure* on_done, + grpc_http_response* response) { + // Intercept only requests for GCP service account identity tokens. + if (uri.authority() != "metadata.google.internal." || + uri.path() != + "/computeMetadata/v1/instance/service-accounts/default/identity") { + return 0; + } + // Validate request. + ValidateHttpRequest(request, uri); + // Generate response. + response->status = 200; + response->body = gpr_strdup(const_cast(g_token)); + response->body_length = strlen(g_token); + grpc_core::ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus()); + return 1; + } + + // Constructs a synthetic JWT token that's just valid enough for the + // call creds to extract the expiration date. + static std::string MakeToken(grpc_core::Timestamp expiration) { + gpr_timespec ts = expiration.as_timespec(GPR_CLOCK_REALTIME); + std::string json = absl::StrCat("{\"exp\":", ts.tv_sec, "}"); + return absl::StrCat("foo.", absl::WebSafeBase64Escape(json), ".bar"); + } + + Listener BuildListenerWithGcpAuthnFilter(bool optional = false) { + Listener listener = default_listener_; + HttpConnectionManager hcm = ClientHcmAccessor().Unpack(listener); + HttpFilter* filter0 = hcm.mutable_http_filters(0); + *hcm.add_http_filters() = *filter0; + filter0->set_name(kFilterInstanceName); + if (optional) filter0->set_is_optional(true); + filter0->mutable_typed_config()->PackFrom(GcpAuthnFilterConfig()); + ClientHcmAccessor().Pack(hcm, &listener); + return listener; + } + + Cluster BuildClusterWithAudience(absl::string_view audience) { + Audience audience_proto; + audience_proto.set_url(audience); + Cluster cluster = default_cluster_; + auto& filter_map = + *cluster.mutable_metadata()->mutable_typed_filter_metadata(); + auto& entry = filter_map[kFilterInstanceName]; + entry.PackFrom(audience_proto); + return cluster; + } + + static absl::string_view g_audience; + static const char* g_token; +}; + +absl::string_view XdsGcpAuthnEnd2endTest::g_audience; +const char* XdsGcpAuthnEnd2endTest::g_token; + +INSTANTIATE_TEST_SUITE_P(XdsTest, XdsGcpAuthnEnd2endTest, + ::testing::Values(XdsTestType()), &XdsTestType::Name); + +TEST_P(XdsGcpAuthnEnd2endTest, Basic) { + grpc_core::testing::ScopedExperimentalEnvVar env( + "GRPC_EXPERIMENTAL_XDS_GCP_AUTHENTICATION_FILTER"); + // Construct auth token. + g_audience = kAudience; + std::string token = MakeToken(grpc_core::Timestamp::InfFuture()); + g_token = token.c_str(); + // Set xDS resources. + CreateAndStartBackends(1, /*xds_enabled=*/false, + CreateTlsServerCredentials()); + SetListenerAndRouteConfiguration(balancer_.get(), + BuildListenerWithGcpAuthnFilter(), + default_route_config_); + balancer_->ads_service()->SetCdsResource(BuildClusterWithAudience(kAudience)); + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + // Send an RPC and check that it arrives with the right auth token. + std::multimap server_initial_metadata; + Status status = SendRpc(RpcOptions().set_echo_metadata_initially(true), + /*response=*/nullptr, &server_initial_metadata); + EXPECT_TRUE(status.ok()) << "code=" << status.error_code() + << " message=" << status.error_message(); + EXPECT_THAT(server_initial_metadata, + ::testing::Contains(::testing::Pair( + "authorization", absl::StrCat("Bearer ", g_token)))); +} + +TEST_P(XdsGcpAuthnEnd2endTest, NoOpWhenClusterHasNoAudience) { + grpc_core::testing::ScopedExperimentalEnvVar env( + "GRPC_EXPERIMENTAL_XDS_GCP_AUTHENTICATION_FILTER"); + // Set xDS resources. + CreateAndStartBackends(1, /*xds_enabled=*/false, + CreateTlsServerCredentials()); + SetListenerAndRouteConfiguration(balancer_.get(), + BuildListenerWithGcpAuthnFilter(), + default_route_config_); + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + // Send an RPC and check that it does not have an auth token. + std::multimap server_initial_metadata; + Status status = SendRpc(RpcOptions().set_echo_metadata_initially(true), + /*response=*/nullptr, &server_initial_metadata); + EXPECT_TRUE(status.ok()) << "code=" << status.error_code() + << " message=" << status.error_message(); + EXPECT_THAT( + server_initial_metadata, + ::testing::Not(::testing::Contains(::testing::Key("authorization")))); +} + +TEST_P(XdsGcpAuthnEnd2endTest, FilterIgnoredWhenEnvVarNotSet) { + // Construct auth token. + g_audience = kAudience; + std::string token = MakeToken(grpc_core::Timestamp::InfFuture()); + g_token = token.c_str(); + // Set xDS resources. + CreateAndStartBackends(1, /*xds_enabled=*/false, + CreateTlsServerCredentials()); + SetListenerAndRouteConfiguration( + balancer_.get(), BuildListenerWithGcpAuthnFilter(/*optional=*/true), + default_route_config_); + balancer_->ads_service()->SetCdsResource(BuildClusterWithAudience(kAudience)); + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + // Send an RPC and check that it does not have an auth token. + std::multimap server_initial_metadata; + Status status = SendRpc(RpcOptions().set_echo_metadata_initially(true), + /*response=*/nullptr, &server_initial_metadata); + EXPECT_TRUE(status.ok()) << "code=" << status.error_code() + << " message=" << status.error_message(); + EXPECT_THAT( + server_initial_metadata, + ::testing::Not(::testing::Contains(::testing::Key("authorization")))); +} + +} // namespace +} // namespace testing +} // namespace grpc + +int main(int argc, char** argv) { + grpc::testing::TestEnvironment env(&argc, argv); + ::testing::InitGoogleTest(&argc, argv); + // Make the backup poller poll very frequently in order to pick up + // updates from all the subchannels's FDs. + grpc_core::ConfigVars::Overrides overrides; + overrides.client_channel_backup_poll_interval_ms = 1; + grpc_core::ConfigVars::SetOverrides(overrides); +#if TARGET_OS_IPHONE + // Workaround Apple CFStream bug + grpc_core::SetEnv("grpc_cfstream", "0"); +#endif + grpc_init(); + const auto result = RUN_ALL_TESTS(); + grpc_shutdown(); + return result; +} diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal index e000762b1bc..75cba764be5 100644 --- a/tools/doxygen/Doxyfile.c++.internal +++ b/tools/doxygen/Doxyfile.c++.internal @@ -1150,6 +1150,10 @@ src/core/ext/filters/fault_injection/fault_injection_filter.cc \ src/core/ext/filters/fault_injection/fault_injection_filter.h \ src/core/ext/filters/fault_injection/fault_injection_service_config_parser.cc \ src/core/ext/filters/fault_injection/fault_injection_service_config_parser.h \ +src/core/ext/filters/gcp_authentication/gcp_authentication_filter.cc \ +src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h \ +src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.cc \ +src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h \ src/core/ext/filters/http/client/http_client_filter.cc \ src/core/ext/filters/http/client/http_client_filter.h \ src/core/ext/filters/http/client_authority_filter.cc \ @@ -2577,6 +2581,8 @@ src/core/lib/security/credentials/external/url_external_account_credentials.cc \ src/core/lib/security/credentials/external/url_external_account_credentials.h \ src/core/lib/security/credentials/fake/fake_credentials.cc \ src/core/lib/security/credentials/fake/fake_credentials.h \ +src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc \ +src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h \ src/core/lib/security/credentials/google_default/credentials_generic.cc \ src/core/lib/security/credentials/google_default/google_default_credentials.cc \ src/core/lib/security/credentials/google_default/google_default_credentials.h \ @@ -2947,6 +2953,7 @@ src/core/util/latent_see.cc \ src/core/util/latent_see.h \ src/core/util/linux/cpu.cc \ src/core/util/log.cc \ +src/core/util/lru_cache.h \ src/core/util/msys/tmpfile.cc \ src/core/util/posix/cpu.cc \ src/core/util/posix/string.cc \ @@ -3006,6 +3013,8 @@ src/core/xds/grpc/xds_http_fault_filter.h \ src/core/xds/grpc/xds_http_filter.h \ src/core/xds/grpc/xds_http_filter_registry.cc \ src/core/xds/grpc/xds_http_filter_registry.h \ +src/core/xds/grpc/xds_http_gcp_authn_filter.cc \ +src/core/xds/grpc/xds_http_gcp_authn_filter.h \ src/core/xds/grpc/xds_http_rbac_filter.cc \ src/core/xds/grpc/xds_http_rbac_filter.h \ src/core/xds/grpc/xds_http_stateful_session_filter.cc \ diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal index 1d1d04c9bd0..2c6b3e5be77 100644 --- a/tools/doxygen/Doxyfile.core.internal +++ b/tools/doxygen/Doxyfile.core.internal @@ -951,6 +951,10 @@ src/core/ext/filters/fault_injection/fault_injection_filter.cc \ src/core/ext/filters/fault_injection/fault_injection_filter.h \ src/core/ext/filters/fault_injection/fault_injection_service_config_parser.cc \ src/core/ext/filters/fault_injection/fault_injection_service_config_parser.h \ +src/core/ext/filters/gcp_authentication/gcp_authentication_filter.cc \ +src/core/ext/filters/gcp_authentication/gcp_authentication_filter.h \ +src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.cc \ +src/core/ext/filters/gcp_authentication/gcp_authentication_service_config_parser.h \ src/core/ext/filters/http/client/http_client_filter.cc \ src/core/ext/filters/http/client/http_client_filter.h \ src/core/ext/filters/http/client_authority_filter.cc \ @@ -2348,6 +2352,8 @@ src/core/lib/security/credentials/external/url_external_account_credentials.cc \ src/core/lib/security/credentials/external/url_external_account_credentials.h \ src/core/lib/security/credentials/fake/fake_credentials.cc \ src/core/lib/security/credentials/fake/fake_credentials.h \ +src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.cc \ +src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h \ src/core/lib/security/credentials/google_default/credentials_generic.cc \ src/core/lib/security/credentials/google_default/google_default_credentials.cc \ src/core/lib/security/credentials/google_default/google_default_credentials.h \ @@ -2726,6 +2732,7 @@ src/core/util/latent_see.cc \ src/core/util/latent_see.h \ src/core/util/linux/cpu.cc \ src/core/util/log.cc \ +src/core/util/lru_cache.h \ src/core/util/msys/tmpfile.cc \ src/core/util/posix/cpu.cc \ src/core/util/posix/string.cc \ @@ -2784,6 +2791,8 @@ src/core/xds/grpc/xds_http_fault_filter.h \ src/core/xds/grpc/xds_http_filter.h \ src/core/xds/grpc/xds_http_filter_registry.cc \ src/core/xds/grpc/xds_http_filter_registry.h \ +src/core/xds/grpc/xds_http_gcp_authn_filter.cc \ +src/core/xds/grpc/xds_http_gcp_authn_filter.h \ src/core/xds/grpc/xds_http_rbac_filter.cc \ src/core/xds/grpc/xds_http_rbac_filter.h \ src/core/xds/grpc/xds_http_stateful_session_filter.cc \ diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json index df179bbb3da..5152c4f42e5 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -3975,6 +3975,30 @@ ], "uses_polling": true }, + { + "args": [], + "benchmark": false, + "ci_platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "cpu_cost": 1.0, + "exclude_configs": [], + "exclude_iomgrs": [], + "flaky": false, + "gtest": true, + "language": "c++", + "name": "gcp_authentication_filter_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": false + }, { "args": [], "benchmark": false, @@ -5777,6 +5801,30 @@ ], "uses_polling": false }, + { + "args": [], + "benchmark": false, + "ci_platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "cpu_cost": 1.0, + "exclude_configs": [], + "exclude_iomgrs": [], + "flaky": false, + "gtest": true, + "language": "c++", + "name": "lru_cache_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": false + }, { "args": [], "benchmark": false,