From b9aca8db8ab1f0650f7ff3076c8c4678c9188ba6 Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Tue, 25 Oct 2022 15:03:16 -0700 Subject: [PATCH] Second attempt: xDS Listener: use ValidationErrors and add unit test (#31457) * Revert "Revert "xDS Listener: use ValidationErrors and add unit test (#31351)" (#31456)" This reverts commit ab3d62ae8fc024867b4db85122cebfab56532871. * Revert "Revert "xds_http_filters_test: fix includes for import (#31454)" (#31455)" This reverts commit 32590d110aa2b987e3c46da36911824b35514a9b. * fix import * work around internal differences in Any API (string vs. cord) * iwyu --- CMakeLists.txt | 258 ++- build_autogenerated.yaml | 92 +- src/core/BUILD | 1 + .../resolver/xds/xds_resolver.cc | 72 +- .../fault_injection/fault_injection_filter.h | 4 - .../fault_injection/service_config_parser.cc | 1 - .../fault_injection/service_config_parser.h | 4 + src/core/ext/xds/xds_cluster.cc | 42 +- src/core/ext/xds/xds_http_fault_filter.cc | 104 +- src/core/ext/xds/xds_http_fault_filter.h | 32 +- src/core/ext/xds/xds_http_filters.cc | 106 +- src/core/ext/xds/xds_http_filters.h | 55 +- src/core/ext/xds/xds_http_rbac_filter.cc | 339 ++- src/core/ext/xds/xds_http_rbac_filter.h | 25 +- src/core/ext/xds/xds_listener.cc | 899 ++++---- src/core/ext/xds/xds_listener.h | 71 +- src/core/ext/xds/xds_route_config.cc | 11 +- src/core/ext/xds/xds_server_config_fetcher.cc | 82 +- src/proto/grpc/testing/xds/v3/address.proto | 6 + src/proto/grpc/testing/xds/v3/route.proto | 2 + test/core/xds/BUILD | 43 + .../xds/xds_cluster_resource_type_test.cc | 40 +- test/core/xds/xds_http_filters_test.cc | 929 ++++++++ .../xds/xds_listener_resource_type_test.cc | 1980 +++++++++++++++++ test/cpp/end2end/xds/BUILD | 13 - test/cpp/end2end/xds/no_op_http_filter.h | 70 - test/cpp/end2end/xds/xds_end2end_test.cc | 738 +----- .../xds/xds_outlier_detection_end2end_test.cc | 1 - .../end2end/xds/xds_routing_end2end_test.cc | 404 +--- tools/run_tests/generated/tests.json | 48 + 30 files changed, 4309 insertions(+), 2163 deletions(-) create mode 100644 test/core/xds/xds_http_filters_test.cc create mode 100644 test/core/xds/xds_listener_resource_type_test.cc delete mode 100644 test/cpp/end2end/xds/no_op_http_filter.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b9e7e45885e..ac99b4ca7a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1268,9 +1268,11 @@ 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() + add_dependencies(buildtests_cxx xds_http_filters_test) add_dependencies(buildtests_cxx xds_interop_client) add_dependencies(buildtests_cxx xds_interop_server) add_dependencies(buildtests_cxx xds_lb_policy_registry_test) + add_dependencies(buildtests_cxx xds_listener_resource_type_test) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) add_dependencies(buildtests_cxx xds_outlier_detection_end2end_test) endif() @@ -20610,6 +20612,10 @@ add_executable(xds_cluster_resource_type_test ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/tls.grpc.pb.cc ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/tls.pb.h ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/tls.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/typed_struct.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/typed_struct.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/typed_struct.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/typed_struct.grpc.pb.h test/core/xds/xds_cluster_resource_type_test.cc third_party/googletest/googletest/src/gtest-all.cc third_party/googletest/googlemock/src/gmock-all.cc @@ -21716,6 +21722,116 @@ endif() endif() if(gRPC_BUILD_TESTS) +add_executable(xds_http_filters_test + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/reflection/v1alpha/reflection.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/reflection/v1alpha/reflection.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/reflection/v1alpha/reflection.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/reflection/v1alpha/reflection.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/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/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/fault.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault_common.pb.cc + ${_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/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/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/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/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/core/xds/xds_http_filters_test.cc + test/cpp/util/cli_call.cc + test/cpp/util/cli_credentials.cc + test/cpp/util/proto_file_parser.cc + test/cpp/util/proto_reflection_descriptor_database.cc + test/cpp/util/service_describer.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc +) + +target_include_directories(xds_http_filters_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_http_filters_test + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + absl::flags + grpc++ + grpc_test_util +) + + +endif() +if(gRPC_BUILD_TESTS) + add_executable(xds_interop_client ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/empty.pb.cc ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/empty.grpc.pb.cc @@ -21954,6 +22070,140 @@ target_link_libraries(xds_lb_policy_registry_test ) +endif() +if(gRPC_BUILD_TESTS) + +add_executable(xds_listener_resource_type_test + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/reflection/v1alpha/reflection.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/reflection/v1alpha/reflection.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/reflection/v1alpha/reflection.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/reflection/v1alpha/reflection.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/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/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/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/fault.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault_common.pb.cc + ${_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/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/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/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 + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/tls.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/tls.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/tls.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/tls.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/typed_struct.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/typed_struct.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/typed_struct.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/typed_struct.grpc.pb.h + test/core/xds/xds_listener_resource_type_test.cc + test/cpp/util/cli_call.cc + test/cpp/util/cli_credentials.cc + test/cpp/util/proto_file_parser.cc + test/cpp/util/proto_reflection_descriptor_database.cc + test/cpp/util/service_describer.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc +) + +target_include_directories(xds_listener_resource_type_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_listener_resource_type_test + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + absl::flags + grpc++ + grpc_test_util +) + + endif() if(gRPC_BUILD_TESTS) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) @@ -22516,14 +22766,6 @@ if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) ${_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/fault.pb.cc - ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.grpc.pb.cc - ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.pb.h - ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.grpc.pb.h - ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault_common.pb.cc - ${_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/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 diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index 8da80c4a287..6f1cb11fead 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -11322,6 +11322,7 @@ targets: - src/proto/grpc/testing/xds/v3/regex.proto - src/proto/grpc/testing/xds/v3/string.proto - src/proto/grpc/testing/xds/v3/tls.proto + - src/proto/grpc/testing/xds/v3/typed_struct.proto - test/core/xds/xds_cluster_resource_type_test.cc deps: - grpc_test_util @@ -11581,7 +11582,6 @@ targets: headers: - test/cpp/end2end/counted_service.h - test/cpp/end2end/test_service_impl.h - - test/cpp/end2end/xds/no_op_http_filter.h - test/cpp/end2end/xds/xds_end2end_test_lib.h - test/cpp/end2end/xds/xds_server.h - test/cpp/util/tls_test_utils.h @@ -11701,6 +11701,46 @@ targets: - linux - posix - mac +- name: xds_http_filters_test + gtest: true + build: test + language: c++ + headers: + - test/cpp/util/cli_call.h + - test/cpp/util/cli_credentials.h + - test/cpp/util/config_grpc_cli.h + - test/cpp/util/proto_file_parser.h + - test/cpp/util/proto_reflection_descriptor_database.h + - test/cpp/util/service_describer.h + src: + - src/proto/grpc/reflection/v1alpha/reflection.proto + - src/proto/grpc/testing/xds/v3/address.proto + - src/proto/grpc/testing/xds/v3/base.proto + - src/proto/grpc/testing/xds/v3/expr.proto + - 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/http_filter_rbac.proto + - src/proto/grpc/testing/xds/v3/metadata.proto + - src/proto/grpc/testing/xds/v3/path.proto + - src/proto/grpc/testing/xds/v3/percent.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/core/xds/xds_http_filters_test.cc + - test/cpp/util/cli_call.cc + - test/cpp/util/cli_credentials.cc + - test/cpp/util/proto_file_parser.cc + - test/cpp/util/proto_reflection_descriptor_database.cc + - test/cpp/util/service_describer.cc + deps: + - absl/flags:flag + - grpc++ + - grpc_test_util + uses_polling: false - name: xds_interop_client build: test run: false @@ -11787,6 +11827,52 @@ targets: - grpc++ - grpc_test_util uses_polling: false +- name: xds_listener_resource_type_test + gtest: true + build: test + language: c++ + headers: + - test/cpp/util/cli_call.h + - test/cpp/util/cli_credentials.h + - test/cpp/util/config_grpc_cli.h + - test/cpp/util/proto_file_parser.h + - test/cpp/util/proto_reflection_descriptor_database.h + - test/cpp/util/service_describer.h + src: + - src/proto/grpc/reflection/v1alpha/reflection.proto + - src/proto/grpc/testing/xds/v3/address.proto + - src/proto/grpc/testing/xds/v3/base.proto + - src/proto/grpc/testing/xds/v3/config_source.proto + - src/proto/grpc/testing/xds/v3/expr.proto + - 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/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/metadata.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 + - src/proto/grpc/testing/xds/v3/tls.proto + - src/proto/grpc/testing/xds/v3/typed_struct.proto + - test/core/xds/xds_listener_resource_type_test.cc + - test/cpp/util/cli_call.cc + - test/cpp/util/cli_credentials.cc + - test/cpp/util/proto_file_parser.cc + - test/cpp/util/proto_reflection_descriptor_database.cc + - test/cpp/util/service_describer.cc + deps: + - absl/flags:flag + - grpc++ + - grpc_test_util + uses_polling: false - name: xds_outlier_detection_end2end_test gtest: true build: test @@ -11795,7 +11881,6 @@ targets: headers: - test/cpp/end2end/counted_service.h - test/cpp/end2end/test_service_impl.h - - test/cpp/end2end/xds/no_op_http_filter.h - test/cpp/end2end/xds/xds_end2end_test_lib.h - test/cpp/end2end/xds/xds_server.h - test/cpp/util/tls_test_utils.h @@ -11964,7 +12049,6 @@ targets: headers: - test/cpp/end2end/counted_service.h - test/cpp/end2end/test_service_impl.h - - test/cpp/end2end/xds/no_op_http_filter.h - test/cpp/end2end/xds/xds_end2end_test_lib.h - test/cpp/end2end/xds/xds_server.h - test/cpp/util/tls_test_utils.h @@ -11982,8 +12066,6 @@ targets: - 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/fault.proto - - src/proto/grpc/testing/xds/v3/fault_common.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 diff --git a/src/core/BUILD b/src/core/BUILD index 1cfe7933c70..56417492e00 100644 --- a/src/core/BUILD +++ b/src/core/BUILD @@ -3524,6 +3524,7 @@ grpc_cc_library( "grpc_xds_channel_stack_modifier", "grpc_xds_client", "iomgr_fwd", + "match", "resolved_address", "slice_refcount", "status_helper", diff --git a/src/core/ext/filters/client_channel/resolver/xds/xds_resolver.cc b/src/core/ext/filters/client_channel/resolver/xds/xds_resolver.cc index 564fa6f3c0c..870e67bc416 100644 --- a/src/core/ext/filters/client_channel/resolver/xds/xds_resolver.cc +++ b/src/core/ext/filters/client_channel/resolver/xds/xds_resolver.cc @@ -377,7 +377,7 @@ class XdsResolver : public Resolver { // This will not contain the RouteConfiguration, even if it comes with the // LDS response; instead, the relevant VirtualHost from the // RouteConfiguration will be saved in current_virtual_host_. - XdsListenerResource current_listener_; + XdsListenerResource::HttpConnectionManager current_listener_; std::string route_config_name_; RouteConfigWatcher* route_config_watcher_ = nullptr; @@ -468,8 +468,7 @@ XdsResolver::XdsConfigSelector::XdsConfigSelector( // one. if (!route_action->max_stream_duration.has_value()) { route_action->max_stream_duration = - resolver_->current_listener_.http_connection_manager - .http_max_stream_duration; + resolver_->current_listener_.http_max_stream_duration; } Match( route_action->action, @@ -524,8 +523,7 @@ XdsResolver::XdsConfigSelector::XdsConfigSelector( } } // Populate filter list. - for (const auto& http_filter : - resolver_->current_listener_.http_connection_manager.http_filters) { + for (const auto& http_filter : resolver_->current_listener_.http_filters) { // Find filter. This is guaranteed to succeed, because it's checked // at config validation time in the XdsApi code. const XdsHttpFilterImpl* filter_impl = @@ -602,7 +600,7 @@ XdsResolver::XdsConfigSelector::CreateMethodConfig( } // Handle xDS HTTP filters. auto result = XdsRouting::GeneratePerHTTPFilterConfigs( - resolver_->current_listener_.http_connection_manager.http_filters, + resolver_->current_listener_.http_filters, resolver_->current_virtual_host_, route, cluster_weight, resolver_->args_); if (!result.ok()) return result.status(); @@ -889,39 +887,35 @@ void XdsResolver::OnListenerUpdate(XdsListenerResource listener) { if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_resolver_trace)) { gpr_log(GPR_INFO, "[xds_resolver %p] received updated listener data", this); } - if (xds_client_ == nullptr) { - return; - } - if (listener.http_connection_manager.route_config_name != - route_config_name_) { - if (route_config_watcher_ != nullptr) { - XdsRouteConfigResourceType::CancelWatch( - xds_client_.get(), route_config_name_, route_config_watcher_, - /*delay_unsubscription=*/ - !listener.http_connection_manager.route_config_name.empty()); - route_config_watcher_ = nullptr; - } - route_config_name_ = - std::move(listener.http_connection_manager.route_config_name); - if (!route_config_name_.empty()) { - current_virtual_host_.routes.clear(); - auto watcher = MakeRefCounted(Ref()); - route_config_watcher_ = watcher.get(); - XdsRouteConfigResourceType::StartWatch( - xds_client_.get(), route_config_name_, std::move(watcher)); - } - } - current_listener_ = std::move(listener); - if (route_config_name_.empty()) { - GPR_ASSERT( - current_listener_.http_connection_manager.rds_update.has_value()); - OnRouteConfigUpdate( - std::move(*current_listener_.http_connection_manager.rds_update)); - } else { - // HCM may contain newer filter config. We need to propagate the update as - // config selector to the channel - GenerateResult(); - } + if (xds_client_ == nullptr) return; + current_listener_ = std::move( + absl::get(listener.listener)); + MatchMutable( + ¤t_listener_.route_config, + // RDS resource name + [&](std::string* rds_name) { + if (route_config_watcher_ != nullptr) { + XdsRouteConfigResourceType::CancelWatch( + xds_client_.get(), route_config_name_, route_config_watcher_, + /*delay_unsubscription=*/!rds_name->empty()); + route_config_watcher_ = nullptr; + } + route_config_name_ = std::move(*rds_name); + if (!route_config_name_.empty()) { + current_virtual_host_.routes.clear(); + auto watcher = MakeRefCounted(Ref()); + route_config_watcher_ = watcher.get(); + XdsRouteConfigResourceType::StartWatch( + xds_client_.get(), route_config_name_, std::move(watcher)); + } + // HCM may contain newer filter config. We need to propagate the + // update as config selector to the channel. + GenerateResult(); + }, + // inlined RouteConfig + [&](XdsRouteConfigResource* route_config) { + OnRouteConfigUpdate(std::move(*route_config)); + }); } namespace { diff --git a/src/core/ext/filters/fault_injection/fault_injection_filter.h b/src/core/ext/filters/fault_injection/fault_injection_filter.h index 611c3d7f45b..d9c08cc7011 100644 --- a/src/core/ext/filters/fault_injection/fault_injection_filter.h +++ b/src/core/ext/filters/fault_injection/fault_injection_filter.h @@ -34,10 +34,6 @@ #include "src/core/lib/promise/arena_promise.h" #include "src/core/lib/transport/transport.h" -// Channel arg key for enabling parsing fault injection via method config. -#define GRPC_ARG_PARSE_FAULT_INJECTION_METHOD_CONFIG \ - "grpc.parse_fault_injection_method_config" - namespace grpc_core { // This channel filter is intended to be used by the dynamic filters, instead diff --git a/src/core/ext/filters/fault_injection/service_config_parser.cc b/src/core/ext/filters/fault_injection/service_config_parser.cc index d9e9d229398..5670d549856 100644 --- a/src/core/ext/filters/fault_injection/service_config_parser.cc +++ b/src/core/ext/filters/fault_injection/service_config_parser.cc @@ -25,7 +25,6 @@ #include "absl/strings/str_cat.h" #include "absl/types/optional.h" -#include "src/core/ext/filters/fault_injection/fault_injection_filter.h" #include "src/core/lib/channel/channel_args.h" #include "src/core/lib/channel/status_util.h" #include "src/core/lib/gprpp/status_helper.h" diff --git a/src/core/ext/filters/fault_injection/service_config_parser.h b/src/core/ext/filters/fault_injection/service_config_parser.h index af266e0f1fd..8961fa30b74 100644 --- a/src/core/ext/filters/fault_injection/service_config_parser.h +++ b/src/core/ext/filters/fault_injection/service_config_parser.h @@ -39,6 +39,10 @@ #include "src/core/lib/json/json.h" #include "src/core/lib/service_config/service_config_parser.h" +// Channel arg key for enabling parsing fault injection via method config. +#define GRPC_ARG_PARSE_FAULT_INJECTION_METHOD_CONFIG \ + "grpc.internal.parse_fault_injection_method_config" + namespace grpc_core { class FaultInjectionMethodParsedConfig diff --git a/src/core/ext/xds/xds_cluster.cc b/src/core/ext/xds/xds_cluster.cc index 0287b967617..86ea34c1fb2 100644 --- a/src/core/ext/xds/xds_cluster.cc +++ b/src/core/ext/xds/xds_cluster.cc @@ -27,6 +27,7 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" #include "absl/strings/strip.h" +#include "absl/types/variant.h" #include "envoy/config/cluster/v3/circuit_breaker.upb.h" #include "envoy/config/cluster/v3/cluster.upb.h" #include "envoy/config/cluster/v3/cluster.upbdefs.h" @@ -53,6 +54,7 @@ #include "src/core/lib/gprpp/host_port.h" #include "src/core/lib/gprpp/time.h" #include "src/core/lib/gprpp/validation_errors.h" +#include "src/core/lib/matchers/matchers.h" namespace grpc_core { @@ -100,39 +102,34 @@ std::string XdsClusterResource::ToString() const { namespace { -absl::optional UpstreamTlsContextParse( +CommonTlsContext UpstreamTlsContextParse( const XdsResourceType::DecodeContext& context, const envoy_config_core_v3_TransportSocket* transport_socket, ValidationErrors* errors) { ValidationErrors::ScopedField field(errors, ".typed_config"); const auto* typed_config = envoy_config_core_v3_TransportSocket_typed_config(transport_socket); - if (typed_config == nullptr) { - errors->AddError("field not present"); - return absl::nullopt; - } - absl::string_view type_url = absl::StripPrefix( - UpbStringToAbsl(google_protobuf_Any_type_url(typed_config)), - "type.googleapis.com/"); - if (type_url != + auto extension = ExtractXdsExtension(context, typed_config, errors); + if (!extension.has_value()) return {}; + if (extension->type != "envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext") { ValidationErrors::ScopedField field(errors, ".type_url"); - errors->AddError( - absl::StrCat("unrecognized transport socket type: ", type_url)); - return absl::nullopt; + errors->AddError("unsupported transport socket type"); + return {}; + } + absl::string_view* serialized_upstream_tls_context = + absl::get_if(&extension->value); + if (serialized_upstream_tls_context == nullptr) { + errors->AddError("can't decode UpstreamTlsContext"); + return {}; } - ValidationErrors::ScopedField field2( - errors, - ".value[envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext]"); - absl::string_view serialized_upstream_tls_context = - UpbStringToAbsl(google_protobuf_Any_value(typed_config)); const auto* upstream_tls_context = envoy_extensions_transport_sockets_tls_v3_UpstreamTlsContext_parse( - serialized_upstream_tls_context.data(), - serialized_upstream_tls_context.size(), context.arena); + serialized_upstream_tls_context->data(), + serialized_upstream_tls_context->size(), context.arena); if (upstream_tls_context == nullptr) { errors->AddError("can't decode UpstreamTlsContext"); - return absl::nullopt; + return {}; } ValidationErrors::ScopedField field3(errors, ".common_tls_context"); const auto* common_tls_context_proto = @@ -394,11 +391,8 @@ absl::StatusOr CdsResourceParse( envoy_config_cluster_v3_Cluster_transport_socket(cluster); if (transport_socket != nullptr) { ValidationErrors::ScopedField field(&errors, ".transport_socket"); - auto common_tls_context = + cds_update.common_tls_context = UpstreamTlsContextParse(context, transport_socket, &errors); - if (common_tls_context.has_value()) { - cds_update.common_tls_context = std::move(*common_tls_context); - } } // Record LRS server name (if any). const envoy_config_core_v3_ConfigSource* lrs_server = diff --git a/src/core/ext/xds/xds_http_fault_filter.cc b/src/core/ext/xds/xds_http_fault_filter.cc index 37ea234b2ce..11e7b1b38de 100644 --- a/src/core/ext/xds/xds_http_fault_filter.cc +++ b/src/core/ext/xds/xds_http_fault_filter.cc @@ -24,7 +24,6 @@ #include #include -#include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" @@ -39,6 +38,7 @@ #include #include "src/core/ext/filters/fault_injection/fault_injection_filter.h" +#include "src/core/ext/filters/fault_injection/service_config_parser.h" #include "src/core/ext/xds/xds_common_types.h" #include "src/core/ext/xds/xds_http_filters.h" #include "src/core/lib/channel/channel_args.h" @@ -50,9 +50,6 @@ namespace grpc_core { -const char* kXdsHttpFaultFilterConfigName = - "envoy.extensions.filters.http.fault.v3.HTTPFault"; - namespace { uint32_t GetDenominator(const envoy_type_v3_FractionalPercent* fraction) { @@ -74,13 +71,36 @@ uint32_t GetDenominator(const envoy_type_v3_FractionalPercent* fraction) { return 100; } -absl::StatusOr ParseHttpFaultIntoJson( - absl::string_view serialized_http_fault, upb_Arena* arena) { +} // namespace + +absl::string_view XdsHttpFaultFilter::ConfigProtoName() const { + return "envoy.extensions.filters.http.fault.v3.HTTPFault"; +} + +absl::string_view XdsHttpFaultFilter::OverrideConfigProtoName() const { + return ""; +} + +void XdsHttpFaultFilter::PopulateSymtab(upb_DefPool* symtab) const { + envoy_extensions_filters_http_fault_v3_HTTPFault_getmsgdef(symtab); +} + +absl::optional +XdsHttpFaultFilter::GenerateFilterConfig(XdsExtension extension, + upb_Arena* arena, + ValidationErrors* errors) const { + absl::string_view* serialized_filter_config = + absl::get_if(&extension.value); + if (serialized_filter_config == nullptr) { + errors->AddError("could not parse fault injection filter config"); + return absl::nullopt; + } auto* http_fault = envoy_extensions_filters_http_fault_v3_HTTPFault_parse( - serialized_http_fault.data(), serialized_http_fault.size(), arena); + serialized_filter_config->data(), serialized_filter_config->size(), + arena); if (http_fault == nullptr) { - return absl::InvalidArgumentError( - "could not parse fault injection filter config"); + errors->AddError("could not parse fault injection filter config"); + return absl::nullopt; } // NOTE(lidiz): Here, we are manually translating the upb messages into the // JSON form of the filter config as part of method config, which will be @@ -95,6 +115,7 @@ absl::StatusOr ParseHttpFaultIntoJson( const auto* fault_abort = envoy_extensions_filters_http_fault_v3_HTTPFault_abort(http_fault); if (fault_abort != nullptr) { + ValidationErrors::ScopedField field(errors, ".abort"); grpc_status_code abort_grpc_status_code = GRPC_STATUS_OK; // Try if gRPC status code is set first int abort_grpc_status_code_raw = @@ -103,8 +124,9 @@ absl::StatusOr ParseHttpFaultIntoJson( if (abort_grpc_status_code_raw != 0) { if (!grpc_status_code_from_int(abort_grpc_status_code_raw, &abort_grpc_status_code)) { - return absl::InvalidArgumentError(absl::StrCat( - "invalid gRPC status code: ", abort_grpc_status_code_raw)); + ValidationErrors::ScopedField field(errors, ".grpc_status"); + errors->AddError(absl::StrCat("invalid gRPC status code: ", + abort_grpc_status_code_raw)); } } else { // if gRPC status code is empty, check http status @@ -131,23 +153,25 @@ absl::StatusOr ParseHttpFaultIntoJson( auto* percent = envoy_extensions_filters_http_fault_v3_FaultAbort_percentage( fault_abort); - fault_injection_policy_json["abortPercentageNumerator"] = - Json(envoy_type_v3_FractionalPercent_numerator(percent)); - fault_injection_policy_json["abortPercentageDenominator"] = - Json(GetDenominator(percent)); + if (percent != nullptr) { + fault_injection_policy_json["abortPercentageNumerator"] = + envoy_type_v3_FractionalPercent_numerator(percent); + fault_injection_policy_json["abortPercentageDenominator"] = + GetDenominator(percent); + } } // Section 2: Parse the delay injection config const auto* fault_delay = envoy_extensions_filters_http_fault_v3_HTTPFault_delay(http_fault); if (fault_delay != nullptr) { + ValidationErrors::ScopedField field(errors, ".delay"); // Parse the delay duration const auto* delay_duration = envoy_extensions_filters_common_fault_v3_FaultDelay_fixed_delay( fault_delay); if (delay_duration != nullptr) { - ValidationErrors errors; - Duration duration = ParseDuration(delay_duration, &errors); - if (!errors.ok()) return errors.status("fixed_delay"); + ValidationErrors::ScopedField field(errors, ".fixed_delay"); + Duration duration = ParseDuration(delay_duration, errors); fault_injection_policy_json["delay"] = duration.ToJsonString(); } // Set the headers if we enabled header delay injection control @@ -162,10 +186,12 @@ absl::StatusOr ParseHttpFaultIntoJson( auto* percent = envoy_extensions_filters_common_fault_v3_FaultDelay_percentage( fault_delay); - fault_injection_policy_json["delayPercentageNumerator"] = - Json(envoy_type_v3_FractionalPercent_numerator(percent)); - fault_injection_policy_json["delayPercentageDenominator"] = - Json(GetDenominator(percent)); + if (percent != nullptr) { + fault_injection_policy_json["delayPercentageNumerator"] = + envoy_type_v3_FractionalPercent_numerator(percent); + fault_injection_policy_json["delayPercentageDenominator"] = + GetDenominator(percent); + } } // Section 3: Parse the maximum active faults const auto* max_fault_wrapper = @@ -175,38 +201,16 @@ absl::StatusOr ParseHttpFaultIntoJson( fault_injection_policy_json["maxFaults"] = google_protobuf_UInt32Value_value(max_fault_wrapper); } - return fault_injection_policy_json; -} - -} // namespace - -void XdsHttpFaultFilter::PopulateSymtab(upb_DefPool* symtab) const { - envoy_extensions_filters_http_fault_v3_HTTPFault_getmsgdef(symtab); -} - -absl::StatusOr -XdsHttpFaultFilter::GenerateFilterConfig(XdsExtension extension, - upb_Arena* arena) const { - absl::string_view* serialized_filter_config = - absl::get_if(&extension.value); - if (serialized_filter_config == nullptr) { - return absl::InvalidArgumentError( - "could not parse fault injection filter config"); - } - absl::StatusOr parse_result = - ParseHttpFaultIntoJson(*serialized_filter_config, arena); - if (!parse_result.ok()) { - return parse_result.status(); - } - return FilterConfig{kXdsHttpFaultFilterConfigName, std::move(*parse_result)}; + return FilterConfig{ConfigProtoName(), + std::move(fault_injection_policy_json)}; } -absl::StatusOr -XdsHttpFaultFilter::GenerateFilterConfigOverride(XdsExtension extension, - upb_Arena* arena) const { +absl::optional +XdsHttpFaultFilter::GenerateFilterConfigOverride( + XdsExtension extension, upb_Arena* arena, 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(std::move(extension), arena); + return GenerateFilterConfig(std::move(extension), arena, errors); } const grpc_channel_filter* XdsHttpFaultFilter::channel_filter() const { diff --git a/src/core/ext/xds/xds_http_fault_filter.h b/src/core/ext/xds/xds_http_fault_filter.h index f075f6ad8c3..a72a6c5cab5 100644 --- a/src/core/ext/xds/xds_http_fault_filter.h +++ b/src/core/ext/xds/xds_http_fault_filter.h @@ -20,6 +20,8 @@ #include #include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" #include "upb/arena.h" #include "upb/def.h" @@ -27,40 +29,30 @@ #include "src/core/ext/xds/xds_http_filters.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" namespace grpc_core { -extern const char* kXdsHttpFaultFilterConfigName; - class XdsHttpFaultFilter : public XdsHttpFilterImpl { public: - // Overrides the PopulateSymtab method + absl::string_view ConfigProtoName() const override; + absl::string_view OverrideConfigProtoName() const override; void PopulateSymtab(upb_DefPool* symtab) const override; - - // Overrides the GenerateFilterConfig method - absl::StatusOr GenerateFilterConfig( - XdsExtension extension, upb_Arena* arena) const override; - - // Overrides the GenerateFilterConfigOverride method - absl::StatusOr GenerateFilterConfigOverride( - XdsExtension extension, upb_Arena* arena) const override; - - // Overrides the channel_filter method + absl::optional GenerateFilterConfig( + XdsExtension extension, upb_Arena* arena, + ValidationErrors* errors) const override; + absl::optional GenerateFilterConfigOverride( + XdsExtension extension, upb_Arena* arena, + ValidationErrors* errors) const override; const grpc_channel_filter* channel_filter() const override; - - // Overrides the ModifyChannelArgs method ChannelArgs ModifyChannelArgs(const ChannelArgs& args) const override; - - // Overrides the GenerateServiceConfig method absl::StatusOr GenerateServiceConfig( const FilterConfig& hcm_filter_config, const FilterConfig* filter_config_override) const override; - bool IsSupportedOnClients() const override { return true; } - bool IsSupportedOnServers() const override { return false; } }; } // namespace grpc_core -#endif /* GRPC_CORE_EXT_XDS_XDS_HTTP_FAULT_FILTER_H */ +#endif // GRPC_CORE_EXT_XDS_XDS_HTTP_FAULT_FILTER_H diff --git a/src/core/ext/xds/xds_http_filters.cc b/src/core/ext/xds/xds_http_filters.cc index a700dd597b6..ac2089c4fde 100644 --- a/src/core/ext/xds/xds_http_filters.cc +++ b/src/core/ext/xds/xds_http_filters.cc @@ -23,63 +23,65 @@ #include #include -#include "absl/status/status.h" #include "absl/types/variant.h" #include "envoy/extensions/filters/http/router/v3/router.upb.h" #include "envoy/extensions/filters/http/router/v3/router.upbdefs.h" +#include + #include "src/core/ext/xds/xds_http_fault_filter.h" #include "src/core/ext/xds/xds_http_rbac_filter.h" namespace grpc_core { -const char* kXdsHttpRouterFilterConfigName = - "envoy.extensions.filters.http.router.v3.Router"; +// +// XdsHttpRouterFilter +// -namespace { +absl::string_view XdsHttpRouterFilter::ConfigProtoName() const { + return "envoy.extensions.filters.http.router.v3.Router"; +} -class XdsHttpRouterFilter : public XdsHttpFilterImpl { - public: - void PopulateSymtab(upb_DefPool* symtab) const override { - envoy_extensions_filters_http_router_v3_Router_getmsgdef(symtab); - } +absl::string_view XdsHttpRouterFilter::OverrideConfigProtoName() const { + return ""; +} - absl::StatusOr GenerateFilterConfig( - XdsExtension extension, upb_Arena* arena) const override { - absl::string_view* serialized_filter_config = - absl::get_if(&extension.value); - if (serialized_filter_config == nullptr) { - return absl::InvalidArgumentError("could not parse router filter config"); - } - if (envoy_extensions_filters_http_router_v3_Router_parse( - serialized_filter_config->data(), serialized_filter_config->size(), - arena) == nullptr) { - return absl::InvalidArgumentError("could not parse router filter config"); - } - return FilterConfig{kXdsHttpRouterFilterConfigName, Json()}; - } +void XdsHttpRouterFilter::PopulateSymtab(upb_DefPool* symtab) const { + envoy_extensions_filters_http_router_v3_Router_getmsgdef(symtab); +} - absl::StatusOr GenerateFilterConfigOverride( - XdsExtension /*extension*/, upb_Arena* /*arena*/) const override { - return absl::InvalidArgumentError( - "router filter does not support config override"); +absl::optional +XdsHttpRouterFilter::GenerateFilterConfig(XdsExtension extension, + upb_Arena* arena, + ValidationErrors* errors) const { + absl::string_view* serialized_filter_config = + absl::get_if(&extension.value); + if (serialized_filter_config == nullptr) { + errors->AddError("could not parse router filter config"); + return absl::nullopt; } - - const grpc_channel_filter* channel_filter() const override { return nullptr; } - - // No-op. This will never be called, since channel_filter() returns null. - absl::StatusOr GenerateServiceConfig( - const FilterConfig& /*hcm_filter_config*/, - const FilterConfig* /*filter_config_override*/) const override { - return absl::UnimplementedError("router filter should never be called"); + if (envoy_extensions_filters_http_router_v3_Router_parse( + serialized_filter_config->data(), serialized_filter_config->size(), + arena) == nullptr) { + errors->AddError("could not parse router filter config"); + return absl::nullopt; } + return FilterConfig{ConfigProtoName(), Json()}; +} - bool IsSupportedOnClients() const override { return true; } +absl::optional +XdsHttpRouterFilter::GenerateFilterConfigOverride( + XdsExtension /*extension*/, upb_Arena* /*arena*/, + ValidationErrors* errors) const { + errors->AddError("router filter does not support config override"); + return absl::nullopt; +} - bool IsSupportedOnServers() const override { return true; } +// +// XdsHttpFilterRegistry +// - bool IsTerminalFilter() const override { return true; } -}; +namespace { using FilterOwnerList = std::vector>; using FilterRegistryMap = std::map; @@ -90,10 +92,13 @@ FilterRegistryMap* g_filter_registry = nullptr; } // namespace void XdsHttpFilterRegistry::RegisterFilter( - std::unique_ptr filter, - const std::set& config_proto_type_names) { - for (auto config_proto_type_name : config_proto_type_names) { - (*g_filter_registry)[config_proto_type_name] = filter.get(); + std::unique_ptr filter) { + GPR_ASSERT(g_filter_registry->emplace(filter->ConfigProtoName(), filter.get()) + .second); + auto override_proto_name = filter->OverrideConfigProtoName(); + if (!override_proto_name.empty()) { + GPR_ASSERT( + g_filter_registry->emplace(override_proto_name, filter.get()).second); } g_filters->push_back(std::move(filter)); } @@ -111,17 +116,14 @@ void XdsHttpFilterRegistry::PopulateSymtab(upb_DefPool* symtab) { } } -void XdsHttpFilterRegistry::Init() { +void XdsHttpFilterRegistry::Init(bool register_builtins) { g_filters = new FilterOwnerList; g_filter_registry = new FilterRegistryMap; - RegisterFilter(std::make_unique(), - {kXdsHttpRouterFilterConfigName}); - RegisterFilter(std::make_unique(), - {kXdsHttpFaultFilterConfigName}); - RegisterFilter(std::make_unique(), - {kXdsHttpRbacFilterConfigName}); - RegisterFilter(std::make_unique(), - {kXdsHttpRbacFilterConfigOverrideName}); + if (register_builtins) { + RegisterFilter(std::make_unique()); + RegisterFilter(std::make_unique()); + RegisterFilter(std::make_unique()); + } } void XdsHttpFilterRegistry::Shutdown() { diff --git a/src/core/ext/xds/xds_http_filters.h b/src/core/ext/xds/xds_http_filters.h index 3f5dd4724af..88d659905a0 100644 --- a/src/core/ext/xds/xds_http_filters.h +++ b/src/core/ext/xds/xds_http_filters.h @@ -20,24 +20,24 @@ #include #include -#include #include +#include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" +#include "absl/types/optional.h" #include "upb/arena.h" #include "upb/def.h" #include "src/core/ext/xds/xds_common_types.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/lib/json/json.h" namespace grpc_core { -extern const char* kXdsHttpRouterFilterConfigName; - class XdsHttpFilterImpl { public: struct FilterConfig { @@ -69,25 +69,33 @@ class XdsHttpFilterImpl { virtual ~XdsHttpFilterImpl() = default; + // Returns the top-level filter config proto message name. + virtual absl::string_view ConfigProtoName() const = 0; + + // Returns the override filter config proto message name. + // If empty, no override type is supported. + virtual absl::string_view OverrideConfigProtoName() const = 0; + // Loads the proto message into the upb symtab. virtual void PopulateSymtab(upb_DefPool* symtab) const = 0; // Generates a Config from the xDS filter config proto. // Used for the top-level config in the HCM HTTP filter list. - virtual absl::StatusOr GenerateFilterConfig( - XdsExtension extension, upb_Arena* arena) const = 0; + virtual absl::optional GenerateFilterConfig( + XdsExtension extension, upb_Arena* arena, + 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::StatusOr GenerateFilterConfigOverride( - XdsExtension extension, upb_Arena* arena) const = 0; + virtual absl::optional GenerateFilterConfigOverride( + XdsExtension extension, upb_Arena* arena, + ValidationErrors* errors) const = 0; // C-core channel filter implementation. virtual const grpc_channel_filter* channel_filter() const = 0; // Modifies channel args that may affect service config parsing (not // visible to the channel as a whole). - // Takes ownership of args. Caller takes ownership of return value. virtual ChannelArgs ModifyChannelArgs(const ChannelArgs& args) const { return args; } @@ -112,11 +120,32 @@ class XdsHttpFilterImpl { virtual bool IsTerminalFilter() const { return false; } }; +class XdsHttpRouterFilter : 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( + XdsExtension extension, upb_Arena* arena, + ValidationErrors* errors) const override; + absl::optional GenerateFilterConfigOverride( + XdsExtension extension, upb_Arena* arena, + ValidationErrors* errors) const override; + const grpc_channel_filter* channel_filter() const override { return nullptr; } + absl::StatusOr GenerateServiceConfig( + 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"); + } + bool IsSupportedOnClients() const override { return true; } + bool IsSupportedOnServers() const override { return true; } + bool IsTerminalFilter() const override { return true; } +}; + class XdsHttpFilterRegistry { public: - static void RegisterFilter( - std::unique_ptr filter, - const std::set& config_proto_type_names); + static void RegisterFilter(std::unique_ptr filter); static const XdsHttpFilterImpl* GetFilterForType( absl::string_view proto_type_name); @@ -124,10 +153,10 @@ class XdsHttpFilterRegistry { static void PopulateSymtab(upb_DefPool* symtab); // Global init and shutdown. - static void Init(); + static void Init(bool register_builtins = true); static void Shutdown(); }; } // namespace grpc_core -#endif /* GRPC_CORE_EXT_XDS_XDS_HTTP_FILTERS_H */ +#endif // GRPC_CORE_EXT_XDS_XDS_HTTP_FILTERS_H diff --git a/src/core/ext/xds/xds_http_rbac_filter.cc b/src/core/ext/xds/xds_http_rbac_filter.cc index 6f2d722357c..bf4cd463045 100644 --- a/src/core/ext/xds/xds_http_rbac_filter.cc +++ b/src/core/ext/xds/xds_http_rbac_filter.cc @@ -25,12 +25,9 @@ #include #include #include -#include -#include "absl/status/status.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" -#include "absl/strings/str_join.h" #include "absl/strings/string_view.h" #include "absl/types/variant.h" #include "envoy/config/core/v3/address.upb.h" @@ -54,12 +51,6 @@ namespace grpc_core { -const char* kXdsHttpRbacFilterConfigName = - "envoy.extensions.filters.http.rbac.v3.RBAC"; - -const char* kXdsHttpRbacFilterConfigOverrideName = - "envoy.extensions.filters.http.rbac.v3.RBACPerRoute"; - namespace { Json ParseRegexMatcherToJson( @@ -74,18 +65,20 @@ Json ParseInt64RangeToJson(const envoy_type_v3_Int64Range* range) { {"end", envoy_type_v3_Int64Range_end(range)}}; } -absl::StatusOr ParseHeaderMatcherToJson( - const envoy_config_route_v3_HeaderMatcher* header) { +Json ParseHeaderMatcherToJson(const envoy_config_route_v3_HeaderMatcher* header, + ValidationErrors* errors) { Json::Object header_json; - std::vector errors; - std::string name = - UpbStringToStdString(envoy_config_route_v3_HeaderMatcher_name(header)); - if (name == ":scheme") { - errors.emplace_back("':scheme' not allowed in header"); - } else if (absl::StartsWith(name, "grpc-")) { - errors.emplace_back("'grpc-' prefixes not allowed in header"); + { + ValidationErrors::ScopedField field(errors, ".name"); + std::string name = + UpbStringToStdString(envoy_config_route_v3_HeaderMatcher_name(header)); + if (name == ":scheme") { + errors->AddError("':scheme' not allowed in header"); + } else if (absl::StartsWith(name, "grpc-")) { + errors->AddError("'grpc-' prefixes not allowed in header"); + } + header_json.emplace("name", std::move(name)); } - header_json.emplace("name", std::move(name)); if (envoy_config_route_v3_HeaderMatcher_has_exact_match(header)) { header_json.emplace( "exactMatch", @@ -121,19 +114,16 @@ absl::StatusOr ParseHeaderMatcherToJson( UpbStringToStdString( envoy_config_route_v3_HeaderMatcher_contains_match(header))); } else { - errors.emplace_back("Invalid route header matcher specified."); - } - if (!errors.empty()) { - return absl::InvalidArgumentError(absl::StrCat( - "errors parsing HeaderMatcher: [", absl::StrJoin(errors, "; "), "]")); + errors->AddError("invalid route header matcher specified"); } header_json.emplace("invertMatch", envoy_config_route_v3_HeaderMatcher_invert_match(header)); return header_json; } -absl::StatusOr ParseStringMatcherToJson( - const envoy_type_matcher_v3_StringMatcher* matcher) { +Json ParseStringMatcherToJson( + const envoy_type_matcher_v3_StringMatcher* matcher, + ValidationErrors* errors) { Json::Object json; if (envoy_type_matcher_v3_StringMatcher_has_exact(matcher)) { json.emplace("exact", @@ -156,26 +146,23 @@ absl::StatusOr ParseStringMatcherToJson( UpbStringToStdString( envoy_type_matcher_v3_StringMatcher_contains(matcher))); } else { - return absl::InvalidArgumentError("StringMatcher: Invalid match pattern"); + errors->AddError("invalid match pattern"); } json.emplace("ignoreCase", envoy_type_matcher_v3_StringMatcher_ignore_case(matcher)); return json; } -absl::StatusOr ParsePathMatcherToJson( - const envoy_type_matcher_v3_PathMatcher* matcher) { +Json ParsePathMatcherToJson(const envoy_type_matcher_v3_PathMatcher* matcher, + ValidationErrors* errors) { + ValidationErrors::ScopedField field(errors, ".path"); const auto* path = envoy_type_matcher_v3_PathMatcher_path(matcher); if (path == nullptr) { - return absl::InvalidArgumentError("PathMatcher has empty path"); + errors->AddError("field not present"); + return Json(); } - Json::Object json; - auto path_json = ParseStringMatcherToJson(path); - if (!path_json.ok()) { - return path_json; - } - json.emplace("path", std::move(*path_json)); - return json; + Json path_json = ParseStringMatcherToJson(path, errors); + return Json::Object{{"path", std::move(path_json)}}; } Json ParseUInt32ValueToJson(const google_protobuf_UInt32Value* value) { @@ -205,65 +192,49 @@ Json ParseMetadataMatcherToJson( return json; } -absl::StatusOr ParsePermissionToJson( - const envoy_config_rbac_v3_Permission* permission) { +Json ParsePermissionToJson(const envoy_config_rbac_v3_Permission* permission, + ValidationErrors* errors) { Json::Object permission_json; // Helper function to parse Permission::Set to JSON. Used by `and_rules` and // `or_rules`. auto parse_permission_set_to_json = - [](const envoy_config_rbac_v3_Permission_Set* set) - -> absl::StatusOr { - std::vector errors; + [errors](const envoy_config_rbac_v3_Permission_Set* set) -> Json { Json::Array rules_json; size_t size; const envoy_config_rbac_v3_Permission* const* rules = envoy_config_rbac_v3_Permission_Set_rules(set, &size); for (size_t i = 0; i < size; ++i) { - auto permission_json = ParsePermissionToJson(rules[i]); - if (!permission_json.ok()) { - errors.emplace_back(permission_json.status().message()); - } else { - rules_json.emplace_back(std::move(*permission_json)); - } - } - if (!errors.empty()) { - return absl::InvalidArgumentError(absl::StrCat( - "errors parsing Set: [", absl::StrJoin(errors, "; "), "]")); + ValidationErrors::ScopedField field(errors, + absl::StrCat(".rules[", i, "]")); + Json permission_json = ParsePermissionToJson(rules[i], errors); + rules_json.emplace_back(std::move(permission_json)); } return Json::Object({{"rules", std::move(rules_json)}}); }; if (envoy_config_rbac_v3_Permission_has_and_rules(permission)) { + ValidationErrors::ScopedField field(errors, ".and_permission"); const auto* and_rules = envoy_config_rbac_v3_Permission_and_rules(permission); - auto permission_set_json = parse_permission_set_to_json(and_rules); - if (!permission_set_json.ok()) { - return permission_set_json; - } - permission_json.emplace("andRules", std::move(*permission_set_json)); + Json permission_set_json = parse_permission_set_to_json(and_rules); + permission_json.emplace("andRules", std::move(permission_set_json)); } else if (envoy_config_rbac_v3_Permission_has_or_rules(permission)) { + ValidationErrors::ScopedField field(errors, ".or_permission"); const auto* or_rules = envoy_config_rbac_v3_Permission_or_rules(permission); - auto permission_set_json = parse_permission_set_to_json(or_rules); - if (!permission_set_json.ok()) { - return permission_set_json; - } - permission_json.emplace("orRules", std::move(*permission_set_json)); + Json permission_set_json = parse_permission_set_to_json(or_rules); + permission_json.emplace("orRules", std::move(permission_set_json)); } else if (envoy_config_rbac_v3_Permission_has_any(permission)) { permission_json.emplace("any", envoy_config_rbac_v3_Permission_any(permission)); } else if (envoy_config_rbac_v3_Permission_has_header(permission)) { - auto header_json = ParseHeaderMatcherToJson( - envoy_config_rbac_v3_Permission_header(permission)); - if (!header_json.ok()) { - return header_json; - } - permission_json.emplace("header", std::move(*header_json)); + ValidationErrors::ScopedField field(errors, ".header"); + Json header_json = ParseHeaderMatcherToJson( + envoy_config_rbac_v3_Permission_header(permission), errors); + permission_json.emplace("header", std::move(header_json)); } else if (envoy_config_rbac_v3_Permission_has_url_path(permission)) { - auto url_path_json = ParsePathMatcherToJson( - envoy_config_rbac_v3_Permission_url_path(permission)); - if (!url_path_json.ok()) { - return url_path_json; - } - permission_json.emplace("urlPath", std::move(*url_path_json)); + ValidationErrors::ScopedField field(errors, ".url_path"); + Json url_path_json = ParsePathMatcherToJson( + envoy_config_rbac_v3_Permission_url_path(permission), errors); + permission_json.emplace("urlPath", std::move(url_path_json)); } else if (envoy_config_rbac_v3_Permission_has_destination_ip(permission)) { permission_json.emplace( "destinationIp", @@ -278,69 +249,53 @@ absl::StatusOr ParsePermissionToJson( "metadata", ParseMetadataMatcherToJson( envoy_config_rbac_v3_Permission_metadata(permission))); } else if (envoy_config_rbac_v3_Permission_has_not_rule(permission)) { - auto not_rule_json = ParsePermissionToJson( - envoy_config_rbac_v3_Permission_not_rule(permission)); - if (!not_rule_json.ok()) { - return not_rule_json; - } - permission_json.emplace("notRule", std::move(*not_rule_json)); + ValidationErrors::ScopedField field(errors, ".not_rule"); + Json not_rule_json = ParsePermissionToJson( + envoy_config_rbac_v3_Permission_not_rule(permission), errors); + permission_json.emplace("notRule", std::move(not_rule_json)); } else if (envoy_config_rbac_v3_Permission_has_requested_server_name( permission)) { - auto requested_server_name_json = ParseStringMatcherToJson( - envoy_config_rbac_v3_Permission_requested_server_name(permission)); - if (!requested_server_name_json.ok()) { - return requested_server_name_json; - } + ValidationErrors::ScopedField field(errors, ".requested_server_name"); + Json requested_server_name_json = ParseStringMatcherToJson( + envoy_config_rbac_v3_Permission_requested_server_name(permission), + errors); permission_json.emplace("requestedServerName", - std::move(*requested_server_name_json)); + std::move(requested_server_name_json)); } else { - return absl::InvalidArgumentError("Permission: Invalid rule"); + errors->AddError("invalid rule"); } return permission_json; } -absl::StatusOr ParsePrincipalToJson( - const envoy_config_rbac_v3_Principal* principal) { +Json ParsePrincipalToJson(const envoy_config_rbac_v3_Principal* principal, + ValidationErrors* errors) { Json::Object principal_json; // Helper function to parse Principal::Set to JSON. Used by `and_ids` and // `or_ids`. auto parse_principal_set_to_json = - [](const envoy_config_rbac_v3_Principal_Set* set) - -> absl::StatusOr { - Json::Object json; - std::vector errors; + [errors](const envoy_config_rbac_v3_Principal_Set* set) -> Json { Json::Array ids_json; size_t size; const envoy_config_rbac_v3_Principal* const* ids = envoy_config_rbac_v3_Principal_Set_ids(set, &size); for (size_t i = 0; i < size; ++i) { - auto principal_json = ParsePrincipalToJson(ids[i]); - if (!principal_json.ok()) { - errors.emplace_back(principal_json.status().message()); - } else { - ids_json.emplace_back(std::move(*principal_json)); - } - } - if (!errors.empty()) { - return absl::InvalidArgumentError(absl::StrCat( - "errors parsing Set: [", absl::StrJoin(errors, "; "), "]")); + ValidationErrors::ScopedField field(errors, + absl::StrCat(".ids[", i, "]")); + Json principal_json = ParsePrincipalToJson(ids[i], errors); + ids_json.emplace_back(std::move(principal_json)); } return Json::Object({{"ids", std::move(ids_json)}}); }; if (envoy_config_rbac_v3_Principal_has_and_ids(principal)) { + ValidationErrors::ScopedField field(errors, ".and_ids"); const auto* and_rules = envoy_config_rbac_v3_Principal_and_ids(principal); - auto principal_set_json = parse_principal_set_to_json(and_rules); - if (!principal_set_json.ok()) { - return principal_set_json; - } - principal_json.emplace("andIds", std::move(*principal_set_json)); + Json principal_set_json = parse_principal_set_to_json(and_rules); + principal_json.emplace("andIds", std::move(principal_set_json)); } else if (envoy_config_rbac_v3_Principal_has_or_ids(principal)) { + ValidationErrors::ScopedField field(errors, ".or_ids"); const auto* or_rules = envoy_config_rbac_v3_Principal_or_ids(principal); - auto principal_set_json = parse_principal_set_to_json(or_rules); - if (!principal_set_json.ok()) { - return principal_set_json; - } - principal_json.emplace("orIds", std::move(*principal_set_json)); + Json principal_set_json = parse_principal_set_to_json(or_rules); + principal_json.emplace("orIds", std::move(principal_set_json)); } else if (envoy_config_rbac_v3_Principal_has_any(principal)) { principal_json.emplace("any", envoy_config_rbac_v3_Principal_any(principal)); @@ -352,12 +307,12 @@ absl::StatusOr ParsePrincipalToJson( envoy_config_rbac_v3_Principal_Authenticated_principal_name( envoy_config_rbac_v3_Principal_authenticated(principal)); if (principal_name != nullptr) { - auto principal_name_json = ParseStringMatcherToJson(principal_name); - if (!principal_name_json.ok()) { - return principal_name_json; - } + ValidationErrors::ScopedField field(errors, + ".authenticated.principal_name"); + Json principal_name_json = + ParseStringMatcherToJson(principal_name, errors); authenticated_json->emplace("principalName", - std::move(*principal_name_json)); + std::move(principal_name_json)); } } else if (envoy_config_rbac_v3_Principal_has_source_ip(principal)) { principal_json.emplace( @@ -373,84 +328,71 @@ absl::StatusOr ParsePrincipalToJson( "remoteIp", ParseCidrRangeToJson( envoy_config_rbac_v3_Principal_remote_ip(principal))); } else if (envoy_config_rbac_v3_Principal_has_header(principal)) { - auto header_json = ParseHeaderMatcherToJson( - envoy_config_rbac_v3_Principal_header(principal)); - if (!header_json.ok()) { - return header_json; - } - principal_json.emplace("header", std::move(*header_json)); + ValidationErrors::ScopedField field(errors, ".header"); + Json header_json = ParseHeaderMatcherToJson( + envoy_config_rbac_v3_Principal_header(principal), errors); + principal_json.emplace("header", std::move(header_json)); } else if (envoy_config_rbac_v3_Principal_has_url_path(principal)) { - auto url_path_json = ParsePathMatcherToJson( - envoy_config_rbac_v3_Principal_url_path(principal)); - if (!url_path_json.ok()) { - return url_path_json; - } - principal_json.emplace("urlPath", std::move(*url_path_json)); + ValidationErrors::ScopedField field(errors, ".url_path"); + Json url_path_json = ParsePathMatcherToJson( + envoy_config_rbac_v3_Principal_url_path(principal), errors); + principal_json.emplace("urlPath", std::move(url_path_json)); } else if (envoy_config_rbac_v3_Principal_has_metadata(principal)) { principal_json.emplace( "metadata", ParseMetadataMatcherToJson( envoy_config_rbac_v3_Principal_metadata(principal))); } else if (envoy_config_rbac_v3_Principal_has_not_id(principal)) { - auto not_id_json = - ParsePrincipalToJson(envoy_config_rbac_v3_Principal_not_id(principal)); - if (!not_id_json.ok()) { - return not_id_json; - } - principal_json.emplace("notId", std::move(*not_id_json)); + ValidationErrors::ScopedField field(errors, ".not_id"); + Json not_id_json = ParsePrincipalToJson( + envoy_config_rbac_v3_Principal_not_id(principal), errors); + principal_json.emplace("notId", std::move(not_id_json)); } else { - return absl::InvalidArgumentError("Principal: Invalid rule"); + errors->AddError("invalid rule"); } return principal_json; } -absl::StatusOr ParsePolicyToJson( - const envoy_config_rbac_v3_Policy* policy) { +Json ParsePolicyToJson(const envoy_config_rbac_v3_Policy* policy, + ValidationErrors* errors) { Json::Object policy_json; - std::vector errors; size_t size; Json::Array permissions_json; const envoy_config_rbac_v3_Permission* const* permissions = envoy_config_rbac_v3_Policy_permissions(policy, &size); for (size_t i = 0; i < size; ++i) { - auto permission_json = ParsePermissionToJson(permissions[i]); - if (!permission_json.ok()) { - errors.emplace_back(permission_json.status().message()); - } else { - permissions_json.emplace_back(std::move(*permission_json)); - } + ValidationErrors::ScopedField field(errors, + absl::StrCat(".permissions[", i, "]")); + Json permission_json = ParsePermissionToJson(permissions[i], errors); + permissions_json.emplace_back(std::move(permission_json)); } policy_json.emplace("permissions", std::move(permissions_json)); Json::Array principals_json; const envoy_config_rbac_v3_Principal* const* principals = envoy_config_rbac_v3_Policy_principals(policy, &size); for (size_t i = 0; i < size; ++i) { - auto principal_json = ParsePrincipalToJson(principals[i]); - if (!principal_json.ok()) { - errors.emplace_back(principal_json.status().message()); - } else { - principals_json.emplace_back(std::move(*principal_json)); - } + ValidationErrors::ScopedField field(errors, + absl::StrCat(".principals[", i, "]")); + Json principal_json = ParsePrincipalToJson(principals[i], errors); + principals_json.emplace_back(std::move(principal_json)); } policy_json.emplace("principals", std::move(principals_json)); if (envoy_config_rbac_v3_Policy_has_condition(policy)) { - errors.emplace_back("Policy: condition not supported"); + ValidationErrors::ScopedField field(errors, ".condition"); + errors->AddError("condition not supported"); } if (envoy_config_rbac_v3_Policy_has_checked_condition(policy)) { - errors.emplace_back("Policy: checked condition not supported"); - } - if (!errors.empty()) { - return absl::InvalidArgumentError(absl::StrCat( - "errors parsing Policy: [", absl::StrJoin(errors, "; "), "]")); + ValidationErrors::ScopedField field(errors, ".checked_condition"); + errors->AddError("checked condition not supported"); } return policy_json; } -absl::StatusOr ParseHttpRbacToJson( - const envoy_extensions_filters_http_rbac_v3_RBAC* rbac) { +Json ParseHttpRbacToJson(const envoy_extensions_filters_http_rbac_v3_RBAC* rbac, + ValidationErrors* errors) { Json::Object rbac_json; - std::vector errors; const auto* rules = envoy_extensions_filters_http_rbac_v3_RBAC_rules(rbac); if (rules != nullptr) { + ValidationErrors::ScopedField field(errors, ".rules"); int action = envoy_config_rbac_v3_RBAC_action(rules); // Treat Log action as RBAC being absent if (action == envoy_config_rbac_v3_RBAC_LOG) { @@ -466,89 +408,82 @@ absl::StatusOr ParseHttpRbacToJson( if (entry == nullptr) { break; } - auto policy = ParsePolicyToJson( - envoy_config_rbac_v3_RBAC_PoliciesEntry_value(entry)); - if (!policy.ok()) { - errors.emplace_back(absl::StrCat( - "RBAC PoliciesEntry key:", - UpbStringToStdString( - envoy_config_rbac_v3_RBAC_PoliciesEntry_key(entry)), - " error:", policy.status().message())); - } else { - policies_object.emplace( - UpbStringToStdString( - envoy_config_rbac_v3_RBAC_PoliciesEntry_key(entry)), - std::move(*policy)); - } + absl::string_view key = + UpbStringToAbsl(envoy_config_rbac_v3_RBAC_PoliciesEntry_key(entry)); + ValidationErrors::ScopedField field( + errors, absl::StrCat(".policies[", key, "]")); + Json policy = ParsePolicyToJson( + envoy_config_rbac_v3_RBAC_PoliciesEntry_value(entry), errors); + policies_object.emplace(std::string(key), std::move(policy)); } inner_rbac_json.emplace("policies", std::move(policies_object)); } rbac_json.emplace("rules", std::move(inner_rbac_json)); } - if (!errors.empty()) { - return absl::InvalidArgumentError(absl::StrCat( - "errors parsing RBAC: [", absl::StrJoin(errors, "; "), "]")); - } return rbac_json; } } // namespace +absl::string_view XdsHttpRbacFilter::ConfigProtoName() const { + return "envoy.extensions.filters.http.rbac.v3.RBAC"; +} + +absl::string_view XdsHttpRbacFilter::OverrideConfigProtoName() const { + return "envoy.extensions.filters.http.rbac.v3.RBACPerRoute"; +} + void XdsHttpRbacFilter::PopulateSymtab(upb_DefPool* symtab) const { envoy_extensions_filters_http_rbac_v3_RBAC_getmsgdef(symtab); } -absl::StatusOr +absl::optional XdsHttpRbacFilter::GenerateFilterConfig(XdsExtension extension, - upb_Arena* arena) const { + upb_Arena* arena, + ValidationErrors* errors) const { absl::string_view* serialized_filter_config = absl::get_if(&extension.value); if (serialized_filter_config == nullptr) { - return absl::InvalidArgumentError( - "could not parse HTTP RBAC filter config"); + errors->AddError("could not parse HTTP RBAC filter config"); + return absl::nullopt; } auto* rbac = envoy_extensions_filters_http_rbac_v3_RBAC_parse( serialized_filter_config->data(), serialized_filter_config->size(), arena); if (rbac == nullptr) { - return absl::InvalidArgumentError( - "could not parse HTTP RBAC filter config"); + errors->AddError("could not parse HTTP RBAC filter config"); + return absl::nullopt; } - absl::StatusOr rbac_json = ParseHttpRbacToJson(rbac); - if (!rbac_json.ok()) { - return rbac_json.status(); - } - return FilterConfig{kXdsHttpRbacFilterConfigName, std::move(*rbac_json)}; + return FilterConfig{ConfigProtoName(), ParseHttpRbacToJson(rbac, errors)}; } -absl::StatusOr -XdsHttpRbacFilter::GenerateFilterConfigOverride(XdsExtension extension, - upb_Arena* arena) const { +absl::optional +XdsHttpRbacFilter::GenerateFilterConfigOverride( + XdsExtension extension, upb_Arena* arena, ValidationErrors* errors) const { absl::string_view* serialized_filter_config = absl::get_if(&extension.value); if (serialized_filter_config == nullptr) { - return absl::InvalidArgumentError("could not parse RBACPerRoute"); + errors->AddError("could not parse RBACPerRoute"); + return absl::nullopt; } auto* rbac_per_route = envoy_extensions_filters_http_rbac_v3_RBACPerRoute_parse( serialized_filter_config->data(), serialized_filter_config->size(), arena); if (rbac_per_route == nullptr) { - return absl::InvalidArgumentError("could not parse RBACPerRoute"); + errors->AddError("could not parse RBACPerRoute"); + return absl::nullopt; } - absl::StatusOr rbac_json; + Json rbac_json; const auto* rbac = envoy_extensions_filters_http_rbac_v3_RBACPerRoute_rbac(rbac_per_route); if (rbac == nullptr) { rbac_json = Json::Object(); } else { - rbac_json = ParseHttpRbacToJson(rbac); - if (!rbac_json.ok()) { - return rbac_json.status(); - } + ValidationErrors::ScopedField field(errors, ".rbac"); + rbac_json = ParseHttpRbacToJson(rbac, errors); } - return FilterConfig{kXdsHttpRbacFilterConfigOverrideName, - std::move(*rbac_json)}; + return FilterConfig{OverrideConfigProtoName(), std::move(rbac_json)}; } const grpc_channel_filter* XdsHttpRbacFilter::channel_filter() const { diff --git a/src/core/ext/xds/xds_http_rbac_filter.h b/src/core/ext/xds/xds_http_rbac_filter.h index 89602a3c61b..a582f06f625 100644 --- a/src/core/ext/xds/xds_http_rbac_filter.h +++ b/src/core/ext/xds/xds_http_rbac_filter.h @@ -20,6 +20,8 @@ #include #include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" #include "upb/arena.h" #include "upb/def.h" @@ -27,32 +29,27 @@ #include "src/core/ext/xds/xds_http_filters.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" namespace grpc_core { -extern const char* kXdsHttpRbacFilterConfigName; -extern const char* kXdsHttpRbacFilterConfigOverrideName; - class XdsHttpRbacFilter : public XdsHttpFilterImpl { public: + absl::string_view ConfigProtoName() const override; + absl::string_view OverrideConfigProtoName() const override; void PopulateSymtab(upb_DefPool* symtab) const override; - - absl::StatusOr GenerateFilterConfig( - XdsExtension extension, upb_Arena* arena) const override; - - absl::StatusOr GenerateFilterConfigOverride( - XdsExtension extension, upb_Arena* arena) const override; - + absl::optional GenerateFilterConfig( + XdsExtension extension, upb_Arena* arena, + ValidationErrors* errors) const override; + absl::optional GenerateFilterConfigOverride( + XdsExtension extension, upb_Arena* arena, + ValidationErrors* errors) const override; const grpc_channel_filter* channel_filter() const override; - ChannelArgs ModifyChannelArgs(const ChannelArgs& args) const override; - absl::StatusOr GenerateServiceConfig( const FilterConfig& hcm_filter_config, const FilterConfig* filter_config_override) const override; - bool IsSupportedOnClients() const override { return false; } - bool IsSupportedOnServers() const override { return true; } }; diff --git a/src/core/ext/xds/xds_listener.cc b/src/core/ext/xds/xds_listener.cc index 1270d80f455..6fbe1eebf28 100644 --- a/src/core/ext/xds/xds_listener.cc +++ b/src/core/ext/xds/xds_listener.cc @@ -28,7 +28,6 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" -#include "absl/strings/strip.h" #include "envoy/config/core/v3/address.upb.h" #include "envoy/config/core/v3/base.upb.h" #include "envoy/config/core/v3/config_source.upb.h" @@ -56,42 +55,29 @@ #include "src/core/lib/address_utils/sockaddr_utils.h" #include "src/core/lib/debug/trace.h" #include "src/core/lib/gprpp/host_port.h" -#include "src/core/lib/gprpp/status_helper.h" +#include "src/core/lib/gprpp/match.h" #include "src/core/lib/gprpp/validation_errors.h" -#include "src/core/lib/iomgr/error.h" #include "src/core/lib/iomgr/sockaddr.h" +#include "src/core/lib/matchers/matchers.h" namespace grpc_core { -// -// XdsListenerResource::DownstreamTlsContext -// - -std::string XdsListenerResource::DownstreamTlsContext::ToString() const { - return absl::StrFormat("common_tls_context=%s, require_client_certificate=%s", - common_tls_context.ToString(), - require_client_certificate ? "true" : "false"); -} - -bool XdsListenerResource::DownstreamTlsContext::Empty() const { - return common_tls_context.Empty(); -} - // // XdsListenerResource::HttpConnectionManager // std::string XdsListenerResource::HttpConnectionManager::ToString() const { std::vector contents; - contents.push_back(absl::StrFormat( - "route_config_name=%s", - !route_config_name.empty() ? route_config_name.c_str() : "")); - contents.push_back(absl::StrFormat("http_max_stream_duration=%s", - http_max_stream_duration.ToString())); - if (rds_update.has_value()) { - contents.push_back( - absl::StrFormat("rds_update=%s", rds_update->ToString())); - } + contents.push_back(Match( + route_config, + [](const std::string& rds_name) { + return absl::StrCat("rds_name=", rds_name); + }, + [](const XdsRouteConfigResource& route_config) { + return absl::StrCat("route_config=", route_config.ToString()); + })); + contents.push_back(absl::StrCat("http_max_stream_duration=", + http_max_stream_duration.ToString())); if (!http_filters.empty()) { std::vector filter_strings; for (const auto& http_filter : http_filters) { @@ -104,7 +90,7 @@ std::string XdsListenerResource::HttpConnectionManager::ToString() const { } // -// XdsListenerResource::HttpFilter +// XdsListenerResource::HttpConnectionManager::HttpFilter // std::string XdsListenerResource::HttpConnectionManager::HttpFilter::ToString() @@ -112,6 +98,20 @@ std::string XdsListenerResource::HttpConnectionManager::HttpFilter::ToString() return absl::StrCat("{name=", name, ", config=", config.ToString(), "}"); } +// +// XdsListenerResource::DownstreamTlsContext +// + +std::string XdsListenerResource::DownstreamTlsContext::ToString() const { + return absl::StrFormat("common_tls_context=%s, require_client_certificate=%s", + common_tls_context.ToString(), + require_client_certificate ? "true" : "false"); +} + +bool XdsListenerResource::DownstreamTlsContext::Empty() const { + return common_tls_context.Empty(); +} + // // XdsListenerResource::FilterChainData // @@ -243,26 +243,36 @@ std::string XdsListenerResource::FilterChainMap::ToString() const { } // -// XdsListenerResource +// XdsListenerResource::TcpListener // -std::string XdsListenerResource::ToString() const { +std::string XdsListenerResource::TcpListener::ToString() const { std::vector contents; - if (type == ListenerType::kTcpListener) { - contents.push_back(absl::StrCat("address=", address)); - contents.push_back( - absl::StrCat("filter_chain_map=", filter_chain_map.ToString())); - if (default_filter_chain.has_value()) { - contents.push_back(absl::StrCat("default_filter_chain=", - default_filter_chain->ToString())); - } - } else if (type == ListenerType::kHttpApiListener) { - contents.push_back(absl::StrFormat("http_connection_manager=%s", - http_connection_manager.ToString())); + contents.push_back(absl::StrCat("address=", address)); + contents.push_back( + absl::StrCat("filter_chain_map=", filter_chain_map.ToString())); + if (default_filter_chain.has_value()) { + contents.push_back(absl::StrCat("default_filter_chain=", + default_filter_chain->ToString())); } return absl::StrCat("{", absl::StrJoin(contents, ", "), "}"); } +// +// XdsListenerResource +// + +std::string XdsListenerResource::ToString() const { + return Match( + listener, + [](const HttpConnectionManager& hcm) { + return absl::StrCat("{http_connection_manager=", hcm.ToString(), "}"); + }, + [](const TcpListener& tcp) { + return absl::StrCat("{tcp_listener=", tcp.ToString(), "}"); + }); +} + // // XdsListenerResourceType // @@ -286,143 +296,159 @@ void MaybeLogHttpConnectionManager( } } -absl::StatusOr -HttpConnectionManagerParse( +XdsListenerResource::HttpConnectionManager HttpConnectionManagerParse( bool is_client, const XdsResourceType::DecodeContext& context, - const envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager* - http_connection_manager_proto) { + XdsExtension extension, ValidationErrors* errors) { + if (extension.type != + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager") { + errors->AddError("unsupported filter type"); + return {}; + } + auto* serialized_hcm_config = + absl::get_if(&extension.value); + if (serialized_hcm_config == nullptr) { + errors->AddError("could not parse HttpConnectionManager config"); + return {}; + } + const auto* http_connection_manager_proto = + envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_parse( + serialized_hcm_config->data(), serialized_hcm_config->size(), + context.arena); + if (http_connection_manager_proto == nullptr) { + errors->AddError("could not parse HttpConnectionManager config"); + return {}; + } MaybeLogHttpConnectionManager(context, http_connection_manager_proto); - std::vector errors; XdsListenerResource::HttpConnectionManager http_connection_manager; - // NACK a non-zero `xff_num_trusted_hops` and a `non-empty - // original_ip_detection_extensions` as mentioned in + // xff_num_trusted_hops -- must be zero as per // https://github.com/grpc/proposal/blob/master/A41-xds-rbac.md if (envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_xff_num_trusted_hops( http_connection_manager_proto) != 0) { - errors.emplace_back("'xff_num_trusted_hops' must be zero"); + ValidationErrors::ScopedField field(errors, ".xff_num_trusted_hops"); + errors->AddError("must be zero"); } + // original_ip_detection_extensions -- must be empty as per + // https://github.com/grpc/proposal/blob/master/A41-xds-rbac.md if (envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_has_original_ip_detection_extensions( http_connection_manager_proto)) { - errors.emplace_back("'original_ip_detection_extensions' must be empty"); + ValidationErrors::ScopedField field(errors, + ".original_ip_detection_extensions"); + errors->AddError("must be empty"); } - // Obtain max_stream_duration from Http Protocol Options. + // common_http_protocol_options const envoy_config_core_v3_HttpProtocolOptions* options = envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_common_http_protocol_options( http_connection_manager_proto); if (options != nullptr) { + // max_stream_duration const google_protobuf_Duration* duration = envoy_config_core_v3_HttpProtocolOptions_max_stream_duration(options); if (duration != nullptr) { - ValidationErrors validation_errors; + ValidationErrors::ScopedField field( + errors, ".common_http_protocol_options.max_stream_duration"); http_connection_manager.http_max_stream_duration = - ParseDuration(duration, &validation_errors); - if (!validation_errors.ok()) { - errors.emplace_back( - validation_errors.status("max_stream_duration").message()); - } + ParseDuration(duration, errors); } } - // Parse filters. - size_t num_filters = 0; - const auto* http_filters = - envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_http_filters( - http_connection_manager_proto, &num_filters); - std::set names_seen; - for (size_t i = 0; i < num_filters; ++i) { - const auto* http_filter = http_filters[i]; - absl::string_view name = UpbStringToAbsl( - envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_name( - http_filter)); - if (name.empty()) { - errors.emplace_back(absl::StrCat("empty filter name at index ", i)); - continue; - } - if (names_seen.find(name) != names_seen.end()) { - errors.emplace_back(absl::StrCat("duplicate HTTP filter name: ", name)); - continue; - } - names_seen.insert(name); - const bool is_optional = - envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_is_optional( - http_filter); - const google_protobuf_Any* any = - envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_typed_config( - http_filter); - if (any == nullptr) { - if (!is_optional) { - errors.emplace_back( - absl::StrCat("no filter config specified for filter name ", name)); - } - continue; - } - ValidationErrors validation_errors; - ValidationErrors::ScopedField field( - &validation_errors, - absl::StrCat(".http_filters[", i, "].typed_config")); - auto extension = ExtractXdsExtension(context, any, &validation_errors); - if (!validation_errors.ok()) { - errors.emplace_back( - validation_errors.status(absl::StrCat("filter name ", name)) - .message()); - continue; - } - GPR_ASSERT(extension.has_value()); - const XdsHttpFilterImpl* filter_impl = - XdsHttpFilterRegistry::GetFilterForType(extension->type); - if (filter_impl == nullptr) { - if (!is_optional) { - errors.emplace_back(absl::StrCat( - "no filter registered for config type ", extension->type)); + // http_filters + { + ValidationErrors::ScopedField field(errors, ".http_filters"); + size_t num_filters = 0; + const auto* http_filters = + envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_http_filters( + http_connection_manager_proto, &num_filters); + std::set names_seen; + const size_t original_error_size = errors->size(); + for (size_t i = 0; i < num_filters; ++i) { + ValidationErrors::ScopedField field(errors, absl::StrCat("[", i, "]")); + const auto* http_filter = http_filters[i]; + // name + absl::string_view name = UpbStringToAbsl( + envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_name( + http_filter)); + { + ValidationErrors::ScopedField field(errors, ".name"); + if (name.empty()) { + errors->AddError("empty filter name"); + continue; + } + if (names_seen.find(name) != names_seen.end()) { + errors->AddError(absl::StrCat("duplicate HTTP filter name: ", name)); + continue; + } } - continue; - } - if ((is_client && !filter_impl->IsSupportedOnClients()) || - (!is_client && !filter_impl->IsSupportedOnServers())) { - if (!is_optional) { - errors.emplace_back(absl::StrFormat("Filter %s is not supported on %s", - extension->type, - is_client ? "clients" : "servers")); + names_seen.insert(name); + // is_optional + const bool is_optional = + envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_is_optional( + http_filter); + // typed_config + { + ValidationErrors::ScopedField field(errors, ".typed_config"); + const google_protobuf_Any* typed_config = + envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_typed_config( + http_filter); + if (typed_config == nullptr) { + if (!is_optional) errors->AddError("field not present"); + continue; + } + auto extension = ExtractXdsExtension(context, typed_config, errors); + const XdsHttpFilterImpl* filter_impl = nullptr; + if (extension.has_value()) { + filter_impl = + XdsHttpFilterRegistry::GetFilterForType(extension->type); + } + if (filter_impl == nullptr) { + if (!is_optional) errors->AddError("unsupported filter type"); + continue; + } + if ((is_client && !filter_impl->IsSupportedOnClients()) || + (!is_client && !filter_impl->IsSupportedOnServers())) { + if (!is_optional) { + errors->AddError(absl::StrCat("filter is not supported on ", + is_client ? "clients" : "servers")); + } + continue; + } + absl::optional filter_config = + filter_impl->GenerateFilterConfig(std::move(*extension), + context.arena, errors); + if (filter_config.has_value()) { + http_connection_manager.http_filters.emplace_back( + XdsListenerResource::HttpConnectionManager::HttpFilter{ + std::string(name), std::move(*filter_config)}); + } } - continue; } - absl::StatusOr filter_config = - filter_impl->GenerateFilterConfig(std::move(*extension), context.arena); - if (!filter_config.ok()) { - errors.emplace_back(absl::StrCat( - "filter config for type ", extension->type, - " failed to parse: ", StatusToString(filter_config.status()))); - continue; + if (errors->size() == original_error_size && + http_connection_manager.http_filters.empty()) { + errors->AddError("expected at least one HTTP filter"); } - http_connection_manager.http_filters.emplace_back( - XdsListenerResource::HttpConnectionManager::HttpFilter{ - std::string(name), std::move(*filter_config)}); - } - if (http_connection_manager.http_filters.empty()) { - errors.emplace_back("Expected at least one HTTP filter"); - } - // Make sure that the last filter is terminal and non-last filters are - // non-terminal. Note that this check is being performed in a separate loop - // to take care of the case where there are two terminal filters in the list - // out of which only one gets added in the final list. - for (const auto& http_filter : http_connection_manager.http_filters) { - const XdsHttpFilterImpl* filter_impl = - XdsHttpFilterRegistry::GetFilterForType( - http_filter.config.config_proto_type_name); - if (&http_filter != &http_connection_manager.http_filters.back()) { - // Filters before the last filter must not be terminal. - if (filter_impl->IsTerminalFilter()) { - errors.emplace_back( - absl::StrCat("terminal filter for config type ", - http_filter.config.config_proto_type_name, - " must be the last filter in the chain")); - } - } else { - // The last filter must be terminal. - if (!filter_impl->IsTerminalFilter()) { - errors.emplace_back( - absl::StrCat("non-terminal filter for config type ", - http_filter.config.config_proto_type_name, - " is the last filter in the chain")); + // Make sure that the last filter is terminal and non-last filters are + // non-terminal. Note that this check is being performed in a separate loop + // to take care of the case where there are two terminal filters in the list + // out of which only one gets added in the final list. + for (const auto& http_filter : http_connection_manager.http_filters) { + const XdsHttpFilterImpl* filter_impl = + XdsHttpFilterRegistry::GetFilterForType( + http_filter.config.config_proto_type_name); + if (&http_filter != &http_connection_manager.http_filters.back()) { + // Filters before the last filter must not be terminal. + if (filter_impl->IsTerminalFilter()) { + errors->AddError( + absl::StrCat("terminal filter for config type ", + http_filter.config.config_proto_type_name, + " must be the last filter in the chain")); + } + } else { + // The last filter must be terminal. + if (!filter_impl->IsTerminalFilter()) { + errors->AddError( + absl::StrCat("non-terminal filter for config type ", + http_filter.config.config_proto_type_name, + " is the last filter in the chain")); + } } } } @@ -434,9 +460,10 @@ HttpConnectionManagerParse( http_connection_manager_proto); auto rds_update = XdsRouteConfigResource::Parse(context, route_config); if (!rds_update.ok()) { - errors.emplace_back(rds_update.status().message()); + ValidationErrors::ScopedField field(errors, ".route_config"); + errors->AddError(rds_update.status().message()); } else { - http_connection_manager.rds_update = std::move(*rds_update); + http_connection_manager.route_config = std::move(*rds_update); } } else { // Validate that RDS must be used to get the route_config dynamically. @@ -444,149 +471,149 @@ HttpConnectionManagerParse( envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_rds( http_connection_manager_proto); if (rds == nullptr) { - return GRPC_ERROR_CREATE( - "HttpConnectionManager neither has inlined route_config nor RDS."); - } - // Check that the ConfigSource specifies ADS. - const envoy_config_core_v3_ConfigSource* config_source = - envoy_extensions_filters_network_http_connection_manager_v3_Rds_config_source( - rds); - if (config_source == nullptr) { - errors.emplace_back( - "HttpConnectionManager missing config_source for RDS."); - } else if (!envoy_config_core_v3_ConfigSource_has_ads(config_source) && - !envoy_config_core_v3_ConfigSource_has_self(config_source)) { - errors.emplace_back( - "HttpConnectionManager ConfigSource for RDS does not specify ADS " - "or SELF."); + errors->AddError("neither route_config nor rds fields are present"); } else { // Get the route_config_name. - http_connection_manager.route_config_name = UpbStringToStdString( + http_connection_manager.route_config = UpbStringToStdString( envoy_extensions_filters_network_http_connection_manager_v3_Rds_route_config_name( rds)); + // Check that the ConfigSource specifies ADS. + const envoy_config_core_v3_ConfigSource* config_source = + envoy_extensions_filters_network_http_connection_manager_v3_Rds_config_source( + rds); + ValidationErrors::ScopedField field(errors, ".rds.config_source"); + if (config_source == nullptr) { + errors->AddError("field not present"); + } else if (!envoy_config_core_v3_ConfigSource_has_ads(config_source) && + !envoy_config_core_v3_ConfigSource_has_self(config_source)) { + errors->AddError("ConfigSource does not specify ADS or SELF"); + } } } - // Return result. - if (!errors.empty()) { - return absl::InvalidArgumentError( - absl::StrCat("Errors parsing HttpConnectionManager config: [", - absl::StrJoin(errors, "; "), "]")); - } return http_connection_manager; } absl::StatusOr LdsResourceParseClient( const XdsResourceType::DecodeContext& context, const envoy_config_listener_v3_ApiListener* api_listener) { - const upb_StringView encoded_api_listener = google_protobuf_Any_value( - envoy_config_listener_v3_ApiListener_api_listener(api_listener)); - const auto* http_connection_manager = - envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_parse( - encoded_api_listener.data, encoded_api_listener.size, context.arena); - if (http_connection_manager == nullptr) { - return absl::InvalidArgumentError( - "Could not parse HttpConnectionManager config from ApiListener"); - } - auto hcm = HttpConnectionManagerParse(true /* is_client */, context, - http_connection_manager); - if (!hcm.ok()) return hcm.status(); XdsListenerResource lds_update; - lds_update.type = XdsListenerResource::ListenerType::kHttpApiListener; - lds_update.http_connection_manager = std::move(*hcm); - return lds_update; + ValidationErrors errors; + ValidationErrors::ScopedField field(&errors, "api_listener.api_listener"); + auto* api_listener_field = + envoy_config_listener_v3_ApiListener_api_listener(api_listener); + if (api_listener_field == nullptr) { + errors.AddError("field not present"); + } else { + auto extension = ExtractXdsExtension(context, api_listener_field, &errors); + if (extension.has_value()) { + lds_update.listener = HttpConnectionManagerParse( + /*is_client=*/true, context, std::move(*extension), &errors); + } + } + if (!errors.ok()) return errors.status("errors validating ApiListener"); + return std::move(lds_update); } -absl::StatusOr -DownstreamTlsContextParse( +XdsListenerResource::DownstreamTlsContext DownstreamTlsContextParse( const XdsResourceType::DecodeContext& context, - const envoy_config_core_v3_TransportSocket* transport_socket) { + const envoy_config_core_v3_TransportSocket* transport_socket, + ValidationErrors* errors) { + ValidationErrors::ScopedField field(errors, ".typed_config"); const auto* typed_config = envoy_config_core_v3_TransportSocket_typed_config(transport_socket); - if (typed_config == nullptr) { - return absl::InvalidArgumentError("transport socket typed config unset"); - } - absl::string_view type_url = absl::StripPrefix( - UpbStringToAbsl(google_protobuf_Any_type_url(typed_config)), - "type.googleapis.com/"); - if (type_url != + auto extension = ExtractXdsExtension(context, typed_config, errors); + if (!extension.has_value()) return {}; + if (extension->type != "envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext") { - return absl::InvalidArgumentError( - absl::StrCat("Unrecognized transport socket type: ", type_url)); + ValidationErrors::ScopedField field(errors, ".type_url"); + errors->AddError("unsupported transport socket type"); + return {}; + } + absl::string_view* serialized_downstream_tls_context = + absl::get_if(&extension->value); + if (serialized_downstream_tls_context == nullptr) { + errors->AddError("can't decode DownstreamTlsContext"); + return {}; } - const upb_StringView encoded_downstream_tls_context = - google_protobuf_Any_value(typed_config); const auto* downstream_tls_context_proto = envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_parse( - encoded_downstream_tls_context.data, - encoded_downstream_tls_context.size, context.arena); + serialized_downstream_tls_context->data(), + serialized_downstream_tls_context->size(), context.arena); if (downstream_tls_context_proto == nullptr) { - return absl::InvalidArgumentError("Can't decode downstream tls context."); + errors->AddError("can't decode DownstreamTlsContext"); + return {}; } - std::vector errors; XdsListenerResource::DownstreamTlsContext downstream_tls_context; auto* common_tls_context = envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_common_tls_context( downstream_tls_context_proto); if (common_tls_context != nullptr) { - ValidationErrors validation_errors; - downstream_tls_context.common_tls_context = CommonTlsContext::Parse( - context, common_tls_context, &validation_errors); - if (!validation_errors.ok()) { - errors.emplace_back( - validation_errors.status("errors in common_tls_context").message()); + ValidationErrors::ScopedField field(errors, ".common_tls_context"); + downstream_tls_context.common_tls_context = + CommonTlsContext::Parse(context, common_tls_context, errors); + // Note: We can't be more specific about the field name for this + // error, because we don't know which fields they were found in + // inside of CommonTlsContext, so we make the error message a bit + // more verbose to compensate. + if (!downstream_tls_context.common_tls_context + .certificate_validation_context.match_subject_alt_names.empty()) { + errors->AddError("match_subject_alt_names not supported on servers"); } } + // Note: We can't be more specific about the field name for this + // error, because we don't know which fields they were found in + // inside of CommonTlsContext, so we make the error message a bit + // more verbose to compensate. + if (downstream_tls_context.common_tls_context + .tls_certificate_provider_instance.instance_name.empty()) { + errors->AddError( + "TLS configuration provided but no " + "tls_certificate_provider_instance found"); + } auto* require_client_certificate = envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_require_client_certificate( downstream_tls_context_proto); if (require_client_certificate != nullptr) { downstream_tls_context.require_client_certificate = google_protobuf_BoolValue_value(require_client_certificate); + if (downstream_tls_context.require_client_certificate && + downstream_tls_context.common_tls_context.certificate_validation_context + .ca_certificate_provider_instance.instance_name.empty()) { + ValidationErrors::ScopedField field(errors, + ".require_client_certificate"); + errors->AddError( + "client certificate required but no certificate " + "provider instance specified for validation"); + } } auto* require_sni = envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_require_sni( downstream_tls_context_proto); if (require_sni != nullptr && google_protobuf_BoolValue_value(require_sni)) { - errors.emplace_back("require_sni: unsupported"); + ValidationErrors::ScopedField field(errors, ".require_sni"); + errors->AddError("field unsupported"); } if (envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_ocsp_staple_policy( downstream_tls_context_proto) != envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_LENIENT_STAPLING) { - errors.emplace_back("ocsp_staple_policy: Only LENIENT_STAPLING supported"); - } - if (downstream_tls_context.common_tls_context - .tls_certificate_provider_instance.instance_name.empty()) { - errors.emplace_back( - "TLS configuration provided but no " - "tls_certificate_provider_instance found."); - } - if (downstream_tls_context.require_client_certificate && - downstream_tls_context.common_tls_context.certificate_validation_context - .ca_certificate_provider_instance.instance_name.empty()) { - errors.emplace_back( - "TLS configuration requires client certificates but no certificate " - "provider instance specified for validation."); - } - if (!downstream_tls_context.common_tls_context.certificate_validation_context - .match_subject_alt_names.empty()) { - errors.emplace_back("match_subject_alt_names not supported on servers"); - } - // Return result. - if (!errors.empty()) { - return absl::InvalidArgumentError( - absl::StrCat("Errors parsing DownstreamTlsContext: [", - absl::StrJoin(errors, "; "), "]")); + ValidationErrors::ScopedField field(errors, ".ocsp_staple_policy"); + errors->AddError("value must be LENIENT_STAPLING"); } return downstream_tls_context; } -absl::StatusOr CidrRangeParse( - const envoy_config_core_v3_CidrRange* cidr_range_proto) { +absl::optional CidrRangeParse( + const envoy_config_core_v3_CidrRange* cidr_range_proto, + ValidationErrors* errors) { + ValidationErrors::ScopedField field(errors, ".address_prefix"); XdsListenerResource::FilterChainMap::CidrRange cidr_range; std::string address_prefix = UpbStringToStdString( envoy_config_core_v3_CidrRange_address_prefix(cidr_range_proto)); auto address = StringToSockaddr(address_prefix, /*port=*/0); - if (!address.ok()) return address.status(); + if (!address.ok()) { + errors->AddError(address.status().message()); + return absl::nullopt; + } cidr_range.address = *address; cidr_range.prefix_len = 0; auto* prefix_len_proto = @@ -604,10 +631,12 @@ absl::StatusOr CidrRangeParse( return cidr_range; } -absl::StatusOr FilterChainMatchParse( - const envoy_config_listener_v3_FilterChainMatch* filter_chain_match_proto) { - std::vector errors; +absl::optional FilterChainMatchParse( + const envoy_config_listener_v3_FilterChainMatch* filter_chain_match_proto, + ValidationErrors* errors) { FilterChain::FilterChainMatch filter_chain_match; + const size_t original_error_size = errors->size(); + // destination_port auto* destination_port = envoy_config_listener_v3_FilterChainMatch_destination_port( filter_chain_match_proto); @@ -615,51 +644,56 @@ absl::StatusOr FilterChainMatchParse( filter_chain_match.destination_port = google_protobuf_UInt32Value_value(destination_port); } + // prefix_ranges size_t size = 0; auto* prefix_ranges = envoy_config_listener_v3_FilterChainMatch_prefix_ranges( filter_chain_match_proto, &size); filter_chain_match.prefix_ranges.reserve(size); for (size_t i = 0; i < size; i++) { - auto cidr_range = CidrRangeParse(prefix_ranges[i]); - if (!cidr_range.ok()) { - errors.emplace_back(absl::StrCat("prefix range ", i, ": ", - cidr_range.status().message())); - continue; + ValidationErrors::ScopedField field( + errors, absl::StrCat(".prefix_ranges[", i, "]")); + auto cidr_range = CidrRangeParse(prefix_ranges[i], errors); + if (cidr_range.has_value()) { + filter_chain_match.prefix_ranges.push_back(*cidr_range); } - filter_chain_match.prefix_ranges.push_back(*cidr_range); } + // source_type filter_chain_match.source_type = static_cast( envoy_config_listener_v3_FilterChainMatch_source_type( filter_chain_match_proto)); + // source_prefix_ranges auto* source_prefix_ranges = envoy_config_listener_v3_FilterChainMatch_source_prefix_ranges( filter_chain_match_proto, &size); filter_chain_match.source_prefix_ranges.reserve(size); for (size_t i = 0; i < size; i++) { - auto cidr_range = CidrRangeParse(source_prefix_ranges[i]); - if (!cidr_range.ok()) { - errors.emplace_back(absl::StrCat("source prefix range ", i, ": ", - cidr_range.status().message())); - continue; + ValidationErrors::ScopedField field( + errors, absl::StrCat(".source_prefix_ranges[", i, "]")); + auto cidr_range = CidrRangeParse(source_prefix_ranges[i], errors); + if (cidr_range.has_value()) { + filter_chain_match.source_prefix_ranges.push_back(*cidr_range); } - filter_chain_match.source_prefix_ranges.push_back(*cidr_range); } + // source_ports auto* source_ports = envoy_config_listener_v3_FilterChainMatch_source_ports( filter_chain_match_proto, &size); filter_chain_match.source_ports.reserve(size); for (size_t i = 0; i < size; i++) { filter_chain_match.source_ports.push_back(source_ports[i]); } + // server_names auto* server_names = envoy_config_listener_v3_FilterChainMatch_server_names( filter_chain_match_proto, &size); for (size_t i = 0; i < size; i++) { filter_chain_match.server_names.push_back( UpbStringToStdString(server_names[i])); } + // transport_protocol filter_chain_match.transport_protocol = UpbStringToStdString( envoy_config_listener_v3_FilterChainMatch_transport_protocol( filter_chain_match_proto)); + // application_protocols auto* application_protocols = envoy_config_listener_v3_FilterChainMatch_application_protocols( filter_chain_match_proto, &size); @@ -668,111 +702,93 @@ absl::StatusOr FilterChainMatchParse( UpbStringToStdString(application_protocols[i])); } // Return result. - if (!errors.empty()) { - return absl::InvalidArgumentError( - absl::StrCat("errors parsing filter chain match: [", - absl::StrJoin(errors, "; "), "]")); - } + if (errors->size() != original_error_size) return absl::nullopt; return filter_chain_match; } -absl::StatusOr FilterChainParse( +absl::optional FilterChainParse( const XdsResourceType::DecodeContext& context, - const envoy_config_listener_v3_FilterChain* filter_chain_proto) { + const envoy_config_listener_v3_FilterChain* filter_chain_proto, + ValidationErrors* errors) { FilterChain filter_chain; - std::vector errors; + const size_t original_error_size = errors->size(); + // filter_chain_match auto* filter_chain_match = envoy_config_listener_v3_FilterChain_filter_chain_match( filter_chain_proto); if (filter_chain_match != nullptr) { - auto match = FilterChainMatchParse(filter_chain_match); - if (!match.ok()) { - errors.emplace_back(match.status().message()); - } else { + ValidationErrors::ScopedField field(errors, ".filter_chain_match"); + auto match = FilterChainMatchParse(filter_chain_match, errors); + if (match.has_value()) { filter_chain.filter_chain_match = std::move(*match); } } - filter_chain.filter_chain_data = - std::make_shared(); - // Parse the filters list. Currently we only support HttpConnectionManager. - size_t size = 0; - auto* filters = - envoy_config_listener_v3_FilterChain_filters(filter_chain_proto, &size); - if (size != 1) { - errors.push_back( - "FilterChain should have exactly one filter: HttpConnectionManager; no " - "other filter is supported at the moment"); - } else { - auto* typed_config = - envoy_config_listener_v3_Filter_typed_config(filters[0]); - if (typed_config == nullptr) { - errors.emplace_back("No typed_config found in filter."); - } else { - absl::string_view type_url = absl::StripPrefix( - UpbStringToAbsl(google_protobuf_Any_type_url(typed_config)), - "type.googleapis.com/"); - if (type_url != - "envoy.extensions.filters.network.http_connection_manager.v3." - "HttpConnectionManager") { - errors.emplace_back(absl::StrCat("Unsupported filter type ", type_url)); - } else { - const upb_StringView encoded_http_connection_manager = - google_protobuf_Any_value(typed_config); - const auto* http_connection_manager = - envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_parse( - encoded_http_connection_manager.data, - encoded_http_connection_manager.size, context.arena); - if (http_connection_manager == nullptr) { - errors.emplace_back( - "Could not parse HttpConnectionManager config from filter " - "typed_config"); - } else { - auto hcm = HttpConnectionManagerParse( - /*is_client=*/false, context, http_connection_manager); - if (!hcm.ok()) { - errors.emplace_back(hcm.status().message()); - } else { - filter_chain.filter_chain_data->http_connection_manager = - std::move(*hcm); - } - } + // filters + { + ValidationErrors::ScopedField field(errors, ".filters"); + filter_chain.filter_chain_data = + std::make_shared(); + size_t size = 0; + auto* filters = + envoy_config_listener_v3_FilterChain_filters(filter_chain_proto, &size); + if (size != 1) { + errors->AddError( + "must have exactly one filter (HttpConnectionManager -- " + "no other filter is supported at the moment)"); + } + // entries in filters list + for (size_t i = 0; i < size; ++i) { + ValidationErrors::ScopedField field( + errors, absl::StrCat("[", i, "].typed_config")); + auto* typed_config = + envoy_config_listener_v3_Filter_typed_config(filters[i]); + auto extension = ExtractXdsExtension(context, typed_config, errors); + if (extension.has_value()) { + filter_chain.filter_chain_data->http_connection_manager = + HttpConnectionManagerParse(/*is_client=*/false, context, + std::move(*extension), errors); } } } + // transport_socket auto* transport_socket = envoy_config_listener_v3_FilterChain_transport_socket(filter_chain_proto); if (transport_socket != nullptr) { - auto downstream_context = - DownstreamTlsContextParse(context, transport_socket); - if (!downstream_context.ok()) { - errors.emplace_back(downstream_context.status().message()); - } else { - filter_chain.filter_chain_data->downstream_tls_context = - std::move(*downstream_context); - } + ValidationErrors::ScopedField field(errors, ".transport_socket"); + filter_chain.filter_chain_data->downstream_tls_context = + DownstreamTlsContextParse(context, transport_socket, errors); } // Return result. - if (!errors.empty()) { - return absl::InvalidArgumentError(absl::StrCat( - "Errors parsing FilterChain: [", absl::StrJoin(errors, "; "), "]")); - } + if (errors->size() != original_error_size) return absl::nullopt; return filter_chain; } -absl::StatusOr AddressParse( - const envoy_config_core_v3_Address* address_proto) { +absl::optional AddressParse( + const envoy_config_core_v3_Address* address_proto, + ValidationErrors* errors) { + if (address_proto == nullptr) { + errors->AddError("field not present"); + return absl::nullopt; + } + ValidationErrors::ScopedField field(errors, ".socket_address"); const auto* socket_address = envoy_config_core_v3_Address_socket_address(address_proto); if (socket_address == nullptr) { - return absl::InvalidArgumentError("Address does not have socket_address"); - } - if (envoy_config_core_v3_SocketAddress_protocol(socket_address) != - envoy_config_core_v3_SocketAddress_TCP) { - return absl::InvalidArgumentError("SocketAddress protocol is not TCP"); + errors->AddError("field not present"); + return absl::nullopt; + } + { + ValidationErrors::ScopedField field(errors, ".protocol"); + if (envoy_config_core_v3_SocketAddress_protocol(socket_address) != + envoy_config_core_v3_SocketAddress_TCP) { + errors->AddError("value must be TCP"); + } } + ValidationErrors::ScopedField field2(errors, ".port_value"); uint32_t port = envoy_config_core_v3_SocketAddress_port_value(socket_address); if (port > 65535) { - return absl::InvalidArgumentError("Invalid port"); + errors->AddError("invalid port"); + return absl::nullopt; } return JoinHostPort( UpbStringToAbsl( @@ -796,97 +812,103 @@ struct InternalFilterChainMap { DestinationIpMap destination_ip_map; }; -absl::Status AddFilterChainDataForSourcePort( +void AddFilterChainDataForSourcePort( const FilterChain& filter_chain, uint32_t port, - XdsListenerResource::FilterChainMap::SourcePortsMap* ports_map) { + XdsListenerResource::FilterChainMap::SourcePortsMap* ports_map, + ValidationErrors* errors) { auto insert_result = ports_map->emplace( port, XdsListenerResource::FilterChainMap::FilterChainDataSharedPtr{ filter_chain.filter_chain_data}); if (!insert_result.second) { - return absl::InvalidArgumentError(absl::StrCat( - "Duplicate matching rules detected when adding filter chain: ", + errors->AddError(absl::StrCat( + "duplicate matching rules detected when adding filter chain: ", filter_chain.filter_chain_match.ToString())); } - return absl::OkStatus(); } -absl::Status AddFilterChainDataForSourcePorts( +void AddFilterChainDataForSourcePorts( const FilterChain& filter_chain, - XdsListenerResource::FilterChainMap::SourcePortsMap* ports_map) { + XdsListenerResource::FilterChainMap::SourcePortsMap* ports_map, + ValidationErrors* errors) { if (filter_chain.filter_chain_match.source_ports.empty()) { - return AddFilterChainDataForSourcePort(filter_chain, 0, ports_map); + AddFilterChainDataForSourcePort(filter_chain, 0, ports_map, errors); } else { for (uint32_t port : filter_chain.filter_chain_match.source_ports) { - absl::Status status = - AddFilterChainDataForSourcePort(filter_chain, port, ports_map); - if (!status.ok()) return status; + AddFilterChainDataForSourcePort(filter_chain, port, ports_map, errors); } } - return absl::OkStatus(); } -absl::Status AddFilterChainDataForSourceIpRange( +void AddFilterChainDataForSourceIpRange( const FilterChain& filter_chain, - InternalFilterChainMap::SourceIpMap* source_ip_map) { + InternalFilterChainMap::SourceIpMap* source_ip_map, + ValidationErrors* errors) { if (filter_chain.filter_chain_match.source_prefix_ranges.empty()) { auto insert_result = source_ip_map->emplace( "", XdsListenerResource::FilterChainMap::SourceIp()); - return AddFilterChainDataForSourcePorts( - filter_chain, &insert_result.first->second.ports_map); + AddFilterChainDataForSourcePorts( + filter_chain, &insert_result.first->second.ports_map, errors); } else { for (const auto& prefix_range : filter_chain.filter_chain_match.source_prefix_ranges) { auto addr_str = grpc_sockaddr_to_string(&prefix_range.address, false); - if (!addr_str.ok()) return addr_str.status(); + if (!addr_str.ok()) { + errors->AddError(absl::StrCat( + "error parsing source IP sockaddr (should not happen): ", + addr_str.status().message())); + continue; + } auto insert_result = source_ip_map->emplace( absl::StrCat(*addr_str, "/", prefix_range.prefix_len), XdsListenerResource::FilterChainMap::SourceIp()); if (insert_result.second) { insert_result.first->second.prefix_range.emplace(prefix_range); } - absl::Status status = AddFilterChainDataForSourcePorts( - filter_chain, &insert_result.first->second.ports_map); - if (!status.ok()) return status; + AddFilterChainDataForSourcePorts( + filter_chain, &insert_result.first->second.ports_map, errors); } } - return absl::OkStatus(); } -absl::Status AddFilterChainDataForSourceType( +void AddFilterChainDataForSourceType( const FilterChain& filter_chain, - InternalFilterChainMap::DestinationIp* destination_ip) { + InternalFilterChainMap::DestinationIp* destination_ip, + ValidationErrors* errors) { GPR_ASSERT(static_cast( filter_chain.filter_chain_match.source_type) < 3); - return AddFilterChainDataForSourceIpRange( - filter_chain, &destination_ip->source_types_array[static_cast( - filter_chain.filter_chain_match.source_type)]); + AddFilterChainDataForSourceIpRange( + filter_chain, + &destination_ip->source_types_array[static_cast( + filter_chain.filter_chain_match.source_type)], + errors); } -absl::Status AddFilterChainDataForApplicationProtocols( +void AddFilterChainDataForApplicationProtocols( const FilterChain& filter_chain, - InternalFilterChainMap::DestinationIp* destination_ip) { + InternalFilterChainMap::DestinationIp* destination_ip, + ValidationErrors* errors) { // Only allow filter chains that do not mention application protocols - if (!filter_chain.filter_chain_match.application_protocols.empty()) { - return absl::OkStatus(); + if (filter_chain.filter_chain_match.application_protocols.empty()) { + AddFilterChainDataForSourceType(filter_chain, destination_ip, errors); } - return AddFilterChainDataForSourceType(filter_chain, destination_ip); } -absl::Status AddFilterChainDataForTransportProtocol( +void AddFilterChainDataForTransportProtocol( const FilterChain& filter_chain, - InternalFilterChainMap::DestinationIp* destination_ip) { + InternalFilterChainMap::DestinationIp* destination_ip, + ValidationErrors* errors) { const std::string& transport_protocol = filter_chain.filter_chain_match.transport_protocol; // Only allow filter chains with no transport protocol or "raw_buffer" if (!transport_protocol.empty() && transport_protocol != "raw_buffer") { - return absl::OkStatus(); + return; } // If for this configuration, we've already seen filter chains that mention // the transport protocol as "raw_buffer", we will never match filter chains // that do not mention it. if (destination_ip->transport_protocol_raw_buffer_provided && transport_protocol.empty()) { - return absl::OkStatus(); + return; } if (!transport_protocol.empty() && !destination_ip->transport_protocol_raw_buffer_provided) { @@ -896,45 +918,50 @@ absl::Status AddFilterChainDataForTransportProtocol( destination_ip->source_types_array = InternalFilterChainMap::ConnectionSourceTypesArray(); } - return AddFilterChainDataForApplicationProtocols(filter_chain, - destination_ip); + AddFilterChainDataForApplicationProtocols(filter_chain, destination_ip, + errors); } -absl::Status AddFilterChainDataForServerNames( +void AddFilterChainDataForServerNames( const FilterChain& filter_chain, - InternalFilterChainMap::DestinationIp* destination_ip) { + InternalFilterChainMap::DestinationIp* destination_ip, + ValidationErrors* errors) { // Don't continue adding filter chains with server names mentioned - if (!filter_chain.filter_chain_match.server_names.empty()) { - return absl::OkStatus(); + if (filter_chain.filter_chain_match.server_names.empty()) { + AddFilterChainDataForTransportProtocol(filter_chain, destination_ip, + errors); } - return AddFilterChainDataForTransportProtocol(filter_chain, destination_ip); } -absl::Status AddFilterChainDataForDestinationIpRange( +void AddFilterChainDataForDestinationIpRange( const FilterChain& filter_chain, - InternalFilterChainMap::DestinationIpMap* destination_ip_map) { + InternalFilterChainMap::DestinationIpMap* destination_ip_map, + ValidationErrors* errors) { if (filter_chain.filter_chain_match.prefix_ranges.empty()) { auto insert_result = destination_ip_map->emplace( "", InternalFilterChainMap::DestinationIp()); - return AddFilterChainDataForServerNames(filter_chain, - &insert_result.first->second); + AddFilterChainDataForServerNames(filter_chain, &insert_result.first->second, + errors); } else { for (const auto& prefix_range : filter_chain.filter_chain_match.prefix_ranges) { auto addr_str = grpc_sockaddr_to_string(&prefix_range.address, false); - if (!addr_str.ok()) return addr_str.status(); + if (!addr_str.ok()) { + errors->AddError(absl::StrCat( + "error parsing destination IP sockaddr (should not happen): ", + addr_str.status().message())); + continue; + } auto insert_result = destination_ip_map->emplace( absl::StrCat(*addr_str, "/", prefix_range.prefix_len), InternalFilterChainMap::DestinationIp()); if (insert_result.second) { insert_result.first->second.prefix_range.emplace(prefix_range); } - absl::Status status = AddFilterChainDataForServerNames( - filter_chain, &insert_result.first->second); - if (!status.ok()) return status; + AddFilterChainDataForServerNames(filter_chain, + &insert_result.first->second, errors); } } - return absl::OkStatus(); } XdsListenerResource::FilterChainMap BuildFromInternalFilterChainMap( @@ -956,15 +983,14 @@ XdsListenerResource::FilterChainMap BuildFromInternalFilterChainMap( return filter_chain_map; } -absl::StatusOr BuildFilterChainMap( - const std::vector& filter_chains) { +XdsListenerResource::FilterChainMap BuildFilterChainMap( + const std::vector& filter_chains, ValidationErrors* errors) { InternalFilterChainMap internal_filter_chain_map; for (const auto& filter_chain : filter_chains) { // Discard filter chain entries that specify destination port if (filter_chain.filter_chain_match.destination_port != 0) continue; - absl::Status status = AddFilterChainDataForDestinationIpRange( - filter_chain, &internal_filter_chain_map.destination_ip_map); - if (!status.ok()) return status; + AddFilterChainDataForDestinationIpRange( + filter_chain, &internal_filter_chain_map.destination_ip_map, errors); } return BuildFromInternalFilterChainMap(&internal_filter_chain_map); } @@ -972,46 +998,65 @@ absl::StatusOr BuildFilterChainMap( absl::StatusOr LdsResourceParseServer( const XdsResourceType::DecodeContext& context, const envoy_config_listener_v3_Listener* listener) { - XdsListenerResource lds_update; - lds_update.type = XdsListenerResource::ListenerType::kTcpListener; - auto address = - AddressParse(envoy_config_listener_v3_Listener_address(listener)); - if (!address.ok()) return address.status(); - lds_update.address = std::move(*address); - const auto* use_original_dst = - envoy_config_listener_v3_Listener_use_original_dst(listener); - if (use_original_dst != nullptr) { - if (google_protobuf_BoolValue_value(use_original_dst)) { - return absl::InvalidArgumentError( - "Field \'use_original_dst\' is not supported."); + ValidationErrors errors; + XdsListenerResource::TcpListener tcp_listener; + // address + { + ValidationErrors::ScopedField field(&errors, "address"); + auto address = AddressParse( + envoy_config_listener_v3_Listener_address(listener), &errors); + if (address.has_value()) tcp_listener.address = std::move(*address); + } + // use_original_dst + { + ValidationErrors::ScopedField field(&errors, "use_original_dst"); + const auto* use_original_dst = + envoy_config_listener_v3_Listener_use_original_dst(listener); + if (use_original_dst != nullptr && + google_protobuf_BoolValue_value(use_original_dst)) { + errors.AddError("field not supported"); } } - size_t size = 0; - auto* filter_chains = - envoy_config_listener_v3_Listener_filter_chains(listener, &size); - std::vector parsed_filter_chains; - parsed_filter_chains.reserve(size); - for (size_t i = 0; i < size; i++) { - auto filter_chain = FilterChainParse(context, filter_chains[i]); - if (!filter_chain.ok()) return filter_chain.status(); - parsed_filter_chains.push_back(std::move(*filter_chain)); - } - auto filter_chain_map = BuildFilterChainMap(parsed_filter_chains); - if (!filter_chain_map.ok()) return filter_chain_map.status(); - lds_update.filter_chain_map = std::move(*filter_chain_map); - auto* default_filter_chain = - envoy_config_listener_v3_Listener_default_filter_chain(listener); - if (default_filter_chain != nullptr) { - auto filter_chain = FilterChainParse(context, default_filter_chain); - if (!filter_chain.ok()) return filter_chain.status(); - if (filter_chain->filter_chain_data != nullptr) { - lds_update.default_filter_chain = - std::move(*filter_chain->filter_chain_data); + // filter_chains + size_t num_filter_chains = 0; + { + ValidationErrors::ScopedField field(&errors, "filter_chains"); + auto* filter_chains = envoy_config_listener_v3_Listener_filter_chains( + listener, &num_filter_chains); + std::vector parsed_filter_chains; + parsed_filter_chains.reserve(num_filter_chains); + for (size_t i = 0; i < num_filter_chains; i++) { + ValidationErrors::ScopedField field(&errors, absl::StrCat("[", i, "]")); + auto filter_chain = FilterChainParse(context, filter_chains[i], &errors); + if (filter_chain.has_value()) { + parsed_filter_chains.push_back(std::move(*filter_chain)); + } + } + tcp_listener.filter_chain_map = + BuildFilterChainMap(parsed_filter_chains, &errors); + } + // default_filter_chain + { + ValidationErrors::ScopedField field(&errors, "default_filter_chain"); + auto* default_filter_chain = + envoy_config_listener_v3_Listener_default_filter_chain(listener); + if (default_filter_chain != nullptr) { + auto filter_chain = + FilterChainParse(context, default_filter_chain, &errors); + if (filter_chain.has_value() && + filter_chain->filter_chain_data != nullptr) { + tcp_listener.default_filter_chain = + std::move(*filter_chain->filter_chain_data); + } + } else if (num_filter_chains == 0) { + // Make sure that there is at least one filter chain to use. + errors.AddError("must be set if filter_chains is unset"); } } - if (size == 0 && default_filter_chain == nullptr) { - return absl::InvalidArgumentError("No filter chain provided."); - } + // Return result. + if (!errors.ok()) return errors.status("errors validating server Listener"); + XdsListenerResource lds_update; + lds_update.listener = std::move(tcp_listener); return lds_update; } diff --git a/src/core/ext/xds/xds_listener.h b/src/core/ext/xds/xds_listener.h index 19c5f145011..67d41c02ba9 100644 --- a/src/core/ext/xds/xds_listener.h +++ b/src/core/ext/xds/xds_listener.h @@ -32,6 +32,7 @@ #include "absl/strings/string_view.h" #include "absl/types/optional.h" +#include "absl/types/variant.h" #include "envoy/config/listener/v3/listener.upbdefs.h" #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upbdefs.h" #include "upb/def.h" @@ -46,36 +47,14 @@ namespace grpc_core { -// TODO(roth): When we can use absl::variant<>, consider using that -// here, to enforce the fact that only one of the two fields can be set. struct XdsListenerResource : public XdsResourceType::ResourceData { - struct DownstreamTlsContext { - CommonTlsContext common_tls_context; - bool require_client_certificate = false; - - bool operator==(const DownstreamTlsContext& other) const { - return common_tls_context == other.common_tls_context && - require_client_certificate == other.require_client_certificate; - } - - std::string ToString() const; - bool Empty() const; - }; - - enum class ListenerType { - kTcpListener = 0, - kHttpApiListener, - } type; - struct HttpConnectionManager { - // The name to use in the RDS request. - std::string route_config_name; + // The RDS resource name or inline RouteConfiguration. + absl::variant route_config; + // Storing the Http Connection Manager Common Http Protocol Option // max_stream_duration Duration http_max_stream_duration; - // The RouteConfiguration to use for this listener. - // Present only if it is inlined in the LDS response. - absl::optional rds_update; struct HttpFilter { std::string name; @@ -90,21 +69,26 @@ struct XdsListenerResource : public XdsResourceType::ResourceData { std::vector http_filters; bool operator==(const HttpConnectionManager& other) const { - return route_config_name == other.route_config_name && + return route_config == other.route_config && http_max_stream_duration == other.http_max_stream_duration && - rds_update == other.rds_update && http_filters == other.http_filters; } std::string ToString() const; }; - // Populated for type=kHttpApiListener. - HttpConnectionManager http_connection_manager; + struct DownstreamTlsContext { + CommonTlsContext common_tls_context; + bool require_client_certificate = false; + + bool operator==(const DownstreamTlsContext& other) const { + return common_tls_context == other.common_tls_context && + require_client_certificate == other.require_client_certificate; + } - // Populated for type=kTcpListener. - // host:port listening_address set when type is kTcpListener - std::string address; + std::string ToString() const; + bool Empty() const; + }; struct FilterChainData { DownstreamTlsContext downstream_tls_context; @@ -185,15 +169,26 @@ struct XdsListenerResource : public XdsResourceType::ResourceData { } std::string ToString() const; - } filter_chain_map; + }; + + struct TcpListener { + std::string address; // host:port listening address + FilterChainMap filter_chain_map; + absl::optional default_filter_chain; + + bool operator==(const TcpListener& other) const { + return address == other.address && + filter_chain_map == other.filter_chain_map && + default_filter_chain == other.default_filter_chain; + } + + std::string ToString() const; + }; - absl::optional default_filter_chain; + absl::variant listener; bool operator==(const XdsListenerResource& other) const { - return http_connection_manager == other.http_connection_manager && - address == other.address && - filter_chain_map == other.filter_chain_map && - default_filter_chain == other.default_filter_chain; + return listener == other.listener; } std::string ToString() const; diff --git a/src/core/ext/xds/xds_route_config.cc b/src/core/ext/xds/xds_route_config.cc index bfce809b070..7a63e1ceec5 100644 --- a/src/core/ext/xds/xds_route_config.cc +++ b/src/core/ext/xds/xds_route_config.cc @@ -638,14 +638,13 @@ ParseTypedPerFilterConfig( return absl::InvalidArgumentError(absl::StrCat( "no filter registered for config type ", extension->type)); } - absl::StatusOr filter_config = + absl::optional filter_config = filter_impl->GenerateFilterConfigOverride(std::move(*extension), - context.arena); - if (!filter_config.ok()) { - return absl::InvalidArgumentError( - absl::StrCat("filter config for type ", extension->type, - " failed to parse: ", filter_config.status().message())); + context.arena, &errors); + if (!errors.ok()) { + return errors.status("errors validation extension"); } + GPR_ASSERT(filter_config.has_value()); typed_per_filter_config[std::string(key)] = std::move(*filter_config); } return typed_per_filter_config; diff --git a/src/core/ext/xds/xds_server_config_fetcher.cc b/src/core/ext/xds/xds_server_config_fetcher.cc index 5ba7882fa14..0e1f3da3042 100644 --- a/src/core/ext/xds/xds_server_config_fetcher.cc +++ b/src/core/ext/xds/xds_server_config_fetcher.cc @@ -70,6 +70,7 @@ #include "src/core/lib/debug/trace.h" #include "src/core/lib/gprpp/debug_location.h" #include "src/core/lib/gprpp/host_port.h" +#include "src/core/lib/gprpp/match.h" #include "src/core/lib/gprpp/ref_counted_ptr.h" #include "src/core/lib/gprpp/status_helper.h" #include "src/core/lib/gprpp/sync.h" @@ -550,7 +551,9 @@ void XdsServerConfigFetcher::ListenerWatcher::OnResourceChanged( "[ListenerWatcher %p] Received LDS update from xds client %p: %s", this, xds_client_.get(), listener.ToString().c_str()); } - if (listener.address != listening_address_) { + auto& tcp_listener = + absl::get(listener.listener); + if (tcp_listener.address != listening_address_) { MutexLock lock(&mu_); OnFatalError(absl::FailedPreconditionError( "Address in LDS update does not match listening address")); @@ -558,8 +561,8 @@ void XdsServerConfigFetcher::ListenerWatcher::OnResourceChanged( } auto new_filter_chain_match_manager = MakeRefCounted( xds_client_->Ref(DEBUG_LOCATION, "FilterChainMatchManager"), - std::move(listener.filter_chain_map), - std::move(listener.default_filter_chain)); + std::move(tcp_listener.filter_chain_map), + std::move(tcp_listener.default_filter_chain)); MutexLock lock(&mu_); if (filter_chain_match_manager_ == nullptr || !(new_filter_chain_match_manager->filter_chain_map() == @@ -675,32 +678,25 @@ void XdsServerConfigFetcher::ListenerWatcher::FilterChainMatchManager:: for (const auto& source_type : destination_ip.source_types_array) { for (const auto& source_ip : source_type) { for (const auto& source_port_pair : source_ip.ports_map) { - if (!source_port_pair.second.data->http_connection_manager - .route_config_name.empty()) { - resource_names.insert( - source_port_pair.second.data->http_connection_manager - .route_config_name); - } - filter_chain_data_set.insert(source_port_pair.second.data.get()); + auto* filter_chain_data = source_port_pair.second.data.get(); + const auto* rds_name = absl::get_if( + &filter_chain_data->http_connection_manager.route_config); + if (rds_name != nullptr) resource_names.insert(*rds_name); + filter_chain_data_set.insert(filter_chain_data); } } } } if (default_filter_chain_.has_value()) { - if (!default_filter_chain_->http_connection_manager.route_config_name - .empty()) { - resource_names.insert( - default_filter_chain_->http_connection_manager.route_config_name); - } - std::reverse( - default_filter_chain_->http_connection_manager.http_filters.begin(), - default_filter_chain_->http_connection_manager.http_filters.end()); + auto& hcm = default_filter_chain_->http_connection_manager; + const auto* rds_name = absl::get_if(&hcm.route_config); + if (rds_name != nullptr) resource_names.insert(*rds_name); + std::reverse(hcm.http_filters.begin(), hcm.http_filters.end()); } // Reverse the lists of HTTP filters in all the filter chains for (auto* filter_chain_data : filter_chain_data_set) { - std::reverse( - filter_chain_data->http_connection_manager.http_filters.begin(), - filter_chain_data->http_connection_manager.http_filters.end()); + auto& hcm = filter_chain_data->http_connection_manager; + std::reverse(hcm.http_filters.begin(), hcm.http_filters.end()); } // Start watching on referenced RDS resources struct WatcherToStart { @@ -1076,27 +1072,29 @@ absl::StatusOr XdsServerConfigFetcher::ListenerWatcher:: filters.push_back(&kServerConfigSelectorFilter); channel_stack_modifier = MakeRefCounted(std::move(filters)); - if (filter_chain->http_connection_manager.rds_update.has_value()) { - server_config_selector_provider = - MakeRefCounted( - filter_chain->http_connection_manager.rds_update.value(), - filter_chain->http_connection_manager.http_filters); - } else { - absl::StatusOr initial_resource; - { - MutexLock lock(&mu_); - initial_resource = - rds_map_[filter_chain->http_connection_manager.route_config_name] - .rds_update.value(); - } - server_config_selector_provider = - MakeRefCounted( - xds_client_->Ref(DEBUG_LOCATION, - "DynamicXdsServerConfigSelectorProvider"), - filter_chain->http_connection_manager.route_config_name, - std::move(initial_resource), - filter_chain->http_connection_manager.http_filters); - } + Match( + filter_chain->http_connection_manager.route_config, + // RDS resource name + [&](const std::string& rds_name) { + absl::StatusOr initial_resource; + { + MutexLock lock(&mu_); + initial_resource = rds_map_[rds_name].rds_update.value(); + } + server_config_selector_provider = + MakeRefCounted( + xds_client_->Ref(DEBUG_LOCATION, + "DynamicXdsServerConfigSelectorProvider"), + rds_name, std::move(initial_resource), + filter_chain->http_connection_manager.http_filters); + }, + // inline RouteConfig + [&](const XdsRouteConfigResource& route_config) { + server_config_selector_provider = + MakeRefCounted( + route_config, + filter_chain->http_connection_manager.http_filters); + }); args = args.SetObject(server_config_selector_provider) .SetObject(channel_stack_modifier); // Add XdsCertificateProvider if credentials are xDS. diff --git a/src/proto/grpc/testing/xds/v3/address.proto b/src/proto/grpc/testing/xds/v3/address.proto index 47efbed8e06..df0c90acd4c 100644 --- a/src/proto/grpc/testing/xds/v3/address.proto +++ b/src/proto/grpc/testing/xds/v3/address.proto @@ -24,6 +24,12 @@ import "google/protobuf/wrappers.proto"; // [#next-free-field: 7] message SocketAddress { + enum Protocol { + TCP = 0; + UDP = 1; + } + Protocol protocol = 1; + // The address for this socket. :ref:`Listeners ` will bind // to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` // to bind to any address. [#comment:TODO(zuercher) reinstate when implemented: diff --git a/src/proto/grpc/testing/xds/v3/route.proto b/src/proto/grpc/testing/xds/v3/route.proto index b06fbc71946..8a8a2cec681 100644 --- a/src/proto/grpc/testing/xds/v3/route.proto +++ b/src/proto/grpc/testing/xds/v3/route.proto @@ -426,6 +426,8 @@ message HeaderMatcher { // // * The suffix *abcd* matches the value *xyzabcd*, but not for *xyzbcd*. string suffix_match = 10; + + string contains_match = 12; } // If specified, the match result will be inverted before checking. Defaults to false. diff --git a/test/core/xds/BUILD b/test/core/xds/BUILD index 1a086416e47..a69a0e3a439 100644 --- a/test/core/xds/BUILD +++ b/test/core/xds/BUILD @@ -107,6 +107,7 @@ grpc_cc_test( srcs = ["xds_lb_policy_registry_test.cc"], external_deps = ["gtest"], language = "C++", + uses_event_engine = False, uses_polling = False, deps = [ "//:gpr", @@ -174,6 +175,47 @@ grpc_cc_test( ], ) +grpc_cc_test( + name = "xds_http_filters_test", + srcs = ["xds_http_filters_test.cc"], + external_deps = ["gtest"], + language = "C++", + uses_event_engine = False, + uses_polling = False, + deps = [ + "//:gpr", + "//:grpc", + "//src/proto/grpc/testing/xds/v3:fault_proto", + "//src/proto/grpc/testing/xds/v3:http_filter_rbac_proto", + "//src/proto/grpc/testing/xds/v3:router_proto", + "//test/core/util:grpc_test_util", + "//test/cpp/util:grpc_cli_utils", + ], +) + +grpc_cc_test( + name = "xds_listener_resource_type_test", + srcs = ["xds_listener_resource_type_test.cc"], + external_deps = ["gtest"], + language = "C++", + uses_event_engine = False, + uses_polling = False, + deps = [ + "//:gpr", + "//:grpc", + "//src/core:grpc_xds_client", + "//src/proto/grpc/testing/xds/v3:fault_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:router_proto", + "//src/proto/grpc/testing/xds/v3:tls_proto", + "//src/proto/grpc/testing/xds/v3:typed_struct_proto", + "//test/core/util:grpc_test_util", + "//test/cpp/util:grpc_cli_utils", + ], +) + grpc_cc_test( name = "xds_cluster_resource_type_test", srcs = ["xds_cluster_resource_type_test.cc"], @@ -188,6 +230,7 @@ grpc_cc_test( "//src/proto/grpc/testing/xds/v3:aggregate_cluster_proto", "//src/proto/grpc/testing/xds/v3:cluster_proto", "//src/proto/grpc/testing/xds/v3:tls_proto", + "//src/proto/grpc/testing/xds/v3:typed_struct_proto", "//test/core/util:grpc_test_util", ], ) diff --git a/test/core/xds/xds_cluster_resource_type_test.cc b/test/core/xds/xds_cluster_resource_type_test.cc index 8622ac7adfc..1924c6acdac 100644 --- a/test/core/xds/xds_cluster_resource_type_test.cc +++ b/test/core/xds/xds_cluster_resource_type_test.cc @@ -53,6 +53,7 @@ #include "src/proto/grpc/testing/xds/v3/endpoint.pb.h" #include "src/proto/grpc/testing/xds/v3/outlier_detection.pb.h" #include "src/proto/grpc/testing/xds/v3/tls.pb.h" +#include "src/proto/grpc/testing/xds/v3/typed_struct.pb.h" #include "test/core/util/test_config.h" using envoy::config::cluster::v3::Cluster; @@ -809,7 +810,7 @@ TEST_F(TlsConfigTest, UnknownCertificateProviderInstance) { << decode_result.resource.status(); } -TEST_F(TlsConfigTest, TransportSocketTypedConfigUnset) { +TEST_F(TlsConfigTest, UnknownTransportSocketType) { Cluster cluster; cluster.set_name("foo"); cluster.set_type(cluster.EDS); @@ -827,13 +828,13 @@ TEST_F(TlsConfigTest, TransportSocketTypedConfigUnset) { absl::StatusCode::kInvalidArgument); EXPECT_EQ(decode_result.resource.status().message(), "errors validating Cluster resource: [" - "field:transport_socket.typed_config.type_url " - "error:unrecognized transport socket type: " - "envoy.config.cluster.v3.Cluster]") + "field:transport_socket.typed_config.value[" + "envoy.config.cluster.v3.Cluster].type_url " + "error:unsupported transport socket type]") << decode_result.resource.status(); } -TEST_F(TlsConfigTest, UnknownTransportSocketType) { +TEST_F(TlsConfigTest, UnparseableUpstreamTlsContext) { Cluster cluster; cluster.set_name("foo"); cluster.set_type(cluster.EDS); @@ -859,6 +860,35 @@ TEST_F(TlsConfigTest, UnknownTransportSocketType) { << decode_result.resource.status(); } +TEST_F(TlsConfigTest, UpstreamTlsContextInTypedStruct) { + Cluster cluster; + cluster.set_name("foo"); + cluster.set_type(cluster.EDS); + cluster.mutable_eds_cluster_config()->mutable_eds_config()->mutable_self(); + auto* transport_socket = cluster.mutable_transport_socket(); + xds::type::v3::TypedStruct typed_struct; + typed_struct.set_type_url( + "types.googleapis.com/" + "envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext"); + transport_socket->mutable_typed_config()->PackFrom(typed_struct); + std::string serialized_resource; + ASSERT_TRUE(cluster.SerializeToString(&serialized_resource)); + auto* resource_type = XdsClusterResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating Cluster resource: [" + "field:transport_socket.typed_config.value[" + "xds.type.v3.TypedStruct].value[" + "envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext] " + "error:can't decode UpstreamTlsContext]") + << decode_result.resource.status(); +} + TEST_F(TlsConfigTest, CaCertProviderUnset) { Cluster cluster; cluster.set_name("foo"); diff --git a/test/core/xds/xds_http_filters_test.cc b/test/core/xds/xds_http_filters_test.cc new file mode 100644 index 00000000000..cca65b44803 --- /dev/null +++ b/test/core/xds/xds_http_filters_test.cc @@ -0,0 +1,929 @@ +// +// Copyright 2022 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/xds/xds_http_filters.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/strings/strip.h" +#include "absl/types/variant.h" +#include "gtest/gtest.h" +#include "upb/upb.hpp" + +#include +#include +#include +#include + +#include "src/core/ext/filters/fault_injection/fault_injection_filter.h" +#include "src/core/ext/filters/fault_injection/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/proto/grpc/testing/xds/v3/address.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/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" +#include "src/proto/grpc/testing/xds/v3/percent.pb.h" +#include "src/proto/grpc/testing/xds/v3/range.pb.h" +#include "src/proto/grpc/testing/xds/v3/rbac.pb.h" +#include "src/proto/grpc/testing/xds/v3/regex.pb.h" +#include "src/proto/grpc/testing/xds/v3/route.pb.h" +#include "src/proto/grpc/testing/xds/v3/router.pb.h" +#include "src/proto/grpc/testing/xds/v3/string.pb.h" +#include "test/core/util/test_config.h" + +// IWYU pragma: no_include + +namespace grpc_core { +namespace testing { +namespace { + +using ::envoy::extensions::filters::http::fault::v3::HTTPFault; +using ::envoy::extensions::filters::http::rbac::v3::RBAC; +using ::envoy::extensions::filters::http::rbac::v3::RBACPerRoute; +using ::envoy::extensions::filters::http::router::v3::Router; + +// +// base class for filter tests +// + +class XdsHttpFilterTest : public ::testing::Test { + protected: + XdsHttpFilterTest() { + // Start with a clean registry for each test. + XdsHttpFilterRegistry::Shutdown(); + XdsHttpFilterRegistry::Init(); + } + + XdsExtension MakeXdsExtension(const grpc::protobuf::Message& message) { + google::protobuf::Any any; + any.PackFrom(message); + type_url_storage_ = + std::string(absl::StripPrefix(any.type_url(), "type.googleapis.com/")); + serialized_storage_ = std::string(any.value()); + ValidationErrors::ScopedField field( + &errors_, absl::StrCat("http_filter.value[", type_url_storage_, "]")); + XdsExtension extension; + extension.type = absl::string_view(type_url_storage_); + extension.value = absl::string_view(serialized_storage_); + extension.validation_fields.push_back(std::move(field)); + return extension; + } + + static const XdsHttpFilterImpl* GetFilter(absl::string_view type) { + return XdsHttpFilterRegistry::GetFilterForType( + absl::StripPrefix(type, "type.googleapis.com/")); + } + + ValidationErrors errors_; + upb::Arena arena_; + std::string type_url_storage_; + std::string serialized_storage_; +}; + +// +// XdsHttpFilterRegistry tests +// + +using XdsHttpFilterRegistryTest = XdsHttpFilterTest; + +TEST_F(XdsHttpFilterRegistryTest, Basic) { + // Start with an empty registry. + XdsHttpFilterRegistry::Shutdown(); + XdsHttpFilterRegistry::Init(/*register_builtins=*/false); + // Returns null when a filter has not yet been registered. + XdsExtension extension = MakeXdsExtension(Router()); + EXPECT_EQ(GetFilter(extension.type), nullptr); + // Now register the filter. + auto filter = std::make_unique(); + auto* filter_ptr = filter.get(); + XdsHttpFilterRegistry::RegisterFilter(std::move(filter)); + // And check that it is now present. + EXPECT_EQ(GetFilter(extension.type), filter_ptr); +} + +using XdsHttpFilterRegistryDeathTest = XdsHttpFilterTest; + +TEST_F(XdsHttpFilterRegistryDeathTest, DuplicateRegistryFails) { + GTEST_FLAG_SET(death_test_style, "threadsafe"); + ASSERT_DEATH( + // The router filter is already in the registry. + XdsHttpFilterRegistry::RegisterFilter( + std::make_unique()), + ""); +} + +// +// Router filter tests +// + +class XdsRouterFilterTest : public XdsHttpFilterTest { + protected: + XdsRouterFilterTest() { + XdsExtension extension = MakeXdsExtension(Router()); + filter_ = GetFilter(extension.type); + GPR_ASSERT(filter_ != nullptr); + } + + const XdsHttpFilterImpl* filter_; +}; + +TEST_F(XdsRouterFilterTest, Accessors) { + EXPECT_EQ(filter_->ConfigProtoName(), + "envoy.extensions.filters.http.router.v3.Router"); + EXPECT_EQ(filter_->OverrideConfigProtoName(), ""); + EXPECT_EQ(filter_->channel_filter(), nullptr); + EXPECT_TRUE(filter_->IsSupportedOnClients()); + EXPECT_TRUE(filter_->IsSupportedOnServers()); + EXPECT_TRUE(filter_->IsTerminalFilter()); +} + +TEST_F(XdsRouterFilterTest, GenerateFilterConfig) { + XdsExtension extension = MakeXdsExtension(Router()); + auto config = filter_->GenerateFilterConfig(std::move(extension), + arena_.ptr(), &errors_); + ASSERT_TRUE(errors_.ok()) << errors_.status("unexpected errors"); + ASSERT_TRUE(config.has_value()); + EXPECT_EQ(config->config_proto_type_name, filter_->ConfigProtoName()); + EXPECT_EQ(config->config, Json()) << config->config.Dump(); +} + +TEST_F(XdsRouterFilterTest, GenerateFilterConfigTypedStruct) { + XdsExtension extension = MakeXdsExtension(Router()); + extension.value = Json(); + auto config = filter_->GenerateFilterConfig(std::move(extension), + arena_.ptr(), &errors_); + absl::Status status = errors_.status("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.router.v3.Router] " + "error:could not parse router filter config]") + << status; +} + +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(std::move(extension), + arena_.ptr(), &errors_); + absl::Status status = errors_.status("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.router.v3.Router] " + "error:could not parse router filter config]") + << status; +} + +TEST_F(XdsRouterFilterTest, GenerateFilterConfigOverride) { + XdsExtension extension = MakeXdsExtension(Router()); + auto config = filter_->GenerateFilterConfigOverride(std::move(extension), + arena_.ptr(), &errors_); + absl::Status status = errors_.status("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.router.v3.Router] " + "error:router filter does not support config override]") + << status; +} + +// +// Fault injection filter tests +// + +class XdsFaultInjectionFilterTest : public XdsHttpFilterTest { + protected: + XdsFaultInjectionFilterTest() { + XdsExtension extension = MakeXdsExtension(HTTPFault()); + filter_ = GetFilter(extension.type); + GPR_ASSERT(filter_ != nullptr); + } + + const XdsHttpFilterImpl* filter_; +}; + +TEST_F(XdsFaultInjectionFilterTest, Accessors) { + EXPECT_EQ(filter_->ConfigProtoName(), + "envoy.extensions.filters.http.fault.v3.HTTPFault"); + EXPECT_EQ(filter_->OverrideConfigProtoName(), ""); + EXPECT_EQ(filter_->channel_filter(), &FaultInjectionFilter::kFilter); + EXPECT_TRUE(filter_->IsSupportedOnClients()); + EXPECT_FALSE(filter_->IsSupportedOnServers()); + EXPECT_FALSE(filter_->IsTerminalFilter()); +} + +TEST_F(XdsFaultInjectionFilterTest, ModifyChannelArgs) { + ChannelArgs args = filter_->ModifyChannelArgs(ChannelArgs()); + auto value = args.GetInt(GRPC_ARG_PARSE_FAULT_INJECTION_METHOD_CONFIG); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(*value, 1); +} + +TEST_F(XdsFaultInjectionFilterTest, GenerateServiceConfigTopLevelConfig) { + XdsHttpFilterImpl::FilterConfig config; + config.config = Json::Object{{"foo", "bar"}}; + auto service_config = filter_->GenerateServiceConfig(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) { + XdsHttpFilterImpl::FilterConfig top_config; + top_config.config = Json::Object{{"foo", "bar"}}; + XdsHttpFilterImpl::FilterConfig override_config; + override_config.config = Json::Object{{"baz", "quux"}}; + auto service_config = + filter_->GenerateServiceConfig(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\"}"); +} + +// For the fault injection filter, GenerateFilterConfig() and +// GenerateFilterConfigOverride() accept the same input, so we want to +// run all tests for both. +class XdsFaultInjectionFilterConfigTest + : public XdsFaultInjectionFilterTest, + public ::testing::WithParamInterface { + protected: + absl::optional GenerateConfig( + XdsExtension extension) { + if (GetParam()) { + return filter_->GenerateFilterConfigOverride(std::move(extension), + arena_.ptr(), &errors_); + } + return filter_->GenerateFilterConfig(std::move(extension), arena_.ptr(), + &errors_); + } +}; + +INSTANTIATE_TEST_SUITE_P(XdsFaultFilter, XdsFaultInjectionFilterConfigTest, + ::testing::Bool()); + +TEST_P(XdsFaultInjectionFilterConfigTest, EmptyConfig) { + XdsExtension extension = MakeXdsExtension(HTTPFault()); + auto config = GenerateConfig(std::move(extension)); + ASSERT_TRUE(errors_.ok()) << errors_.status("unexpected errors"); + ASSERT_TRUE(config.has_value()); + EXPECT_EQ(config->config_proto_type_name, filter_->ConfigProtoName()); + EXPECT_EQ(config->config, Json(Json::Object())) << config->config.Dump(); +} + +TEST_P(XdsFaultInjectionFilterConfigTest, BasicConfig) { + HTTPFault fault; + auto* abort = fault.mutable_abort(); + abort->set_grpc_status(GRPC_STATUS_UNAVAILABLE); + abort->mutable_percentage()->set_numerator(75); + auto* delay = fault.mutable_delay(); + auto* fixed_delay = delay->mutable_fixed_delay(); + fixed_delay->set_seconds(1); + fixed_delay->set_nanos(500000000); + delay->mutable_percentage()->set_numerator(25); + fault.mutable_max_active_faults()->set_value(10); + XdsExtension extension = MakeXdsExtension(fault); + auto config = GenerateConfig(std::move(extension)); + ASSERT_TRUE(errors_.ok()) << errors_.status("unexpected errors"); + ASSERT_TRUE(config.has_value()); + EXPECT_EQ(config->config_proto_type_name, filter_->ConfigProtoName()); + EXPECT_EQ(config->config.Dump(), + "{\"abortCode\":\"UNAVAILABLE\"," + "\"abortPercentageDenominator\":100," + "\"abortPercentageNumerator\":75," + "\"delay\":\"1.500000000s\"," + "\"delayPercentageDenominator\":100," + "\"delayPercentageNumerator\":25," + "\"maxFaults\":10}"); +} + +TEST_P(XdsFaultInjectionFilterConfigTest, HttpAbortCode) { + HTTPFault fault; + auto* abort = fault.mutable_abort(); + abort->set_http_status(404); + XdsExtension extension = MakeXdsExtension(fault); + auto config = GenerateConfig(std::move(extension)); + ASSERT_TRUE(errors_.ok()) << errors_.status("unexpected errors"); + ASSERT_TRUE(config.has_value()); + EXPECT_EQ(config->config_proto_type_name, filter_->ConfigProtoName()); + EXPECT_EQ(config->config.Dump(), "{\"abortCode\":\"UNIMPLEMENTED\"}"); +} + +TEST_P(XdsFaultInjectionFilterConfigTest, HeaderAbortAndDelay) { + HTTPFault fault; + fault.mutable_abort()->mutable_header_abort(); + fault.mutable_delay()->mutable_header_delay(); + XdsExtension extension = MakeXdsExtension(fault); + auto config = GenerateConfig(std::move(extension)); + ASSERT_TRUE(errors_.ok()) << errors_.status("unexpected errors"); + ASSERT_TRUE(config.has_value()); + EXPECT_EQ(config->config_proto_type_name, filter_->ConfigProtoName()); + EXPECT_EQ( + config->config.Dump(), + "{\"abortCode\":\"OK\"," + "\"abortCodeHeader\":\"x-envoy-fault-abort-grpc-request\"," + "\"abortPercentageHeader\":\"x-envoy-fault-abort-percentage\"," + "\"delayHeader\":\"x-envoy-fault-delay-request\"," + "\"delayPercentageHeader\":\"x-envoy-fault-delay-request-percentage\"}"); +} + +TEST_P(XdsFaultInjectionFilterConfigTest, InvalidGrpcStatusCode) { + HTTPFault fault; + fault.mutable_abort()->set_grpc_status(17); + XdsExtension extension = MakeXdsExtension(fault); + auto config = GenerateConfig(std::move(extension)); + absl::Status status = errors_.status("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.fault.v3" + ".HTTPFault].abort.grpc_status " + "error:invalid gRPC status code: 17]") + << status; +} + +TEST_P(XdsFaultInjectionFilterConfigTest, InvalidDuration) { + HTTPFault fault; + fault.mutable_delay()->mutable_fixed_delay()->set_seconds(315576000001); + XdsExtension extension = MakeXdsExtension(fault); + auto config = GenerateConfig(std::move(extension)); + absl::Status status = errors_.status("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.fault.v3" + ".HTTPFault].delay.fixed_delay.seconds " + "error:value must be in the range [0, 315576000000]]") + << status; +} + +TEST_P(XdsFaultInjectionFilterConfigTest, TypedStruct) { + XdsExtension extension = MakeXdsExtension(HTTPFault()); + extension.value = Json(); + auto config = GenerateConfig(std::move(extension)); + absl::Status status = errors_.status("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.fault.v3" + ".HTTPFault] error:could not parse fault injection filter config]") + << status; +} + +TEST_P(XdsFaultInjectionFilterConfigTest, Unparseable) { + XdsExtension extension = MakeXdsExtension(HTTPFault()); + std::string serialized_resource("\0", 1); + extension.value = absl::string_view(serialized_resource); + auto config = GenerateConfig(std::move(extension)); + absl::Status status = errors_.status("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.fault.v3" + ".HTTPFault] error:could not parse fault injection filter config]") + << status; +} + +// +// RBAC filter tests +// + +class XdsRbacFilterTest : public XdsHttpFilterTest { + protected: + XdsRbacFilterTest() { + XdsExtension extension = MakeXdsExtension(RBAC()); + filter_ = GetFilter(extension.type); + GPR_ASSERT(filter_ != nullptr); + } + + const XdsHttpFilterImpl* filter_; +}; + +TEST_F(XdsRbacFilterTest, Accessors) { + EXPECT_EQ(filter_->ConfigProtoName(), + "envoy.extensions.filters.http.rbac.v3.RBAC"); + EXPECT_EQ(filter_->OverrideConfigProtoName(), + "envoy.extensions.filters.http.rbac.v3.RBACPerRoute"); + EXPECT_EQ(filter_->channel_filter(), &RbacFilter::kFilterVtable); + EXPECT_FALSE(filter_->IsSupportedOnClients()); + EXPECT_TRUE(filter_->IsSupportedOnServers()); + EXPECT_FALSE(filter_->IsTerminalFilter()); +} + +TEST_F(XdsRbacFilterTest, ModifyChannelArgs) { + ChannelArgs args = filter_->ModifyChannelArgs(ChannelArgs()); + auto value = args.GetInt(GRPC_ARG_PARSE_RBAC_METHOD_CONFIG); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(*value, 1); +} + +TEST_F(XdsRbacFilterTest, GenerateFilterConfig) { + XdsExtension extension = MakeXdsExtension(RBAC()); + auto config = filter_->GenerateFilterConfig(std::move(extension), + arena_.ptr(), &errors_); + ASSERT_TRUE(errors_.ok()) << errors_.status("unexpected errors"); + ASSERT_TRUE(config.has_value()); + EXPECT_EQ(config->config_proto_type_name, filter_->ConfigProtoName()); + EXPECT_EQ(config->config, Json(Json::Object())) << config->config.Dump(); +} + +TEST_F(XdsRbacFilterTest, GenerateFilterConfigTypedStruct) { + XdsExtension extension = MakeXdsExtension(RBAC()); + extension.value = Json(); + auto config = filter_->GenerateFilterConfig(std::move(extension), + arena_.ptr(), &errors_); + absl::Status status = errors_.status("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.rbac.v3.RBAC] " + "error:could not parse HTTP RBAC filter config]") + << status; +} + +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(std::move(extension), + arena_.ptr(), &errors_); + absl::Status status = errors_.status("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.rbac.v3.RBAC] " + "error:could not parse HTTP RBAC filter config]") + << status; +} + +TEST_F(XdsRbacFilterTest, GenerateFilterConfigOverride) { + XdsExtension extension = MakeXdsExtension(RBACPerRoute()); + auto config = filter_->GenerateFilterConfigOverride(std::move(extension), + arena_.ptr(), &errors_); + ASSERT_TRUE(errors_.ok()) << errors_.status("unexpected errors"); + ASSERT_TRUE(config.has_value()); + EXPECT_EQ(config->config_proto_type_name, filter_->OverrideConfigProtoName()); + EXPECT_EQ(config->config, Json(Json::Object())) << config->config.Dump(); +} + +TEST_F(XdsRbacFilterTest, GenerateFilterConfigOverrideTypedStruct) { + XdsExtension extension = MakeXdsExtension(RBACPerRoute()); + extension.value = Json(); + auto config = filter_->GenerateFilterConfigOverride(std::move(extension), + arena_.ptr(), &errors_); + absl::Status status = errors_.status("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.rbac.v3" + ".RBACPerRoute] error:could not parse RBACPerRoute]") + << status; +} + +TEST_F(XdsRbacFilterTest, GenerateFilterConfigOverrideUnparseable) { + XdsExtension extension = MakeXdsExtension(RBACPerRoute()); + std::string serialized_resource("\0", 1); + extension.value = absl::string_view(serialized_resource); + auto config = filter_->GenerateFilterConfigOverride(std::move(extension), + arena_.ptr(), &errors_); + absl::Status status = errors_.status("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.rbac.v3" + ".RBACPerRoute] error:could not parse RBACPerRoute]") + << status; +} + +// 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(). +class XdsRbacFilterConfigTest : public XdsRbacFilterTest, + public ::testing::WithParamInterface { + protected: + absl::optional GenerateConfig(RBAC rbac) { + if (GetParam()) { + RBACPerRoute rbac_per_route; + *rbac_per_route.mutable_rbac() = rbac; + XdsExtension extension = MakeXdsExtension(rbac_per_route); + return filter_->GenerateFilterConfigOverride(std::move(extension), + arena_.ptr(), &errors_); + } + XdsExtension extension = MakeXdsExtension(rbac); + return filter_->GenerateFilterConfig(std::move(extension), arena_.ptr(), + &errors_); + } + + std::string FieldPrefix() { + return absl::StrCat("http_filter.value[", + (GetParam() ? filter_->OverrideConfigProtoName() + : filter_->ConfigProtoName()), + "]", (GetParam() ? ".rbac" : "")); + } +}; + +INSTANTIATE_TEST_SUITE_P(XdsRbacFilter, XdsRbacFilterConfigTest, + ::testing::Bool()); + +TEST_P(XdsRbacFilterConfigTest, EmptyConfig) { + auto config = GenerateConfig(RBAC()); + ASSERT_TRUE(errors_.ok()) << errors_.status("unexpected errors"); + ASSERT_TRUE(config.has_value()); + EXPECT_EQ(config->config_proto_type_name, + GetParam() ? filter_->OverrideConfigProtoName() + : filter_->ConfigProtoName()); + EXPECT_EQ(config->config, Json(Json::Object())) << config->config.Dump(); +} + +TEST_P(XdsRbacFilterConfigTest, AllPermissionTypes) { + RBAC rbac; + auto* rules = rbac.mutable_rules(); + rules->set_action(rules->ALLOW); + auto& policy = (*rules->mutable_policies())["policy_name"]; + // any + policy.add_permissions()->set_any(true); + // header exact match with invert + auto* header = policy.add_permissions()->mutable_header(); + header->set_name("header_name1"); + header->set_exact_match("exact_match"); + header->set_invert_match(true); + // header regex match + header = policy.add_permissions()->mutable_header(); + header->set_name("header_name2"); + header->mutable_safe_regex_match()->set_regex("regex_match"); + // header range match + header = policy.add_permissions()->mutable_header(); + header->set_name("header_name3"); + auto* range = header->mutable_range_match(); + range->set_start(1); + range->set_end(3); + // header present match + header = policy.add_permissions()->mutable_header(); + header->set_name("header_name4"); + header->set_present_match(true); + // header prefix match + header = policy.add_permissions()->mutable_header(); + header->set_name("header_name5"); + header->set_prefix_match("prefix_match"); + // header suffix match + header = policy.add_permissions()->mutable_header(); + header->set_name("header_name6"); + header->set_suffix_match("suffix_match"); + // header contains match + header = policy.add_permissions()->mutable_header(); + header->set_name("header_name7"); + header->set_contains_match("contains_match"); + // path exact match with ignore_case + auto* string_matcher = + policy.add_permissions()->mutable_url_path()->mutable_path(); + string_matcher->set_exact("exact_match"); + string_matcher->set_ignore_case(true); + // path prefix match + string_matcher = policy.add_permissions()->mutable_url_path()->mutable_path(); + string_matcher->set_prefix("prefix_match"); + // path suffix match + string_matcher = policy.add_permissions()->mutable_url_path()->mutable_path(); + string_matcher->set_suffix("suffix_match"); + // path contains match + string_matcher = policy.add_permissions()->mutable_url_path()->mutable_path(); + string_matcher->set_contains("contains_match"); + // path regex match + string_matcher = policy.add_permissions()->mutable_url_path()->mutable_path(); + string_matcher->mutable_safe_regex()->set_regex("regex_match"); + // destination IP match with prefix len + auto* cidr_range = policy.add_permissions()->mutable_destination_ip(); + cidr_range->set_address_prefix("127.0.0"); + cidr_range->mutable_prefix_len()->set_value(24); + // destination IP match + cidr_range = policy.add_permissions()->mutable_destination_ip(); + cidr_range->set_address_prefix("10.0.0"); + // destination port match + policy.add_permissions()->set_destination_port(1234); + // metadata match + policy.add_permissions()->mutable_metadata(); + // metadata match with invert + policy.add_permissions()->mutable_metadata()->set_invert(true); + // requested server name + string_matcher = policy.add_permissions()->mutable_requested_server_name(); + string_matcher->set_exact("exact_match"); + // not + policy.add_permissions()->mutable_not_rule()->set_any(true); + // and + policy.add_permissions()->mutable_and_rules()->add_rules()->set_any(true); + // or + policy.add_permissions()->mutable_or_rules()->add_rules()->set_any(true); + auto config = GenerateConfig(rbac); + ASSERT_TRUE(errors_.ok()) << errors_.status("unexpected errors"); + ASSERT_TRUE(config.has_value()); + EXPECT_EQ(config->config_proto_type_name, + GetParam() ? filter_->OverrideConfigProtoName() + : filter_->ConfigProtoName()); + EXPECT_EQ(config->config.Dump(), + "{\"rules\":{" + "\"action\":0," + "\"policies\":{" + "\"policy_name\":{" + "\"permissions\":[" + // any + "{\"any\":true}," + // header exact match with invert + "{\"header\":" + "{\"exactMatch\":\"exact_match\",\"invertMatch\":true," + "\"name\":\"header_name1\"}}," + // header regex match + "{\"header\":" + "{\"invertMatch\":false,\"name\":\"header_name2\"," + "\"safeRegexMatch\":{\"regex\":\"regex_match\"}}}," + // header range match + "{\"header\":" + "{\"invertMatch\":false,\"name\":\"header_name3\"," + "\"rangeMatch\":{\"end\":3,\"start\":1}}}," + // header present match + "{\"header\":" + "{\"invertMatch\":false,\"name\":\"header_name4\"," + "\"presentMatch\":true}}," + // header prefix match + "{\"header\":" + "{\"invertMatch\":false,\"name\":\"header_name5\"," + "\"prefixMatch\":\"prefix_match\"}}," + // header suffix match + "{\"header\":" + "{\"invertMatch\":false,\"name\":\"header_name6\"," + "\"suffixMatch\":\"suffix_match\"}}," + // header contains match + "{\"header\":" + "{\"containsMatch\":\"contains_match\",\"invertMatch\":false," + "\"name\":\"header_name7\"}}," + // path exact match with ignore_case + "{\"urlPath\":{\"path\":{" + "\"exact\":\"exact_match\",\"ignoreCase\":true}}}," + // path prefix match + "{\"urlPath\":{\"path\":{" + "\"ignoreCase\":false,\"prefix\":\"prefix_match\"}}}," + // path suffix match + "{\"urlPath\":{\"path\":{" + "\"ignoreCase\":false,\"suffix\":\"suffix_match\"}}}," + // path contains match + "{\"urlPath\":{\"path\":{" + "\"contains\":\"contains_match\",\"ignoreCase\":false}}}," + // path regex match + "{\"urlPath\":{\"path\":{" + "\"ignoreCase\":false,\"safeRegex\":{\"regex\":\"regex_match\"}}}}," + // destination IP match with prefix len + "{\"destinationIp\":{" + "\"addressPrefix\":\"127.0.0\",\"prefixLen\":{\"value\":24}}}," + // destination IP match + "{\"destinationIp\":{\"addressPrefix\":\"10.0.0\"}}," + // destination port match + "{\"destinationPort\":1234}," + // metadata match + "{\"metadata\":{\"invert\":false}}," + // metadata match with invert + "{\"metadata\":{\"invert\":true}}," + // requested server name + "{\"requestedServerName\":{" + "\"exact\":\"exact_match\",\"ignoreCase\":false}}," + // not + "{\"notRule\":{\"any\":true}}," + // and + "{\"andRules\":{\"rules\":[{\"any\":true}]}}," + // or + "{\"orRules\":{\"rules\":[{\"any\":true}]}}" + "]," + "\"principals\":[]" + "}}}}"); +} + +TEST_P(XdsRbacFilterConfigTest, AllPrincipalTypes) { + RBAC rbac; + auto* rules = rbac.mutable_rules(); + rules->set_action(rules->ALLOW); + auto& policy = (*rules->mutable_policies())["policy_name"]; + // any + policy.add_principals()->set_any(true); + // authenticated principal name + // (not testing all possible string matchers here, since they're + // covered in the AllPermissionTypes test above) + auto* string_matcher = policy.add_principals() + ->mutable_authenticated() + ->mutable_principal_name(); + string_matcher->set_exact("exact_match"); + // source IP + auto* cidr_range = policy.add_principals()->mutable_source_ip(); + cidr_range->set_address_prefix("127.0.0"); + // direct remote IP + cidr_range = policy.add_principals()->mutable_direct_remote_ip(); + cidr_range->set_address_prefix("127.0.1"); + // remote IP + cidr_range = policy.add_principals()->mutable_remote_ip(); + cidr_range->set_address_prefix("127.0.2"); + // header match + // (not testing all possible header matchers here, since they're + // covered in the AllPermissionTypes test above) + auto* header = policy.add_principals()->mutable_header(); + header->set_name("header_name1"); + header->set_exact_match("exact_match"); + // path match + // (not testing all possible string matchers here, since they're + // covered in the AllPermissionTypes test above) + string_matcher = policy.add_principals()->mutable_url_path()->mutable_path(); + string_matcher->set_exact("exact_match"); + // metadata match + // (not testing invert here, since it's covered in the AllPermissionTypes + // test above) + policy.add_principals()->mutable_metadata(); + // not + policy.add_principals()->mutable_not_id()->set_any(true); + // and + policy.add_principals()->mutable_and_ids()->add_ids()->set_any(true); + // or + policy.add_principals()->mutable_or_ids()->add_ids()->set_any(true); + auto config = GenerateConfig(rbac); + ASSERT_TRUE(errors_.ok()) << errors_.status("unexpected errors"); + ASSERT_TRUE(config.has_value()); + EXPECT_EQ(config->config_proto_type_name, + GetParam() ? filter_->OverrideConfigProtoName() + : filter_->ConfigProtoName()); + EXPECT_EQ(config->config.Dump(), + "{\"rules\":{" + "\"action\":0," + "\"policies\":{" + "\"policy_name\":{" + "\"permissions\":[]," + "\"principals\":[" + // any + "{\"any\":true}," + // authenticated principal name + "{\"authenticated\":{\"principalName\":{" + "\"exact\":\"exact_match\",\"ignoreCase\":false}}}," + // source IP + "{\"sourceIp\":{\"addressPrefix\":\"127.0.0\"}}," + // direct remote IP + "{\"directRemoteIp\":{\"addressPrefix\":\"127.0.1\"}}," + // remote IP + "{\"remoteIp\":{\"addressPrefix\":\"127.0.2\"}}," + // header exact match with invert + "{\"header\":" + "{\"exactMatch\":\"exact_match\",\"invertMatch\":false," + "\"name\":\"header_name1\"}}," + // path exact match + "{\"urlPath\":{\"path\":{" + "\"exact\":\"exact_match\",\"ignoreCase\":false}}}," + // metadata match + "{\"metadata\":{\"invert\":false}}," + // not + "{\"notId\":{\"any\":true}}," + // and + "{\"andIds\":{\"ids\":[{\"any\":true}]}}," + // or + "{\"orIds\":{\"ids\":[{\"any\":true}]}}" + "]" + "}}}}"); +} + +TEST_P(XdsRbacFilterConfigTest, InvalidFieldsInPolicy) { + RBAC rbac; + auto* rules = rbac.mutable_rules(); + rules->set_action(rules->ALLOW); + auto& policy = (*rules->mutable_policies())["policy_name"]; + policy.mutable_condition(); + policy.mutable_checked_condition(); + auto config = GenerateConfig(rbac); + absl::Status status = errors_.status("errors validating filter config"); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_EQ(status.message(), + absl::StrCat("errors validating filter config: [" + "field:", + FieldPrefix(), + ".rules.policies[policy_name].checked_condition " + "error:checked condition not supported; " + "field:", + FieldPrefix(), + ".rules.policies[policy_name].condition " + "error:condition not supported]")) + << status; +} + +TEST_P(XdsRbacFilterConfigTest, InvalidHeaderMatchers) { + RBAC rbac; + auto* rules = rbac.mutable_rules(); + rules->set_action(rules->ALLOW); + auto& policy = (*rules->mutable_policies())["policy_name"]; + auto* header = policy.add_permissions()->mutable_header(); + header->set_name(":scheme"); + header->set_exact_match("exact_match"); + header = policy.add_principals()->mutable_header(); + header->set_name("grpc-foo"); + header->set_exact_match("exact_match"); + header = policy.add_principals()->mutable_header(); + header->set_name("header_name"); + auto config = GenerateConfig(rbac); + absl::Status status = errors_.status("errors validating filter config"); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_EQ( + status.message(), + absl::StrCat("errors validating filter config: [" + "field:", + FieldPrefix(), + ".rules.policies[policy_name].permissions[0].header.name " + "error:':scheme' not allowed in header; " + "field:", + FieldPrefix(), + ".rules.policies[policy_name].principals[0].header.name " + "error:'grpc-' prefixes not allowed in header; " + "field:", + FieldPrefix(), + ".rules.policies[policy_name].principals[1].header " + "error:invalid route header matcher specified]")) + << status; +} + +TEST_P(XdsRbacFilterConfigTest, InvalidStringMatchers) { + RBAC rbac; + auto* rules = rbac.mutable_rules(); + rules->set_action(rules->ALLOW); + auto& policy = (*rules->mutable_policies())["policy_name"]; + policy.add_permissions()->mutable_url_path()->mutable_path(); + policy.add_principals()->mutable_url_path(); + auto config = GenerateConfig(rbac); + absl::Status status = errors_.status("errors validating filter config"); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_EQ( + status.message(), + absl::StrCat("errors validating filter config: [" + "field:", + FieldPrefix(), + ".rules.policies[policy_name].permissions[0].url_path.path " + "error:invalid match pattern; " + "field:", + FieldPrefix(), + ".rules.policies[policy_name].principals[0].url_path.path " + "error:field not present]")) + << status; +} + +TEST_P(XdsRbacFilterConfigTest, InvalidPermissionAndPrincipal) { + RBAC rbac; + auto* rules = rbac.mutable_rules(); + rules->set_action(rules->ALLOW); + auto& policy = (*rules->mutable_policies())["policy_name"]; + policy.add_permissions(); + policy.add_principals(); + auto config = GenerateConfig(rbac); + absl::Status status = errors_.status("errors validating filter config"); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_EQ(status.message(), + absl::StrCat("errors validating filter config: [" + "field:", + FieldPrefix(), + ".rules.policies[policy_name].permissions[0] " + "error:invalid rule; " + "field:", + FieldPrefix(), + ".rules.policies[policy_name].principals[0] " + "error:invalid rule]")) + << status; +} + +} // namespace +} // namespace testing +} // namespace grpc_core + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + grpc::testing::TestEnvironment env(&argc, argv); + grpc_init(); + auto result = RUN_ALL_TESTS(); + grpc_shutdown(); + return result; +} diff --git a/test/core/xds/xds_listener_resource_type_test.cc b/test/core/xds/xds_listener_resource_type_test.cc new file mode 100644 index 00000000000..ce8206f1fb0 --- /dev/null +++ b/test/core/xds/xds_listener_resource_type_test.cc @@ -0,0 +1,1980 @@ +// +// Copyright 2022 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 +#include +#include + +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "absl/types/variant.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "upb/def.hpp" +#include "upb/upb.hpp" + +#include +#include + +#include "src/core/ext/xds/xds_bootstrap.h" +#include "src/core/ext/xds/xds_bootstrap_grpc.h" +#include "src/core/ext/xds/xds_client.h" +#include "src/core/ext/xds/xds_common_types.h" +#include "src/core/ext/xds/xds_http_filters.h" +#include "src/core/ext/xds/xds_listener.h" +#include "src/core/ext/xds/xds_resource_type.h" +#include "src/core/lib/address_utils/sockaddr_utils.h" +#include "src/core/lib/debug/trace.h" +#include "src/core/lib/gprpp/ref_counted_ptr.h" +#include "src/core/lib/gprpp/time.h" +#include "src/core/lib/iomgr/error.h" +#include "src/core/lib/json/json.h" +#include "src/proto/grpc/testing/xds/v3/address.pb.h" +#include "src/proto/grpc/testing/xds/v3/base.pb.h" +#include "src/proto/grpc/testing/xds/v3/config_source.pb.h" +#include "src/proto/grpc/testing/xds/v3/fault.pb.h" +#include "src/proto/grpc/testing/xds/v3/http_connection_manager.pb.h" +#include "src/proto/grpc/testing/xds/v3/http_filter_rbac.pb.h" +#include "src/proto/grpc/testing/xds/v3/listener.pb.h" +#include "src/proto/grpc/testing/xds/v3/protocol.pb.h" +#include "src/proto/grpc/testing/xds/v3/router.pb.h" +#include "src/proto/grpc/testing/xds/v3/string.pb.h" +#include "src/proto/grpc/testing/xds/v3/tls.pb.h" +#include "src/proto/grpc/testing/xds/v3/typed_struct.pb.h" +#include "test/core/util/test_config.h" + +using envoy::config::listener::v3::Listener; +using envoy::extensions::filters::http::fault::v3::HTTPFault; +using envoy::extensions::filters::http::rbac::v3::RBAC; +using envoy::extensions::filters::http::router::v3::Router; +using envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager; +using envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext; + +namespace grpc_core { +namespace testing { +namespace { + +TraceFlag xds_listener_resource_type_test_trace( + true, "xds_listener_resource_type_test"); + +class XdsListenerTest : public ::testing::Test { + protected: + XdsListenerTest() + : xds_client_(MakeXdsClient()), + decode_context_{xds_client_.get(), xds_client_->bootstrap().server(), + &xds_listener_resource_type_test_trace, + upb_def_pool_.ptr(), upb_arena_.ptr()} {} + + static RefCountedPtr MakeXdsClient() { + grpc_error_handle error; + auto bootstrap = GrpcXdsBootstrap::Create( + "{\n" + " \"xds_servers\": [\n" + " {\n" + " \"server_uri\": \"xds.example.com\",\n" + " \"channel_creds\": [\n" + " {\"type\": \"google_default\"}\n" + " ]\n" + " }\n" + " ],\n" + " \"certificate_providers\": {\n" + " \"provider1\": {\n" + " \"plugin_name\": \"file_watcher\",\n" + " \"config\": {\n" + " \"certificate_file\": \"/path/to/cert\",\n" + " \"private_key_file\": \"/path/to/key\"\n" + " }\n" + " }\n" + " }\n" + "}"); + if (!bootstrap.ok()) { + gpr_log(GPR_ERROR, "Error parsing bootstrap: %s", + bootstrap.status().ToString().c_str()); + GPR_ASSERT(false); + } + return MakeRefCounted(std::move(*bootstrap), + /*transport_factory=*/nullptr); + } + + RefCountedPtr xds_client_; + upb::DefPool upb_def_pool_; + upb::Arena upb_arena_; + XdsResourceType::DecodeContext decode_context_; +}; + +TEST_F(XdsListenerTest, Definition) { + auto* resource_type = XdsListenerResourceType::Get(); + ASSERT_NE(resource_type, nullptr); + EXPECT_EQ(resource_type->type_url(), "envoy.config.listener.v3.Listener"); + EXPECT_TRUE(resource_type->AllResourcesRequiredInSotW()); +} + +TEST_F(XdsListenerTest, UnparseableProto) { + std::string serialized_resource("\0", 1); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "Can't parse Listener resource.") + << decode_result.resource.status(); +} + +TEST_F(XdsListenerTest, NeitherAddressNotApiListener) { + Listener listener; + listener.set_name("foo"); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "Listener has neither address nor ApiListener") + << decode_result.resource.status(); +} + +// TODO(roth): Re-enable the following test once +// github.com/istio/istio/issues/38914 is resolved. +TEST_F(XdsListenerTest, DISABLED_BothAddressAndApiListener) { + Listener listener; + listener.set_name("foo"); + listener.mutable_api_listener(); + listener.mutable_address(); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "Listener has both address and ApiListener") + << decode_result.resource.status(); +} + +// +// HttpConnectionManager tests +// + +struct HttpConnectionManagerLocation { + bool in_api_listener = false; + + explicit HttpConnectionManagerLocation(bool in_api_listener) + : in_api_listener(in_api_listener) {} + + // For use as the final parameter in INSTANTIATE_TEST_SUITE_P(). + static std::string Name( + const ::testing::TestParamInfo& info) { + return info.param.in_api_listener ? "ApiListener" : "TcpListener"; + } +}; + +// These tests cover common behavior for both API listeners and TCP +// listeners, so we run them in both contexts. +class HttpConnectionManagerTest + : public XdsListenerTest, + public ::testing::WithParamInterface { + protected: + static Listener MakeListener(HttpConnectionManager hcm) { + Listener listener; + listener.set_name("foo"); + if (GetParam().in_api_listener) { + // Client. + listener.mutable_api_listener()->mutable_api_listener()->PackFrom(hcm); + } else { + // Server. + listener.mutable_default_filter_chain() + ->add_filters() + ->mutable_typed_config() + ->PackFrom(hcm); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + } + return listener; + } + + static absl::optional + GetHCMConfig(const XdsListenerResource& resource) { + if (GetParam().in_api_listener) { + // Client. + auto* hcm = absl::get_if( + &resource.listener); + if (hcm == nullptr) return absl::nullopt; + return *hcm; + } + // Server. + auto* tcp_listener = + absl::get_if(&resource.listener); + if (tcp_listener == nullptr) return absl::nullopt; + if (!tcp_listener->default_filter_chain.has_value()) return absl::nullopt; + return tcp_listener->default_filter_chain->http_connection_manager; + } + + static absl::string_view ErrorPrefix() { + // Client. + if (GetParam().in_api_listener) return "errors validating ApiListener: "; + // Server. + return "errors validating server Listener: "; + } + + static absl::string_view FieldPrefix() { + // Client. + if (GetParam().in_api_listener) return "api_listener.api_listener"; + // Server. + return "default_filter_chain.filters[0].typed_config"; + } +}; + +INSTANTIATE_TEST_SUITE_P( + XdsHcm, HttpConnectionManagerTest, + ::testing::Values(HttpConnectionManagerLocation(true), + HttpConnectionManagerLocation(false)), + &HttpConnectionManagerLocation::Name); + +TEST_P(HttpConnectionManagerTest, MinimumValidConfig) { + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast(**decode_result.resource); + auto http_connection_manager = GetHCMConfig(resource); + ASSERT_TRUE(http_connection_manager.has_value()); + auto* rds_name = + absl::get_if(&http_connection_manager->route_config); + ASSERT_NE(rds_name, nullptr); + EXPECT_EQ(*rds_name, "rds_name"); + ASSERT_EQ(http_connection_manager->http_filters.size(), 1UL); + auto& router = http_connection_manager->http_filters[0]; + EXPECT_EQ(router.name, "router"); + EXPECT_EQ(router.config.config_proto_type_name, + "envoy.extensions.filters.http.router.v3.Router"); + EXPECT_EQ(router.config.config, Json()) << router.config.config.Dump(); + EXPECT_EQ(http_connection_manager->http_max_stream_duration, + Duration::Zero()); +} + +TEST_P(HttpConnectionManagerTest, RdsConfigSourceUsesAds) { + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_ads(); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast(**decode_result.resource); + auto http_connection_manager = GetHCMConfig(resource); + ASSERT_TRUE(http_connection_manager.has_value()); + auto* rds_name = + absl::get_if(&http_connection_manager->route_config); + ASSERT_NE(rds_name, nullptr); + EXPECT_EQ(*rds_name, "rds_name"); + ASSERT_EQ(http_connection_manager->http_filters.size(), 1UL); + auto& router = http_connection_manager->http_filters[0]; + EXPECT_EQ(router.name, "router"); + EXPECT_EQ(router.config.config_proto_type_name, + "envoy.extensions.filters.http.router.v3.Router"); + EXPECT_EQ(router.config.config, Json()) << router.config.config.Dump(); + EXPECT_EQ(http_connection_manager->http_max_stream_duration, + Duration::Zero()); +} + +TEST_P(HttpConnectionManagerTest, NeitherRouteConfigNorRdsName) { + HttpConnectionManager hcm; + hcm.mutable_scoped_routes(); + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ( + decode_result.resource.status().message(), + absl::StrCat(ErrorPrefix(), "[field:", FieldPrefix(), + ".value[" + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager] " + "error:neither route_config nor rds fields are present]")) + << decode_result.resource.status(); +} + +TEST_P(HttpConnectionManagerTest, RdsConfigSourceNotAdsOrSelf) { + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->set_path("/foo/bar"); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ( + decode_result.resource.status().message(), + absl::StrCat(ErrorPrefix(), "[field:", FieldPrefix(), + ".value[" + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager].rds.config_source " + "error:ConfigSource does not specify ADS or SELF]")) + << decode_result.resource.status(); +} + +TEST_P(HttpConnectionManagerTest, RdsConfigSourceNotSet) { + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ( + decode_result.resource.status().message(), + absl::StrCat(ErrorPrefix(), "[field:", FieldPrefix(), + ".value[" + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager].rds.config_source " + "error:field not present]")) + << decode_result.resource.status(); +} + +TEST_P(HttpConnectionManagerTest, SetsMaxStreamDuration) { + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* duration = + hcm.mutable_common_http_protocol_options()->mutable_max_stream_duration(); + duration->set_seconds(5); + duration->set_nanos(5000000); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast(**decode_result.resource); + auto http_connection_manager = GetHCMConfig(resource); + ASSERT_TRUE(http_connection_manager.has_value()); + auto* rds_name = + absl::get_if(&http_connection_manager->route_config); + ASSERT_NE(rds_name, nullptr); + EXPECT_EQ(*rds_name, "rds_name"); + ASSERT_EQ(http_connection_manager->http_filters.size(), 1UL); + auto& router = http_connection_manager->http_filters[0]; + EXPECT_EQ(router.name, "router"); + EXPECT_EQ(router.config.config_proto_type_name, + "envoy.extensions.filters.http.router.v3.Router"); + EXPECT_EQ(router.config.config, Json()) << router.config.config.Dump(); + EXPECT_EQ(http_connection_manager->http_max_stream_duration, + Duration::Milliseconds(5005)); +} + +TEST_P(HttpConnectionManagerTest, InvalidMaxStreamDuration) { + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + hcm.mutable_common_http_protocol_options() + ->mutable_max_stream_duration() + ->set_seconds(-1); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ( + decode_result.resource.status().message(), + absl::StrCat(ErrorPrefix(), "[field:", FieldPrefix(), + ".value[" + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager].common_http_protocol_options" + ".max_stream_duration.seconds " + "error:value must be in the range [0, 315576000000]]")) + << decode_result.resource.status(); +} + +TEST_P(HttpConnectionManagerTest, UnsupportedFieldsSet) { + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + hcm.set_xff_num_trusted_hops(1); + hcm.add_original_ip_detection_extensions(); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ( + decode_result.resource.status().message(), + absl::StrCat(ErrorPrefix(), "[field:", FieldPrefix(), + ".value[" + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager].original_ip_detection_extensions " + "error:must be empty; field:", + FieldPrefix(), + ".value[" + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager].xff_num_trusted_hops " + "error:must be zero]")) + << decode_result.resource.status(); +} + +TEST_P(HttpConnectionManagerTest, EmptyHttpFilterName) { + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ( + decode_result.resource.status().message(), + absl::StrCat(ErrorPrefix(), "[field:", FieldPrefix(), + ".value[" + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager].http_filters[0].name " + "error:empty filter name]")) + << decode_result.resource.status(); +} + +TEST_P(HttpConnectionManagerTest, DuplicateHttpFilterName) { + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + *hcm.add_http_filters() = hcm.http_filters(0); // Copy filter. + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ( + decode_result.resource.status().message(), + absl::StrCat(ErrorPrefix(), "[field:", FieldPrefix(), + ".value[" + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager].http_filters[1].name " + "error:duplicate HTTP filter name: router]")) + << decode_result.resource.status(); +} + +TEST_P(HttpConnectionManagerTest, HttpFilterMissingConfig) { + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ( + decode_result.resource.status().message(), + absl::StrCat(ErrorPrefix(), "[field:", FieldPrefix(), + ".value[" + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager].http_filters[0].typed_config " + "error:field not present]")) + << decode_result.resource.status(); +} + +TEST_P(HttpConnectionManagerTest, HttpFilterMissingConfigButOptional) { + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("foo"); + filter->set_is_optional(true); + filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast(**decode_result.resource); + auto http_connection_manager = GetHCMConfig(resource); + ASSERT_TRUE(http_connection_manager.has_value()); + ASSERT_EQ(http_connection_manager->http_filters.size(), 1UL); + auto& router = http_connection_manager->http_filters[0]; + EXPECT_EQ(router.name, "router"); + EXPECT_EQ(router.config.config_proto_type_name, + "envoy.extensions.filters.http.router.v3.Router"); + EXPECT_EQ(router.config.config, Json()) << router.config.config.Dump(); +} + +TEST_P(HttpConnectionManagerTest, HttpFilterTypeNotSupported) { + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Listener()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ( + decode_result.resource.status().message(), + absl::StrCat(ErrorPrefix(), "[field:", FieldPrefix(), + ".value[" + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager].http_filters[0].typed_config.value[" + "envoy.config.listener.v3.Listener] " + "error:unsupported filter type]")) + << decode_result.resource.status(); +} + +TEST_P(HttpConnectionManagerTest, HttpFilterTypeNotSupportedButOptional) { + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("unsupported"); + filter->mutable_typed_config()->PackFrom(Listener()); + filter->set_is_optional(true); + filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast(**decode_result.resource); + auto http_connection_manager = GetHCMConfig(resource); + ASSERT_TRUE(http_connection_manager.has_value()); + ASSERT_EQ(http_connection_manager->http_filters.size(), 1UL); + auto& router = http_connection_manager->http_filters[0]; + EXPECT_EQ(router.name, "router"); + EXPECT_EQ(router.config.config_proto_type_name, + "envoy.extensions.filters.http.router.v3.Router"); + EXPECT_EQ(router.config.config, Json()) << router.config.config.Dump(); +} + +TEST_P(HttpConnectionManagerTest, NoHttpFilters) { + HttpConnectionManager hcm; + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ( + decode_result.resource.status().message(), + absl::StrCat(ErrorPrefix(), "[field:", FieldPrefix(), + ".value[" + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager].http_filters " + "error:expected at least one HTTP filter]")) + << decode_result.resource.status(); +} + +TEST_P(HttpConnectionManagerTest, TerminalFilterNotLast) { + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + filter = hcm.add_http_filters(); + if (GetParam().in_api_listener) { + // Client. + filter->set_name("fault"); + filter->mutable_typed_config()->PackFrom(HTTPFault()); + } else { + // Server. + filter->set_name("rbac"); + filter->mutable_typed_config()->PackFrom(RBAC()); + } + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + Listener listener = MakeListener(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ( + decode_result.resource.status().message(), + absl::StrCat(ErrorPrefix(), "[field:", FieldPrefix(), + ".value[" + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager].http_filters errors:[" + "terminal filter for config type " + "envoy.extensions.filters.http.router.v3.Router must be the " + "last filter in the chain; " + "non-terminal filter for config type ", + (GetParam().in_api_listener + ? "envoy.extensions.filters.http.fault.v3.HTTPFault" + : "envoy.extensions.filters.http.rbac.v3.RBAC"), + " is the last filter in the chain]]")) + << decode_result.resource.status(); +} + +using HttpConnectionManagerClientOrServerOnlyTest = XdsListenerTest; + +TEST_F(HttpConnectionManagerClientOrServerOnlyTest, + HttpFilterNotSupportedOnClient) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("rbac"); + filter->mutable_typed_config()->PackFrom( + envoy::extensions::filters::http::rbac::v3::RBAC()); + filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + listener.mutable_api_listener()->mutable_api_listener()->PackFrom(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating ApiListener: [" + "field:api_listener.api_listener.value[" + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager].http_filters[0].typed_config.value[" + "envoy.extensions.filters.http.rbac.v3.RBAC] " + "error:filter is not supported on clients]") + << decode_result.resource.status(); +} + +TEST_F(HttpConnectionManagerClientOrServerOnlyTest, + HttpFilterNotSupportedOnClientButOptional) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("rbac"); + filter->mutable_typed_config()->PackFrom( + envoy::extensions::filters::http::rbac::v3::RBAC()); + filter->set_is_optional(true); + filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + listener.mutable_api_listener()->mutable_api_listener()->PackFrom(hcm); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast(**decode_result.resource); + auto* api_listener = absl::get_if( + &resource.listener); + ASSERT_NE(api_listener, nullptr); + ASSERT_EQ(api_listener->http_filters.size(), 1UL); + auto& router = api_listener->http_filters[0]; + EXPECT_EQ(router.name, "router"); + EXPECT_EQ(router.config.config_proto_type_name, + "envoy.extensions.filters.http.router.v3.Router"); + EXPECT_EQ(router.config.config, Json()) << router.config.config.Dump(); +} + +TEST_F(HttpConnectionManagerClientOrServerOnlyTest, + HttpFilterNotSupportedOnServer) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("fault"); + filter->mutable_typed_config()->PackFrom( + envoy::extensions::filters::http::fault::v3::HTTPFault()); + filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + listener.mutable_default_filter_chain() + ->add_filters() + ->mutable_typed_config() + ->PackFrom(hcm); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [" + "field:default_filter_chain.filters[0].typed_config.value[" + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager].http_filters[0].typed_config.value[" + "envoy.extensions.filters.http.fault.v3.HTTPFault] " + "error:filter is not supported on servers]") + << decode_result.resource.status(); +} + +TEST_F(HttpConnectionManagerClientOrServerOnlyTest, + HttpFilterNotSupportedOnServerButOptional) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("fault"); + filter->mutable_typed_config()->PackFrom( + envoy::extensions::filters::http::fault::v3::HTTPFault()); + filter->set_is_optional(true); + filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + listener.mutable_default_filter_chain() + ->add_filters() + ->mutable_typed_config() + ->PackFrom(hcm); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast(**decode_result.resource); + auto* tcp_listener = + absl::get_if(&resource.listener); + ASSERT_NE(tcp_listener, nullptr); + ASSERT_TRUE(tcp_listener->default_filter_chain.has_value()); + const auto& http_connection_manager = + tcp_listener->default_filter_chain->http_connection_manager; + ASSERT_EQ(http_connection_manager.http_filters.size(), 1UL); + auto& router = http_connection_manager.http_filters[0]; + EXPECT_EQ(router.name, "router"); + EXPECT_EQ(router.config.config_proto_type_name, + "envoy.extensions.filters.http.router.v3.Router"); + EXPECT_EQ(router.config.config, Json()) << router.config.config.Dump(); +} + +// +// API listener tests +// + +using ApiListenerTest = XdsListenerTest; + +TEST_F(ApiListenerTest, InnerApiListenerNotSet) { + Listener listener; + listener.set_name("foo"); + listener.mutable_api_listener(); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating ApiListener: [" + "field:api_listener.api_listener error:field not present]") + << decode_result.resource.status(); +} + +TEST_F(ApiListenerTest, DoesNotContainHttpConnectionManager) { + Listener listener; + listener.set_name("foo"); + listener.mutable_api_listener()->mutable_api_listener()->PackFrom(Listener()); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating ApiListener: [" + "field:api_listener.api_listener.value[" + "envoy.config.listener.v3.Listener] " + "error:unsupported filter type]") + << decode_result.resource.status(); +} + +TEST_F(ApiListenerTest, UnparseableHttpConnectionManagerConfig) { + Listener listener; + listener.set_name("foo"); + auto* any = listener.mutable_api_listener()->mutable_api_listener(); + any->PackFrom(HttpConnectionManager()); + any->set_value(std::string("\0", 1)); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating ApiListener: [" + "field:api_listener.api_listener.value[" + "envoy.extensions.filters.network.http_connection_manager.v3" + ".HttpConnectionManager] " + "error:could not parse HttpConnectionManager config]") + << decode_result.resource.status(); +} + +// +// TCP listener tests +// + +using TcpListenerTest = XdsListenerTest; + +TEST_F(TcpListenerTest, MinimumValidConfig) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + listener.mutable_default_filter_chain() + ->add_filters() + ->mutable_typed_config() + ->PackFrom(hcm); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast(**decode_result.resource); + auto* tcp_listener = + absl::get_if(&resource.listener); + ASSERT_NE(tcp_listener, nullptr); + EXPECT_EQ(tcp_listener->address, "127.0.0.1:443"); + EXPECT_THAT(tcp_listener->filter_chain_map.destination_ip_vector, + ::testing::ElementsAre()); + ASSERT_TRUE(tcp_listener->default_filter_chain.has_value()); + EXPECT_TRUE( + tcp_listener->default_filter_chain->downstream_tls_context.Empty()); + const auto& http_connection_manager = + tcp_listener->default_filter_chain->http_connection_manager; + auto* rds_name = + absl::get_if(&http_connection_manager.route_config); + ASSERT_NE(rds_name, nullptr); + EXPECT_EQ(*rds_name, "rds_name"); + ASSERT_EQ(http_connection_manager.http_filters.size(), 1UL); + auto& router = http_connection_manager.http_filters[0]; + EXPECT_EQ(router.name, "router"); + EXPECT_EQ(router.config.config_proto_type_name, + "envoy.extensions.filters.http.router.v3.Router"); + EXPECT_EQ(router.config.config, Json()) << router.config.config.Dump(); + EXPECT_EQ(http_connection_manager.http_max_stream_duration, Duration::Zero()); +} + +// TODO(yashkt): Add tests for all interesting combinations of filter +// chain match criteria. +TEST_F(TcpListenerTest, FilterChainMatchCriteria) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.add_filter_chains(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* match = filter_chain->mutable_filter_chain_match(); + auto* cidr_range = match->add_prefix_ranges(); + cidr_range->set_address_prefix("1.2.3.4"); + cidr_range->mutable_prefix_len()->set_value(24); + cidr_range = match->add_source_prefix_ranges(); + cidr_range->set_address_prefix("5.6.7.8"); + cidr_range->mutable_prefix_len()->set_value(16); + match->add_source_ports(1025); + match->set_transport_protocol("raw_buffer"); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast(**decode_result.resource); + auto* tcp_listener = + absl::get_if(&resource.listener); + ASSERT_NE(tcp_listener, nullptr); + EXPECT_EQ(tcp_listener->address, "127.0.0.1:443"); + EXPECT_FALSE(tcp_listener->default_filter_chain.has_value()); + ASSERT_EQ(tcp_listener->filter_chain_map.destination_ip_vector.size(), 1UL); + auto& dest_ip = tcp_listener->filter_chain_map.destination_ip_vector.front(); + ASSERT_TRUE(dest_ip.prefix_range.has_value()); + auto addr = grpc_sockaddr_to_string(&dest_ip.prefix_range->address, false); + ASSERT_TRUE(addr.ok()) << addr.status(); + EXPECT_EQ(*addr, "1.2.3.0:0"); + EXPECT_EQ(dest_ip.prefix_range->prefix_len, 24); + ASSERT_EQ(dest_ip.source_types_array.size(), 3UL); + EXPECT_THAT(dest_ip.source_types_array[1], ::testing::ElementsAre()); + EXPECT_THAT(dest_ip.source_types_array[2], ::testing::ElementsAre()); + ASSERT_EQ(dest_ip.source_types_array[0].size(), 1UL); + auto& source_ip = dest_ip.source_types_array[0].front(); + ASSERT_TRUE(source_ip.prefix_range.has_value()); + addr = grpc_sockaddr_to_string(&source_ip.prefix_range->address, false); + ASSERT_TRUE(addr.ok()) << addr.status(); + EXPECT_EQ(*addr, "5.6.0.0:0"); + EXPECT_EQ(source_ip.prefix_range->prefix_len, 16); + ASSERT_EQ(source_ip.ports_map.size(), 1UL); + auto it = source_ip.ports_map.begin(); + EXPECT_EQ(it->first, 1025); + ASSERT_NE(it->second.data, nullptr); + auto& filter_data = *it->second.data; + EXPECT_TRUE(filter_data.downstream_tls_context.Empty()); + const auto& http_connection_manager = filter_data.http_connection_manager; + auto* rds_name = + absl::get_if(&http_connection_manager.route_config); + ASSERT_NE(rds_name, nullptr); + EXPECT_EQ(*rds_name, "rds_name"); + ASSERT_EQ(http_connection_manager.http_filters.size(), 1UL); + auto& router = http_connection_manager.http_filters[0]; + EXPECT_EQ(router.name, "router"); + EXPECT_EQ(router.config.config_proto_type_name, + "envoy.extensions.filters.http.router.v3.Router"); + EXPECT_EQ(router.config.config, Json()) << router.config.config.Dump(); + EXPECT_EQ(http_connection_manager.http_max_stream_duration, Duration::Zero()); +} + +TEST_F(TcpListenerTest, SocketAddressNotPresent) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + listener.mutable_default_filter_chain() + ->add_filters() + ->mutable_typed_config() + ->PackFrom(hcm); + listener.mutable_address(); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [" + "field:address.socket_address error:field not present]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, SocketAddressBadValues) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + listener.mutable_default_filter_chain() + ->add_filters() + ->mutable_typed_config() + ->PackFrom(hcm); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(65536); + address->set_protocol(address->UDP); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [" + "field:address.socket_address.port_value error:invalid port; " + "field:address.socket_address.protocol error:value must be TCP]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, UseOriginalDstNotSupported) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + listener.mutable_default_filter_chain() + ->add_filters() + ->mutable_typed_config() + ->PackFrom(hcm); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + listener.mutable_use_original_dst()->set_value(true); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [" + "field:use_original_dst error:field not supported]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, NoFilterChains) { + Listener listener; + listener.set_name("foo"); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [" + "field:default_filter_chain " + "error:must be set if filter_chains is unset]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, UnsupportedFilter) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + listener.mutable_default_filter_chain() + ->add_filters() + ->mutable_typed_config() + ->PackFrom(Listener()); + listener.mutable_default_filter_chain() + ->add_filters() + ->mutable_typed_config() + ->PackFrom(hcm); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [" + "field:default_filter_chain.filters " + "error:must have exactly one filter (HttpConnectionManager -- " + "no other filter is supported at the moment); " + "field:default_filter_chain.filters[0].typed_config.value[" + "envoy.config.listener.v3.Listener] " + "error:unsupported filter type]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, BadCidrRanges) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.add_filter_chains(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* match = filter_chain->mutable_filter_chain_match(); + auto* cidr_range = match->add_prefix_ranges(); + cidr_range->set_address_prefix("foobar"); + cidr_range->mutable_prefix_len()->set_value(24); + cidr_range = match->add_source_prefix_ranges(); + cidr_range->set_address_prefix("invalid"); + cidr_range->mutable_prefix_len()->set_value(16); + match->add_source_ports(1025); + match->set_transport_protocol("raw_buffer"); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [" + "field:filter_chains[0].filter_chain_match.prefix_ranges[0]" + ".address_prefix error:Failed to parse address:foobar:0; " + "field:filter_chains[0].filter_chain_match.source_prefix_ranges[0]" + ".address_prefix error:Failed to parse address:invalid:0]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, DuplicateMatchOnDestinationPrefixRanges) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.add_filter_chains(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* match = filter_chain->mutable_filter_chain_match(); + auto* cidr_range = match->add_prefix_ranges(); + cidr_range->set_address_prefix("1.2.3.4"); + cidr_range->mutable_prefix_len()->set_value(24); + cidr_range = match->add_prefix_ranges(); + cidr_range->set_address_prefix("1.2.3.4"); + cidr_range->mutable_prefix_len()->set_value(16); + filter_chain = listener.add_filter_chains(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + match = filter_chain->mutable_filter_chain_match(); + cidr_range = match->add_prefix_ranges(); + cidr_range->set_address_prefix("1.2.3.4"); + cidr_range->mutable_prefix_len()->set_value(24); + cidr_range = match->add_prefix_ranges(); + cidr_range->set_address_prefix("1.2.3.4"); + cidr_range->mutable_prefix_len()->set_value(32); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [field:filter_chains " + "error:duplicate matching rules detected when adding filter chain: " + "{prefix_ranges={{address_prefix=1.2.3.0:0, prefix_len=24}, " + "{address_prefix=1.2.3.4:0, prefix_len=32}}}]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, DuplicateMatchOnTransportProtocol) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.add_filter_chains(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + filter_chain->mutable_filter_chain_match()->set_transport_protocol( + "raw_buffer"); + filter_chain = listener.add_filter_chains(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + filter_chain->mutable_filter_chain_match()->set_transport_protocol( + "raw_buffer"); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [field:filter_chains " + "error:duplicate matching rules detected when adding filter chain: " + "{transport_protocol=raw_buffer}]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, DuplicateMatchOnSourceType) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.add_filter_chains(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* match = filter_chain->mutable_filter_chain_match(); + match->set_source_type(match->SAME_IP_OR_LOOPBACK); + filter_chain = listener.add_filter_chains(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + match = filter_chain->mutable_filter_chain_match(); + match->set_source_type(match->SAME_IP_OR_LOOPBACK); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [field:filter_chains " + "error:duplicate matching rules detected when adding filter chain: " + "{source_type=SAME_IP_OR_LOOPBACK}]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, DuplicateMatchOnSourcePrefixRanges) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.add_filter_chains(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* match = filter_chain->mutable_filter_chain_match(); + auto* cidr_range = match->add_source_prefix_ranges(); + cidr_range->set_address_prefix("1.2.3.4"); + cidr_range->mutable_prefix_len()->set_value(24); + cidr_range = match->add_source_prefix_ranges(); + cidr_range->set_address_prefix("1.2.3.4"); + cidr_range->mutable_prefix_len()->set_value(16); + filter_chain = listener.add_filter_chains(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + match = filter_chain->mutable_filter_chain_match(); + cidr_range = match->add_source_prefix_ranges(); + cidr_range->set_address_prefix("1.2.3.4"); + cidr_range->mutable_prefix_len()->set_value(24); + cidr_range = match->add_source_prefix_ranges(); + cidr_range->set_address_prefix("1.2.3.4"); + cidr_range->mutable_prefix_len()->set_value(32); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [field:filter_chains " + "error:duplicate matching rules detected when adding filter chain: " + "{source_prefix_ranges={{address_prefix=1.2.3.0:0, prefix_len=24}, " + "{address_prefix=1.2.3.4:0, prefix_len=32}}}]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, DuplicateMatchOnSourcePort) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.add_filter_chains(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + filter_chain->mutable_filter_chain_match()->add_source_ports(8080); + filter_chain = listener.add_filter_chains(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + filter_chain->mutable_filter_chain_match()->add_source_ports(8080); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [field:filter_chains " + "error:duplicate matching rules detected when adding filter chain: " + "{source_ports={8080}}]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, DownstreamTlsContext) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.mutable_default_filter_chain(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* transport_socket = filter_chain->mutable_transport_socket(); + transport_socket->set_name("envoy.transport_sockets.tls"); + DownstreamTlsContext downstream_tls_context; + auto* common_tls_context = + downstream_tls_context.mutable_common_tls_context(); + auto* cert_provider = + common_tls_context->mutable_tls_certificate_provider_instance(); + cert_provider->set_instance_name("provider1"); + cert_provider->set_certificate_name("cert_name"); + transport_socket->mutable_typed_config()->PackFrom(downstream_tls_context); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast(**decode_result.resource); + auto* tcp_listener = + absl::get_if(&resource.listener); + ASSERT_NE(tcp_listener, nullptr); + EXPECT_EQ(tcp_listener->address, "127.0.0.1:443"); + EXPECT_THAT(tcp_listener->filter_chain_map.destination_ip_vector, + ::testing::ElementsAre()); + ASSERT_TRUE(tcp_listener->default_filter_chain.has_value()); + auto& tls_context = + tcp_listener->default_filter_chain->downstream_tls_context; + EXPECT_FALSE(tls_context.require_client_certificate); + auto& cert_provider_instance = + tls_context.common_tls_context.tls_certificate_provider_instance; + EXPECT_EQ(cert_provider_instance.instance_name, "provider1"); + EXPECT_EQ(cert_provider_instance.certificate_name, "cert_name"); + EXPECT_TRUE( + tls_context.common_tls_context.certificate_validation_context.Empty()); +} + +TEST_F(TcpListenerTest, DownstreamTlsContextWithCaCertProviderInstance) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.mutable_default_filter_chain(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* transport_socket = filter_chain->mutable_transport_socket(); + transport_socket->set_name("envoy.transport_sockets.tls"); + DownstreamTlsContext downstream_tls_context; + auto* common_tls_context = + downstream_tls_context.mutable_common_tls_context(); + auto* cert_provider = + common_tls_context->mutable_tls_certificate_provider_instance(); + cert_provider->set_instance_name("provider1"); + cert_provider->set_certificate_name("cert_name"); + cert_provider = common_tls_context->mutable_validation_context() + ->mutable_ca_certificate_provider_instance(); + cert_provider->set_instance_name("provider1"); + cert_provider->set_certificate_name("ca_cert_name"); + transport_socket->mutable_typed_config()->PackFrom(downstream_tls_context); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast(**decode_result.resource); + auto* tcp_listener = + absl::get_if(&resource.listener); + ASSERT_NE(tcp_listener, nullptr); + EXPECT_EQ(tcp_listener->address, "127.0.0.1:443"); + EXPECT_THAT(tcp_listener->filter_chain_map.destination_ip_vector, + ::testing::ElementsAre()); + ASSERT_TRUE(tcp_listener->default_filter_chain.has_value()); + auto& tls_context = + tcp_listener->default_filter_chain->downstream_tls_context; + EXPECT_FALSE(tls_context.require_client_certificate); + auto& cert_provider_instance = + tls_context.common_tls_context.tls_certificate_provider_instance; + EXPECT_EQ(cert_provider_instance.instance_name, "provider1"); + EXPECT_EQ(cert_provider_instance.certificate_name, "cert_name"); + auto& ca_cert_provider_instance = + tls_context.common_tls_context.certificate_validation_context + .ca_certificate_provider_instance; + EXPECT_EQ(ca_cert_provider_instance.instance_name, "provider1"); + EXPECT_EQ(ca_cert_provider_instance.certificate_name, "ca_cert_name"); +} + +TEST_F(TcpListenerTest, ClientCertificateRequired) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.mutable_default_filter_chain(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* transport_socket = filter_chain->mutable_transport_socket(); + transport_socket->set_name("envoy.transport_sockets.tls"); + DownstreamTlsContext downstream_tls_context; + downstream_tls_context.mutable_require_client_certificate()->set_value(true); + auto* common_tls_context = + downstream_tls_context.mutable_common_tls_context(); + auto* cert_provider = + common_tls_context->mutable_tls_certificate_provider_instance(); + cert_provider->set_instance_name("provider1"); + cert_provider->set_certificate_name("cert_name"); + cert_provider = common_tls_context->mutable_validation_context() + ->mutable_ca_certificate_provider_instance(); + cert_provider->set_instance_name("provider1"); + cert_provider->set_certificate_name("ca_cert_name"); + transport_socket->mutable_typed_config()->PackFrom(downstream_tls_context); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast(**decode_result.resource); + auto* tcp_listener = + absl::get_if(&resource.listener); + ASSERT_NE(tcp_listener, nullptr); + EXPECT_EQ(tcp_listener->address, "127.0.0.1:443"); + EXPECT_THAT(tcp_listener->filter_chain_map.destination_ip_vector, + ::testing::ElementsAre()); + ASSERT_TRUE(tcp_listener->default_filter_chain.has_value()); + auto& tls_context = + tcp_listener->default_filter_chain->downstream_tls_context; + EXPECT_TRUE(tls_context.require_client_certificate); + auto& cert_provider_instance = + tls_context.common_tls_context.tls_certificate_provider_instance; + EXPECT_EQ(cert_provider_instance.instance_name, "provider1"); + EXPECT_EQ(cert_provider_instance.certificate_name, "cert_name"); + auto& ca_cert_provider_instance = + tls_context.common_tls_context.certificate_validation_context + .ca_certificate_provider_instance; + EXPECT_EQ(ca_cert_provider_instance.instance_name, "provider1"); + EXPECT_EQ(ca_cert_provider_instance.certificate_name, "ca_cert_name"); +} + +// This is just one example of where CommonTlsContext::Parse() will +// generate an error, to show that we're propagating any such errors +// correctly. An exhaustive set of tests for CommonTlsContext::Parse() +// is in xds_common_types_test.cc. +TEST_F(TcpListenerTest, UnknownCertificateProviderInstance) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.mutable_default_filter_chain(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* transport_socket = filter_chain->mutable_transport_socket(); + transport_socket->set_name("envoy.transport_sockets.tls"); + DownstreamTlsContext downstream_tls_context; + auto* common_tls_context = + downstream_tls_context.mutable_common_tls_context(); + auto* cert_provider = + common_tls_context->mutable_tls_certificate_provider_instance(); + cert_provider->set_instance_name("fake"); + cert_provider->set_certificate_name("cert_name"); + transport_socket->mutable_typed_config()->PackFrom(downstream_tls_context); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [" + "field:default_filter_chain.transport_socket.typed_config.value[" + "envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext]" + ".common_tls_context.tls_certificate_provider_instance" + ".instance_name " + "error:unrecognized certificate provider instance name: fake]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, UnknownTransportSocketType) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.mutable_default_filter_chain(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* transport_socket = filter_chain->mutable_transport_socket(); + transport_socket->set_name("envoy.transport_sockets.tls"); + transport_socket->mutable_typed_config()->PackFrom(Listener()); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [" + "field:default_filter_chain.transport_socket.typed_config.value[" + "envoy.config.listener.v3.Listener].type_url " + "error:unsupported transport socket type]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, UnparseableDownstreamTlsContext) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.mutable_default_filter_chain(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* transport_socket = filter_chain->mutable_transport_socket(); + transport_socket->set_name("envoy.transport_sockets.tls"); + auto* typed_config = transport_socket->mutable_typed_config(); + typed_config->PackFrom(DownstreamTlsContext()); + typed_config->set_value(std::string("\0", 1)); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [" + "field:default_filter_chain.transport_socket.typed_config.value[" + "envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext] " + "error:can't decode DownstreamTlsContext]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, DownstreamTlsContextInTypedStruct) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.mutable_default_filter_chain(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* transport_socket = filter_chain->mutable_transport_socket(); + transport_socket->set_name("envoy.transport_sockets.tls"); + xds::type::v3::TypedStruct typed_struct; + typed_struct.set_type_url( + "types.googleapis.com/" + "envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext"); + transport_socket->mutable_typed_config()->PackFrom(typed_struct); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [" + "field:default_filter_chain.transport_socket.typed_config.value[" + "xds.type.v3.TypedStruct].value[" + "envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext] " + "error:can't decode DownstreamTlsContext]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, MatchSubjectAltNames) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.mutable_default_filter_chain(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* transport_socket = filter_chain->mutable_transport_socket(); + transport_socket->set_name("envoy.transport_sockets.tls"); + DownstreamTlsContext downstream_tls_context; + auto* common_tls_context = + downstream_tls_context.mutable_common_tls_context(); + auto* cert_provider = + common_tls_context->mutable_tls_certificate_provider_instance(); + cert_provider->set_instance_name("provider1"); + cert_provider->set_certificate_name("cert_name"); + common_tls_context->mutable_validation_context() + ->add_match_subject_alt_names() + ->set_exact("exact"); + transport_socket->mutable_typed_config()->PackFrom(downstream_tls_context); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [" + "field:default_filter_chain.transport_socket.typed_config.value[" + "envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext]" + ".common_tls_context " + "error:match_subject_alt_names not supported on servers]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, NoTlsCertificateProvider) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.mutable_default_filter_chain(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* transport_socket = filter_chain->mutable_transport_socket(); + transport_socket->set_name("envoy.transport_sockets.tls"); + transport_socket->mutable_typed_config()->PackFrom(DownstreamTlsContext()); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [" + "field:default_filter_chain.transport_socket.typed_config.value[" + "envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext] " + "error:TLS configuration provided but no " + "tls_certificate_provider_instance found]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, RequireClientCertWithoutCaCertProvider) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.mutable_default_filter_chain(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* transport_socket = filter_chain->mutable_transport_socket(); + transport_socket->set_name("envoy.transport_sockets.tls"); + DownstreamTlsContext downstream_tls_context; + downstream_tls_context.mutable_require_client_certificate()->set_value(true); + auto* common_tls_context = + downstream_tls_context.mutable_common_tls_context(); + auto* cert_provider = + common_tls_context->mutable_tls_certificate_provider_instance(); + cert_provider->set_instance_name("provider1"); + cert_provider->set_certificate_name("cert_name"); + transport_socket->mutable_typed_config()->PackFrom(downstream_tls_context); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [" + "field:default_filter_chain.transport_socket.typed_config.value[" + "envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext]" + ".require_client_certificate " + "error:client certificate required but no certificate " + "provider instance specified for validation]") + << decode_result.resource.status(); +} + +TEST_F(TcpListenerTest, UnsupportedFields) { + Listener listener; + listener.set_name("foo"); + HttpConnectionManager hcm; + auto* filter = hcm.add_http_filters(); + filter->set_name("router"); + filter->mutable_typed_config()->PackFrom(Router()); + auto* rds = hcm.mutable_rds(); + rds->set_route_config_name("rds_name"); + rds->mutable_config_source()->mutable_self(); + auto* filter_chain = listener.mutable_default_filter_chain(); + filter_chain->add_filters()->mutable_typed_config()->PackFrom(hcm); + auto* transport_socket = filter_chain->mutable_transport_socket(); + transport_socket->set_name("envoy.transport_sockets.tls"); + DownstreamTlsContext downstream_tls_context; + downstream_tls_context.mutable_require_sni()->set_value(true); + downstream_tls_context.set_ocsp_staple_policy( + downstream_tls_context.STRICT_STAPLING); + auto* common_tls_context = + downstream_tls_context.mutable_common_tls_context(); + auto* cert_provider = + common_tls_context->mutable_tls_certificate_provider_instance(); + cert_provider->set_instance_name("provider1"); + cert_provider->set_certificate_name("cert_name"); + transport_socket->mutable_typed_config()->PackFrom(downstream_tls_context); + auto* address = listener.mutable_address()->mutable_socket_address(); + address->set_address("127.0.0.1"); + address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(listener.SerializeToString(&serialized_resource)); + auto* resource_type = XdsListenerResourceType::Get(); + auto decode_result = + resource_type->Decode(decode_context_, serialized_resource); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors validating server Listener: [" + "field:default_filter_chain.transport_socket.typed_config.value[" + "envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext]" + ".ocsp_staple_policy " + "error:value must be LENIENT_STAPLING; " + "field:default_filter_chain.transport_socket.typed_config.value[" + "envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext]" + ".require_sni " + "error:field unsupported]") + << decode_result.resource.status(); +} + +} // namespace +} // namespace testing +} // namespace grpc_core + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + grpc::testing::TestEnvironment env(&argc, argv); + grpc_init(); + int ret = RUN_ALL_TESTS(); + grpc_shutdown(); + return ret; +} diff --git a/test/cpp/end2end/xds/BUILD b/test/cpp/end2end/xds/BUILD index 7d7d2d4d48e..ecd312f1c3f 100644 --- a/test/cpp/end2end/xds/BUILD +++ b/test/cpp/end2end/xds/BUILD @@ -45,15 +45,6 @@ grpc_cc_library( ], ) -grpc_cc_library( - name = "no_op_http_filter", - testonly = True, - hdrs = ["no_op_http_filter.h"], - deps = [ - "//:grpc", - ], -) - grpc_cc_library( name = "xds_end2end_test_lib", testonly = True, @@ -112,7 +103,6 @@ grpc_cc_test( "no_windows", ], # TODO(jtattermusch): fix test on windows deps = [ - ":no_op_http_filter", ":xds_end2end_test_lib", "//:gpr", "//:grpc", @@ -279,7 +269,6 @@ grpc_cc_test( "no_windows", ], # TODO(jtattermusch): fix test on windows deps = [ - ":no_op_http_filter", ":xds_end2end_test_lib", "//:gpr", "//:grpc", @@ -360,12 +349,10 @@ grpc_cc_test( "no_windows", ], # TODO(jtattermusch): fix test on windows deps = [ - ":no_op_http_filter", ":xds_end2end_test_lib", "//:gpr", "//:grpc", "//:grpc++", - "//src/proto/grpc/testing/xds/v3:fault_proto", "//src/proto/grpc/testing/xds/v3:router_proto", "//test/core/util:grpc_test_util", ], diff --git a/test/cpp/end2end/xds/no_op_http_filter.h b/test/cpp/end2end/xds/no_op_http_filter.h deleted file mode 100644 index 35b3a96919f..00000000000 --- a/test/cpp/end2end/xds/no_op_http_filter.h +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2017 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/xds/xds_http_filters.h" - -namespace grpc { -namespace testing { - -// A No-op HTTP filter used for verifying parsing logic. -class NoOpHttpFilter : public grpc_core::XdsHttpFilterImpl { - public: - NoOpHttpFilter(std::string name, bool supported_on_clients, - bool supported_on_servers, bool is_terminal_filter) - : name_(std::move(name)), - supported_on_clients_(supported_on_clients), - supported_on_servers_(supported_on_servers), - is_terminal_filter_(is_terminal_filter) {} - - void PopulateSymtab(upb_DefPool* /*symtab*/) const override {} - - absl::StatusOr - GenerateFilterConfig(grpc_core::XdsExtension /*extension*/, - upb_Arena* /*arena*/) const override { - return grpc_core::XdsHttpFilterImpl::FilterConfig{name_, grpc_core::Json()}; - } - - absl::StatusOr - GenerateFilterConfigOverride( - grpc_core::XdsExtension /*serialized_filter_config*/, - upb_Arena* /*arena*/) const override { - return grpc_core::XdsHttpFilterImpl::FilterConfig{name_, grpc_core::Json()}; - } - - const grpc_channel_filter* channel_filter() const override { return nullptr; } - - absl::StatusOr - GenerateServiceConfig( - const FilterConfig& /*hcm_filter_config*/, - const FilterConfig* /*filter_config_override*/) const override { - return grpc_core::XdsHttpFilterImpl::ServiceConfigJsonEntry{name_, ""}; - } - - bool IsSupportedOnClients() const override { return supported_on_clients_; } - - bool IsSupportedOnServers() const override { return supported_on_servers_; } - - bool IsTerminalFilter() const override { return is_terminal_filter_; } - - private: - const std::string name_; - const bool supported_on_clients_; - const bool supported_on_servers_; - const bool is_terminal_filter_; -}; - -} // namespace testing -} // namespace grpc diff --git a/test/cpp/end2end/xds/xds_end2end_test.cc b/test/cpp/end2end/xds/xds_end2end_test.cc index 314b5bcf727..6a396db8cd7 100644 --- a/test/cpp/end2end/xds/xds_end2end_test.cc +++ b/test/cpp/end2end/xds/xds_end2end_test.cc @@ -101,7 +101,6 @@ #include "src/proto/grpc/testing/xds/v3/tls.grpc.pb.h" #include "test/core/util/port.h" #include "test/core/util/test_config.h" -#include "test/cpp/end2end/xds/no_op_http_filter.h" #include "test/cpp/end2end/xds/xds_end2end_test_lib.h" #include "test/cpp/util/test_config.h" #include "test/cpp/util/tls_test_utils.h" @@ -767,183 +766,24 @@ TEST_P(XdsEnabledServerTest, ListenerDeletionIgnored) { CheckRpcSendOk(DEBUG_LOCATION); } +// Testing just one example of an invalid resource here. +// Unit tests for XdsListenerResourceType have exhaustive tests for all +// of the invalid cases. TEST_P(XdsEnabledServerTest, BadLdsUpdateNoApiListenerNorAddress) { DoSetUp(); Listener listener = default_server_listener_; listener.clear_address(); - listener.set_name( - absl::StrCat("grpc/server?xds.resource.listening_address=", - ipv6_only_ ? "[::1]:" : "127.0.0.1:", backends_[0]->port())); + listener.set_name(GetServerListenerName(backends_[0]->port())); balancer_->ads_service()->SetLdsResource(listener); backends_[0]->Start(); const auto response_state = WaitForLdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( + EXPECT_EQ( response_state->error_message, - ::testing::HasSubstr("Listener has neither address nor ApiListener")); -} - -// TODO(roth): Re-enable the following test once -// github.com/istio/istio/issues/38914 is resolved. -TEST_P(XdsEnabledServerTest, DISABLED_BadLdsUpdateBothApiListenerAndAddress) { - DoSetUp(); - Listener listener = default_server_listener_; - listener.mutable_api_listener(); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr("Listener has both address and ApiListener")); -} - -TEST_P(XdsEnabledServerTest, NacksNonZeroXffNumTrusterHops) { - DoSetUp(); - Listener listener = default_server_listener_; - HttpConnectionManager http_connection_manager = - ServerHcmAccessor().Unpack(listener); - http_connection_manager.set_xff_num_trusted_hops(1); - ServerHcmAccessor().Pack(http_connection_manager, &listener); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("'xff_num_trusted_hops' must be zero")); -} - -TEST_P(XdsEnabledServerTest, NacksNonEmptyOriginalIpDetectionExtensions) { - DoSetUp(); - Listener listener = default_server_listener_; - HttpConnectionManager http_connection_manager = - ServerHcmAccessor().Unpack(listener); - http_connection_manager.add_original_ip_detection_extensions(); - ServerHcmAccessor().Pack(http_connection_manager, &listener); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr("'original_ip_detection_extensions' must be empty")); -} - -TEST_P(XdsEnabledServerTest, UnsupportedL4Filter) { - DoSetUp(); - Listener listener = default_server_listener_; - listener.mutable_default_filter_chain()->clear_filters(); - listener.mutable_default_filter_chain()->add_filters()->mutable_typed_config()->PackFrom(default_listener_ /* any proto object other than HttpConnectionManager */); - balancer_->ads_service()->SetLdsResource( - PopulateServerListenerNameAndPort(listener, backends_[0]->port())); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("Unsupported filter type")); -} - -TEST_P(XdsEnabledServerTest, NacksEmptyHttpFilterList) { - DoSetUp(); - Listener listener = default_server_listener_; - HttpConnectionManager http_connection_manager = - ServerHcmAccessor().Unpack(listener); - http_connection_manager.clear_http_filters(); - ServerHcmAccessor().Pack(http_connection_manager, &listener); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("Expected at least one HTTP filter")); -} - -TEST_P(XdsEnabledServerTest, UnsupportedHttpFilter) { - DoSetUp(); - Listener listener = default_server_listener_; - HttpConnectionManager http_connection_manager = - ServerHcmAccessor().Unpack(listener); - http_connection_manager.clear_http_filters(); - auto* http_filter = http_connection_manager.add_http_filters(); - http_filter->set_name("grpc.testing.unsupported_http_filter"); - http_filter->mutable_typed_config()->set_type_url( - "custom/grpc.testing.unsupported_http_filter"); - http_filter = http_connection_manager.add_http_filters(); - http_filter->set_name("router"); - http_filter->mutable_typed_config()->PackFrom( - envoy::extensions::filters::http::router::v3::Router()); - ServerHcmAccessor().Pack(http_connection_manager, &listener); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("no filter registered for config type " - "grpc.testing.unsupported_http_filter")); -} - -TEST_P(XdsEnabledServerTest, HttpFilterNotSupportedOnServer) { - DoSetUp(); - Listener listener = default_server_listener_; - HttpConnectionManager http_connection_manager = - ServerHcmAccessor().Unpack(listener); - http_connection_manager.clear_http_filters(); - auto* http_filter = http_connection_manager.add_http_filters(); - http_filter->set_name("grpc.testing.client_only_http_filter"); - http_filter->mutable_typed_config()->set_type_url( - "custom/grpc.testing.client_only_http_filter"); - http_filter = http_connection_manager.add_http_filters(); - http_filter->set_name("router"); - http_filter->mutable_typed_config()->PackFrom( - envoy::extensions::filters::http::router::v3::Router()); - ServerHcmAccessor().Pack(http_connection_manager, &listener); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr("Filter grpc.testing.client_only_http_filter is not " - "supported on servers")); -} - -TEST_P(XdsEnabledServerTest, - HttpFilterNotSupportedOnServerIgnoredWhenOptional) { - DoSetUp(); - Listener listener = default_server_listener_; - HttpConnectionManager http_connection_manager = - ServerHcmAccessor().Unpack(listener); - http_connection_manager.clear_http_filters(); - auto* http_filter = http_connection_manager.add_http_filters(); - http_filter->set_name("grpc.testing.client_only_http_filter"); - http_filter->mutable_typed_config()->set_type_url( - "custom/grpc.testing.client_only_http_filter"); - http_filter->set_is_optional(true); - http_filter = http_connection_manager.add_http_filters(); - http_filter->set_name("router"); - http_filter->mutable_typed_config()->PackFrom( - envoy::extensions::filters::http::router::v3::Router()); - ServerHcmAccessor().Pack(http_connection_manager, &listener); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - WaitForBackend(DEBUG_LOCATION, 0); - auto response_state = balancer_->ads_service()->lds_response_state(); - ASSERT_TRUE(response_state.has_value()); - EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); + absl::StrCat( + "xDS response validation errors: [resource index 0: ", + GetServerListenerName(backends_[0]->port()), + ": INVALID_ARGUMENT: Listener has neither address nor ApiListener]")); } // Verify that a mismatch of listening address results in "not serving" @@ -963,21 +803,6 @@ TEST_P(XdsEnabledServerTest, ListenerAddressMismatch) { grpc::StatusCode::FAILED_PRECONDITION); } -TEST_P(XdsEnabledServerTest, UseOriginalDstNotSupported) { - DoSetUp(); - Listener listener = default_server_listener_; - listener.mutable_use_original_dst()->set_value(true); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr("Field \'use_original_dst\' is not supported.")); -} - class XdsServerSecurityTest : public XdsEnd2endTest { protected: void SetUp() override { @@ -1202,173 +1027,6 @@ class XdsServerSecurityTest : public XdsEnd2endTest { std::vector client_authenticated_identity_; }; -TEST_P(XdsServerSecurityTest, TransportSocketTypedConfigUnset) { - Listener listener = default_server_listener_; - auto* filter_chain = listener.mutable_default_filter_chain(); - filter_chain->mutable_transport_socket(); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("transport socket typed config unset")); -} - -TEST_P(XdsServerSecurityTest, UnknownTransportSocket) { - Listener listener = default_server_listener_; - auto* filter_chain = listener.mutable_default_filter_chain(); - auto* transport_socket = filter_chain->mutable_transport_socket(); - transport_socket->mutable_typed_config()->PackFrom(Listener()); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("Unrecognized transport socket type: " - "envoy.config.listener.v3.Listener")); -} - -TEST_P(XdsServerSecurityTest, NacksRequireSNI) { - Listener listener = default_server_listener_; - auto* filter_chain = listener.mutable_default_filter_chain(); - auto* transport_socket = filter_chain->mutable_transport_socket(); - transport_socket->set_name("envoy.transport_sockets.tls"); - DownstreamTlsContext downstream_tls_context; - downstream_tls_context.mutable_common_tls_context() - ->mutable_tls_certificate_provider_instance() - ->set_instance_name("fake_plugin1"); - downstream_tls_context.mutable_require_sni()->set_value(true); - transport_socket->mutable_typed_config()->PackFrom(downstream_tls_context); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("require_sni: unsupported")); -} - -TEST_P(XdsServerSecurityTest, NacksOcspStaplePolicyOtherThanLenientStapling) { - Listener listener = default_server_listener_; - auto* filter_chain = listener.mutable_default_filter_chain(); - auto* transport_socket = filter_chain->mutable_transport_socket(); - transport_socket->set_name("envoy.transport_sockets.tls"); - DownstreamTlsContext downstream_tls_context; - downstream_tls_context.mutable_common_tls_context() - ->mutable_tls_certificate_provider_instance() - ->set_instance_name("fake_plugin1"); - downstream_tls_context.set_ocsp_staple_policy( - envoy::extensions::transport_sockets::tls::v3:: - DownstreamTlsContext_OcspStaplePolicy_STRICT_STAPLING); - transport_socket->mutable_typed_config()->PackFrom(downstream_tls_context); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr( - "ocsp_staple_policy: Only LENIENT_STAPLING supported")); -} - -TEST_P( - XdsServerSecurityTest, - NacksRequiringClientCertificateWithoutValidationCertificateProviderInstance) { - Listener listener = default_server_listener_; - auto* filter_chain = listener.mutable_default_filter_chain(); - auto* transport_socket = filter_chain->mutable_transport_socket(); - transport_socket->set_name("envoy.transport_sockets.tls"); - DownstreamTlsContext downstream_tls_context; - downstream_tls_context.mutable_common_tls_context() - ->mutable_tls_certificate_provider_instance() - ->set_instance_name("fake_plugin1"); - downstream_tls_context.mutable_require_client_certificate()->set_value(true); - transport_socket->mutable_typed_config()->PackFrom(downstream_tls_context); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr( - "TLS configuration requires client certificates but no " - "certificate provider instance specified for validation.")); -} - -TEST_P(XdsServerSecurityTest, - NacksTlsConfigurationWithoutIdentityProviderInstance) { - Listener listener = default_server_listener_; - auto* filter_chain = listener.mutable_default_filter_chain(); - auto* transport_socket = filter_chain->mutable_transport_socket(); - transport_socket->set_name("envoy.transport_sockets.tls"); - DownstreamTlsContext downstream_tls_context; - transport_socket->mutable_typed_config()->PackFrom(downstream_tls_context); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("TLS configuration provided but no " - "tls_certificate_provider_instance found.")); -} - -TEST_P(XdsServerSecurityTest, NacksMatchSubjectAltNames) { - Listener listener = default_server_listener_; - auto* filter_chain = listener.mutable_default_filter_chain(); - auto* transport_socket = filter_chain->mutable_transport_socket(); - transport_socket->set_name("envoy.transport_sockets.tls"); - DownstreamTlsContext downstream_tls_context; - downstream_tls_context.mutable_common_tls_context() - ->mutable_tls_certificate_provider_instance() - ->set_instance_name("fake_plugin1"); - downstream_tls_context.mutable_common_tls_context() - ->mutable_validation_context() - ->add_match_subject_alt_names() - ->set_exact("*.test.google.fr"); - transport_socket->mutable_typed_config()->PackFrom(downstream_tls_context); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr("match_subject_alt_names not supported on servers")); -} - -TEST_P(XdsServerSecurityTest, UnknownIdentityCertificateProvider) { - SetLdsUpdate("", "", "unknown", "", false); - SendRpc([this]() { return CreateTlsChannel(); }, {}, {}, - true /* test_expects_failure */); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr( - "unrecognized certificate provider instance name: unknown")); -} - -TEST_P(XdsServerSecurityTest, UnknownRootCertificateProvider) { - g_fake1_cert_data_map->Set({{"", {root_cert_, identity_pair_}}}); - SetLdsUpdate("unknown", "", "fake_plugin1", "", false); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr( - "unrecognized certificate provider instance name: unknown")); -} - TEST_P(XdsServerSecurityTest, TestDeprecateTlsCertificateCertificateProviderInstanceField) { g_fake1_cert_data_map->Set({{"", {root_cert_, identity_pair_}}}); @@ -2130,232 +1788,6 @@ TEST_P(XdsServerFilterChainMatchTest, SendRpc([this]() { return CreateInsecureChannel(); }, {}, {}); } -TEST_P(XdsServerFilterChainMatchTest, DuplicateMatchNacked) { - Listener listener = default_server_listener_; - // Add filter chain - auto* filter_chain = listener.add_filter_chains(); - filter_chain->add_filters()->mutable_typed_config()->PackFrom( - GetHttpConnectionManager(listener)); - // Add a duplicate filter chain - filter_chain = listener.add_filter_chains(); - filter_chain->add_filters()->mutable_typed_config()->PackFrom( - GetHttpConnectionManager(listener)); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr( - "Duplicate matching rules detected when adding filter chain: {}")); -} - -TEST_P(XdsServerFilterChainMatchTest, DuplicateMatchOnPrefixRangesNacked) { - Listener listener = default_server_listener_; - // Add filter chain with prefix range - auto* filter_chain = listener.add_filter_chains(); - filter_chain->add_filters()->mutable_typed_config()->PackFrom( - GetHttpConnectionManager(listener)); - auto* prefix_range = - filter_chain->mutable_filter_chain_match()->add_prefix_ranges(); - prefix_range->set_address_prefix(ipv6_only_ ? "::1" : "127.0.0.1"); - prefix_range->mutable_prefix_len()->set_value(16); - prefix_range = - filter_chain->mutable_filter_chain_match()->add_prefix_ranges(); - prefix_range->set_address_prefix(ipv6_only_ ? "::1" : "127.0.0.1"); - prefix_range->mutable_prefix_len()->set_value(24); - // Add a filter chain with a duplicate prefix range entry - filter_chain = listener.add_filter_chains(); - filter_chain->add_filters()->mutable_typed_config()->PackFrom( - GetHttpConnectionManager(listener)); - prefix_range = - filter_chain->mutable_filter_chain_match()->add_prefix_ranges(); - prefix_range->set_address_prefix(ipv6_only_ ? "::1" : "127.0.0.1"); - prefix_range->mutable_prefix_len()->set_value(16); - prefix_range = - filter_chain->mutable_filter_chain_match()->add_prefix_ranges(); - prefix_range->set_address_prefix(ipv6_only_ ? "::1" : "127.0.0.1"); - prefix_range->mutable_prefix_len()->set_value(32); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - if (ipv6_only_) { - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr( - "Duplicate matching rules detected when adding filter chain: " - "{prefix_ranges={{address_prefix=[::]:0, prefix_len=16}, " - "{address_prefix=[::]:0, prefix_len=32}}}")); - } else { - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr( - "Duplicate matching rules detected when adding filter chain: " - "{prefix_ranges={{address_prefix=127.0.0.0:0, prefix_len=16}, " - "{address_prefix=127.0.0.1:0, prefix_len=32}}}")); - } -} - -TEST_P(XdsServerFilterChainMatchTest, DuplicateMatchOnTransportProtocolNacked) { - Listener listener = default_server_listener_; - // Add filter chain with "raw_buffer" transport protocol - auto* filter_chain = listener.add_filter_chains(); - filter_chain->add_filters()->mutable_typed_config()->PackFrom( - GetHttpConnectionManager(listener)); - filter_chain->mutable_filter_chain_match()->set_transport_protocol( - "raw_buffer"); - // Add a duplicate filter chain with the same "raw_buffer" transport - // protocol entry - filter_chain = listener.add_filter_chains(); - filter_chain->add_filters()->mutable_typed_config()->PackFrom( - GetHttpConnectionManager(listener)); - filter_chain->mutable_filter_chain_match()->set_transport_protocol( - "raw_buffer"); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr("Duplicate matching rules detected when adding " - "filter chain: {transport_protocol=raw_buffer}")); -} - -TEST_P(XdsServerFilterChainMatchTest, DuplicateMatchOnLocalSourceTypeNacked) { - Listener listener = default_server_listener_; - // Add filter chain with the local source type - auto* filter_chain = listener.add_filter_chains(); - filter_chain->add_filters()->mutable_typed_config()->PackFrom( - GetHttpConnectionManager(listener)); - filter_chain->mutable_filter_chain_match()->set_source_type( - FilterChainMatch::SAME_IP_OR_LOOPBACK); - // Add a duplicate filter chain with the same local source type entry - filter_chain = listener.add_filter_chains(); - filter_chain->add_filters()->mutable_typed_config()->PackFrom( - GetHttpConnectionManager(listener)); - filter_chain->mutable_filter_chain_match()->set_source_type( - FilterChainMatch::SAME_IP_OR_LOOPBACK); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr("Duplicate matching rules detected when adding " - "filter chain: {source_type=SAME_IP_OR_LOOPBACK}")); -} - -TEST_P(XdsServerFilterChainMatchTest, - DuplicateMatchOnExternalSourceTypeNacked) { - Listener listener = default_server_listener_; - // Add filter chain with the external source type - auto* filter_chain = listener.add_filter_chains(); - filter_chain->add_filters()->mutable_typed_config()->PackFrom( - GetHttpConnectionManager(listener)); - filter_chain->mutable_filter_chain_match()->set_source_type( - FilterChainMatch::EXTERNAL); - // Add a duplicate filter chain with the same external source type entry - filter_chain = listener.add_filter_chains(); - filter_chain->add_filters()->mutable_typed_config()->PackFrom( - GetHttpConnectionManager(listener)); - filter_chain->mutable_filter_chain_match()->set_source_type( - FilterChainMatch::EXTERNAL); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr("Duplicate matching rules detected when adding " - "filter chain: {source_type=EXTERNAL}")); -} - -TEST_P(XdsServerFilterChainMatchTest, - DuplicateMatchOnSourcePrefixRangesNacked) { - Listener listener = default_server_listener_; - // Add filter chain with source prefix range - auto* filter_chain = listener.add_filter_chains(); - filter_chain->add_filters()->mutable_typed_config()->PackFrom( - GetHttpConnectionManager(listener)); - auto* prefix_range = - filter_chain->mutable_filter_chain_match()->add_source_prefix_ranges(); - prefix_range->set_address_prefix(ipv6_only_ ? "::1" : "127.0.0.1"); - prefix_range->mutable_prefix_len()->set_value(16); - prefix_range = - filter_chain->mutable_filter_chain_match()->add_source_prefix_ranges(); - prefix_range->set_address_prefix(ipv6_only_ ? "::1" : "127.0.0.1"); - prefix_range->mutable_prefix_len()->set_value(24); - // Add a filter chain with a duplicate source prefix range entry - filter_chain = listener.add_filter_chains(); - filter_chain->add_filters()->mutable_typed_config()->PackFrom( - GetHttpConnectionManager(listener)); - prefix_range = - filter_chain->mutable_filter_chain_match()->add_source_prefix_ranges(); - prefix_range->set_address_prefix(ipv6_only_ ? "::1" : "127.0.0.1"); - prefix_range->mutable_prefix_len()->set_value(16); - prefix_range = - filter_chain->mutable_filter_chain_match()->add_source_prefix_ranges(); - prefix_range->set_address_prefix(ipv6_only_ ? "::1" : "127.0.0.1"); - prefix_range->mutable_prefix_len()->set_value(32); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - if (ipv6_only_) { - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr( - "Duplicate matching rules detected when adding filter chain: " - "{source_prefix_ranges={{address_prefix=[::]:0, prefix_len=16}, " - "{address_prefix=[::]:0, prefix_len=32}}}")); - } else { - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr( - "Duplicate matching rules detected when adding filter chain: " - "{source_prefix_ranges={{address_prefix=127.0.0.0:0, " - "prefix_len=16}, " - "{address_prefix=127.0.0.1:0, prefix_len=32}}}")); - } -} - -TEST_P(XdsServerFilterChainMatchTest, DuplicateMatchOnSourcePortNacked) { - Listener listener = default_server_listener_; - // Add filter chain with the external source type - auto* filter_chain = listener.add_filter_chains(); - filter_chain->add_filters()->mutable_typed_config()->PackFrom( - GetHttpConnectionManager(listener)); - filter_chain->mutable_filter_chain_match()->add_source_ports(8080); - // Add a duplicate filter chain with the same source port entry - filter_chain = listener.add_filter_chains(); - filter_chain->add_filters()->mutable_typed_config()->PackFrom( - GetHttpConnectionManager(listener)); - filter_chain->mutable_filter_chain_match()->add_source_ports(8080); - SetServerListenerNameAndRouteConfiguration(balancer_.get(), listener, - backends_[0]->port(), - default_server_route_config_); - backends_[0]->Start(); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr("Duplicate matching rules detected when adding " - "filter chain: {source_ports={8080}}")); -} - using XdsServerRdsTest = XdsEnabledServerStatusNotificationTest; TEST_P(XdsServerRdsTest, Basic) { @@ -2608,116 +2040,6 @@ TEST_P(XdsRbacTest, LogAction) { SendRpc([this]() { return CreateInsecureChannel(); }, {}, {}); } -using XdsRbacNackTest = XdsRbacTest; - -TEST_P(XdsRbacNackTest, NacksSchemePrincipalHeader) { - RBAC rbac; - auto* rules = rbac.mutable_rules(); - rules->set_action(envoy::config::rbac::v3::RBAC_Action_ALLOW); - Policy policy; - auto* header = policy.add_principals()->mutable_header(); - header->set_name(":scheme"); - header->set_exact_match("http"); - policy.add_permissions()->set_any(true); - (*rules->mutable_policies())["policy"] = policy; - SetServerRbacPolicy(rbac); - backends_[0]->Start(); - if (GetParam().enable_rds_testing() && - GetParam().filter_config_setup() == - XdsTestType::HttpFilterConfigLocation::kHttpFilterConfigInRoute) { - const auto response_state = WaitForRdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("':scheme' not allowed in header")); - } else { - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("':scheme' not allowed in header")); - } -} - -TEST_P(XdsRbacNackTest, NacksGrpcPrefixedPrincipalHeaders) { - RBAC rbac; - auto* rules = rbac.mutable_rules(); - rules->set_action(envoy::config::rbac::v3::RBAC_Action_ALLOW); - Policy policy; - auto* header = policy.add_principals()->mutable_header(); - header->set_name("grpc-status"); - header->set_exact_match("0"); - policy.add_permissions()->set_any(true); - (*rules->mutable_policies())["policy"] = policy; - SetServerRbacPolicy(rbac); - backends_[0]->Start(); - if (GetParam().enable_rds_testing() && - GetParam().filter_config_setup() == - XdsTestType::HttpFilterConfigLocation::kHttpFilterConfigInRoute) { - const auto response_state = WaitForRdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("'grpc-' prefixes not allowed in header")); - } else { - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("'grpc-' prefixes not allowed in header")); - } -} - -TEST_P(XdsRbacNackTest, NacksSchemePermissionHeader) { - RBAC rbac; - auto* rules = rbac.mutable_rules(); - rules->set_action(envoy::config::rbac::v3::RBAC_Action_ALLOW); - Policy policy; - auto* header = policy.add_permissions()->mutable_header(); - header->set_name(":scheme"); - header->set_exact_match("http"); - policy.add_principals()->set_any(true); - (*rules->mutable_policies())["policy"] = policy; - SetServerRbacPolicy(rbac); - backends_[0]->Start(); - if (GetParam().enable_rds_testing() && - GetParam().filter_config_setup() == - XdsTestType::HttpFilterConfigLocation::kHttpFilterConfigInRoute) { - const auto response_state = WaitForRdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("':scheme' not allowed in header")); - } else { - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("':scheme' not allowed in header")); - } -} - -TEST_P(XdsRbacNackTest, NacksGrpcPrefixedPermissionHeaders) { - RBAC rbac; - auto* rules = rbac.mutable_rules(); - rules->set_action(envoy::config::rbac::v3::RBAC_Action_ALLOW); - Policy policy; - auto* header = policy.add_permissions()->mutable_header(); - header->set_name("grpc-status"); - header->set_exact_match("0"); - policy.add_principals()->set_any(true); - (*rules->mutable_policies())["policy"] = policy; - SetServerRbacPolicy(rbac); - backends_[0]->Start(); - if (GetParam().enable_rds_testing() && - GetParam().filter_config_setup() == - XdsTestType::HttpFilterConfigLocation::kHttpFilterConfigInRoute) { - const auto response_state = WaitForRdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("'grpc-' prefixes not allowed in header")); - } else { - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("'grpc-' prefixes not allowed in header")); - } -} - // Tests RBAC policies where a route override is always present. Action // permutations are not added. using XdsRbacTestWithRouteOverrideAlwaysPresent = XdsRbacTest; @@ -3587,30 +2909,6 @@ INSTANTIATE_TEST_SUITE_P( .set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar)), &XdsTestType::Name); -// We are only testing the server here. -// Run with bootstrap from env var, so that we use a global XdsClient -// instance. Otherwise, we would need to use a separate fake resolver -// result generator on the client and server sides. -// Note that we are simply using the default fake credentials instead of xds -// credentials for NACK tests to avoid a mismatch between the client and the -// server's security settings when using the WaitForNack() infrastructure. -INSTANTIATE_TEST_SUITE_P( - XdsTest, XdsRbacNackTest, - ::testing::Values( - XdsTestType().set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar), - XdsTestType().set_enable_rds_testing().set_bootstrap_source( - XdsTestType::kBootstrapFromEnvVar), - XdsTestType() - .set_filter_config_setup( - XdsTestType::HttpFilterConfigLocation::kHttpFilterConfigInRoute) - .set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar), - XdsTestType() - .set_enable_rds_testing() - .set_filter_config_setup( - XdsTestType::HttpFilterConfigLocation::kHttpFilterConfigInRoute) - .set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar)), - &XdsTestType::Name); - // We are only testing the server here. // Run with bootstrap from env var, so that we use a global XdsClient // instance. Otherwise, we would need to use a separate fake resolver @@ -3714,24 +3012,6 @@ int main(int argc, char** argv) { "fake2", grpc::testing::g_fake2_cert_data_map)); }); grpc_init(); - grpc_core::XdsHttpFilterRegistry::RegisterFilter( - std::make_unique( - "grpc.testing.client_only_http_filter", - /* supported_on_clients = */ true, /* supported_on_servers = */ false, - /* is_terminal_filter */ false), - {"grpc.testing.client_only_http_filter"}); - grpc_core::XdsHttpFilterRegistry::RegisterFilter( - std::make_unique( - "grpc.testing.server_only_http_filter", - /* supported_on_clients = */ false, /* supported_on_servers = */ true, - /* is_terminal_filter */ false), - {"grpc.testing.server_only_http_filter"}); - grpc_core::XdsHttpFilterRegistry::RegisterFilter( - std::make_unique( - "grpc.testing.terminal_http_filter", - /* supported_on_clients = */ true, /* supported_on_servers = */ true, - /* is_terminal_filter */ true), - {"grpc.testing.terminal_http_filter"}); const auto result = RUN_ALL_TESTS(); grpc_shutdown(); return result; diff --git a/test/cpp/end2end/xds/xds_outlier_detection_end2end_test.cc b/test/cpp/end2end/xds/xds_outlier_detection_end2end_test.cc index 477fd61817c..153eaf7bd9e 100644 --- a/test/cpp/end2end/xds/xds_outlier_detection_end2end_test.cc +++ b/test/cpp/end2end/xds/xds_outlier_detection_end2end_test.cc @@ -25,7 +25,6 @@ #include "src/proto/grpc/testing/xds/v3/fault.grpc.pb.h" #include "src/proto/grpc/testing/xds/v3/outlier_detection.grpc.pb.h" #include "src/proto/grpc/testing/xds/v3/router.grpc.pb.h" -#include "test/cpp/end2end/xds/no_op_http_filter.h" #include "test/cpp/end2end/xds/xds_end2end_test_lib.h" namespace grpc { diff --git a/test/cpp/end2end/xds/xds_routing_end2end_test.cc b/test/cpp/end2end/xds/xds_routing_end2end_test.cc index d56ac655e6c..25a8a3861f6 100644 --- a/test/cpp/end2end/xds/xds_routing_end2end_test.cc +++ b/test/cpp/end2end/xds/xds_routing_end2end_test.cc @@ -21,16 +21,13 @@ #include #include "src/core/ext/filters/client_channel/backup_poller.h" -#include "src/proto/grpc/testing/xds/v3/fault.grpc.pb.h" #include "src/proto/grpc/testing/xds/v3/router.grpc.pb.h" -#include "test/cpp/end2end/xds/no_op_http_filter.h" #include "test/cpp/end2end/xds/xds_end2end_test_lib.h" namespace grpc { namespace testing { namespace { -using ::envoy::extensions::filters::http::fault::v3::HTTPFault; using std::chrono::system_clock; using LdsTest = XdsEnd2endTest; @@ -38,9 +35,10 @@ using LdsTest = XdsEnd2endTest; INSTANTIATE_TEST_SUITE_P(XdsTest, LdsTest, ::testing::Values(XdsTestType()), &XdsTestType::Name); -// Tests that LDS client should send a NACK if there is no API listener in the -// Listener in the LDS response. -TEST_P(LdsTest, NoApiListener) { +// Testing just one example of an invalid resource here. +// Unit tests for XdsListenerResourceType have exhaustive tests for all +// of the invalid cases. +TEST_P(LdsTest, NacksInvalidListener) { auto listener = default_listener_; listener.clear_api_listener(); balancer_->ads_service()->SetLdsResource(listener); @@ -51,382 +49,6 @@ TEST_P(LdsTest, NoApiListener) { ::testing::HasSubstr("Listener has neither address nor ApiListener")); } -// Tests that LDS client should send a NACK if the route_specifier in the -// http_connection_manager is neither inlined route_config nor RDS. -TEST_P(LdsTest, WrongRouteSpecifier) { - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - http_connection_manager.mutable_scoped_routes(); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - balancer_->ads_service()->SetLdsResource(listener); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr( - "HttpConnectionManager neither has inlined route_config nor RDS.")); -} - -// Tests that LDS client should send a NACK if the rds message in the -// http_connection_manager is missing the config_source field. -TEST_P(LdsTest, RdsMissingConfigSource) { - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - http_connection_manager.mutable_rds()->set_route_config_name( - kDefaultRouteConfigurationName); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - balancer_->ads_service()->SetLdsResource(listener); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr( - "HttpConnectionManager missing config_source for RDS.")); -} - -// Tests that LDS client should send a NACK if the rds message in the -// http_connection_manager has a config_source field that does not specify -// ADS or SELF. -TEST_P(LdsTest, RdsConfigSourceDoesNotSpecifyAdsOrSelf) { - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - auto* rds = http_connection_manager.mutable_rds(); - rds->set_route_config_name(kDefaultRouteConfigurationName); - rds->mutable_config_source()->set_path("/foo/bar"); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - balancer_->ads_service()->SetLdsResource(listener); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("HttpConnectionManager ConfigSource for " - "RDS does not specify ADS or SELF.")); -} - -// Tests that LDS client accepts the rds message in the -// http_connection_manager with a config_source field that specifies ADS. -TEST_P(LdsTest, AcceptsRdsConfigSourceOfTypeAds) { - CreateAndStartBackends(1); - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - auto* rds = http_connection_manager.mutable_rds(); - rds->set_route_config_name(kDefaultRouteConfigurationName); - rds->mutable_config_source()->mutable_ads(); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - WaitForAllBackends(DEBUG_LOCATION); - auto response_state = balancer_->ads_service()->lds_response_state(); - ASSERT_TRUE(response_state.has_value()); - EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); -} - -// Tests that we NACK non-terminal filters at the end of the list. -TEST_P(LdsTest, NacksNonTerminalHttpFilterAtEndOfList) { - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - auto* filter = http_connection_manager.mutable_http_filters(0); - filter->set_name("unknown"); - filter->mutable_typed_config()->set_type_url( - "custom/grpc.testing.client_only_http_filter"); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr( - "non-terminal filter for config type grpc.testing" - ".client_only_http_filter is the last filter in the chain")); -} - -// Test that we NACK terminal filters that are not at the end of the list. -TEST_P(LdsTest, NacksTerminalFilterBeforeEndOfList) { - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - // The default_listener_ has a terminal router filter by default. Add an - // additional filter. - auto* filter = http_connection_manager.add_http_filters(); - filter->set_name("grpc.testing.terminal_http_filter"); - filter->mutable_typed_config()->set_type_url( - "custom/grpc.testing.terminal_http_filter"); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr( - "terminal filter for config type envoy.extensions.filters.http" - ".router.v3.Router must be the last filter in the chain")); -} - -// Test that we NACK empty filter names. -TEST_P(LdsTest, RejectsEmptyHttpFilterName) { - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - *http_connection_manager.add_http_filters() = - http_connection_manager.http_filters(0); - auto* filter = http_connection_manager.mutable_http_filters(0); - filter->Clear(); - filter->mutable_typed_config()->PackFrom(Listener()); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("empty filter name at index 0")); -} - -// Test that we NACK duplicate HTTP filter names. -TEST_P(LdsTest, RejectsDuplicateHttpFilterName) { - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - *http_connection_manager.add_http_filters() = - http_connection_manager.http_filters(0); - http_connection_manager.mutable_http_filters(0) - ->mutable_typed_config() - ->PackFrom(HTTPFault()); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("duplicate HTTP filter name: router")); -} - -// Test that we NACK unknown filter types. -TEST_P(LdsTest, RejectsUnknownHttpFilterType) { - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - *http_connection_manager.add_http_filters() = - http_connection_manager.http_filters(0); - auto* filter = http_connection_manager.mutable_http_filters(0); - filter->set_name("unknown"); - filter->mutable_typed_config()->PackFrom(Listener()); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("no filter registered for config type " - "envoy.config.listener.v3.Listener")); -} - -// Test that we ignore optional unknown filter types. -TEST_P(LdsTest, IgnoresOptionalUnknownHttpFilterType) { - CreateAndStartBackends(1); - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - *http_connection_manager.add_http_filters() = - http_connection_manager.http_filters(0); - auto* filter = http_connection_manager.mutable_http_filters(0); - filter->set_name("unknown"); - filter->mutable_typed_config()->PackFrom(Listener()); - filter->set_is_optional(true); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - WaitForAllBackends(DEBUG_LOCATION); - auto response_state = balancer_->ads_service()->lds_response_state(); - ASSERT_TRUE(response_state.has_value()); - EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); -} - -// Test that we NACK filters without configs. -TEST_P(LdsTest, RejectsHttpFilterWithoutConfig) { - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - *http_connection_manager.add_http_filters() = - http_connection_manager.http_filters(0); - auto* filter = http_connection_manager.mutable_http_filters(0); - filter->Clear(); - filter->set_name("unknown"); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr( - "no filter config specified for filter name unknown")); -} - -// Test that we ignore optional filters without configs. -TEST_P(LdsTest, IgnoresOptionalHttpFilterWithoutConfig) { - CreateAndStartBackends(1); - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - *http_connection_manager.add_http_filters() = - http_connection_manager.http_filters(0); - auto* filter = http_connection_manager.mutable_http_filters(0); - filter->Clear(); - filter->set_name("unknown"); - filter->set_is_optional(true); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - WaitForAllBackends(DEBUG_LOCATION); - auto response_state = balancer_->ads_service()->lds_response_state(); - ASSERT_TRUE(response_state.has_value()); - EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); -} - -// Test that we NACK unparseable filter configs. -TEST_P(LdsTest, RejectsUnparseableHttpFilterType) { - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - *http_connection_manager.add_http_filters() = - http_connection_manager.http_filters(0); - auto* filter = http_connection_manager.mutable_http_filters(0); - filter->set_name("unknown"); - filter->mutable_typed_config()->PackFrom(listener); - filter->mutable_typed_config()->set_type_url( - "type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault"); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr( - "filter config for type " - "envoy.extensions.filters.http.fault.v3.HTTPFault failed to parse")); -} - -// Test that we NACK HTTP filters unsupported on client-side. -TEST_P(LdsTest, RejectsHttpFiltersNotSupportedOnClients) { - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - *http_connection_manager.add_http_filters() = - http_connection_manager.http_filters(0); - auto* filter = http_connection_manager.mutable_http_filters(0); - filter->set_name("grpc.testing.server_only_http_filter"); - filter->mutable_typed_config()->set_type_url( - "custom/grpc.testing.server_only_http_filter"); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr("Filter grpc.testing.server_only_http_filter is not " - "supported on clients")); -} - -// Test that we ignore optional HTTP filters unsupported on client-side. -TEST_P(LdsTest, IgnoresOptionalHttpFiltersNotSupportedOnClients) { - CreateAndStartBackends(1); - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - *http_connection_manager.add_http_filters() = - http_connection_manager.http_filters(0); - auto* filter = http_connection_manager.mutable_http_filters(0); - filter->set_name("grpc.testing.server_only_http_filter"); - filter->mutable_typed_config()->set_type_url( - "custom/grpc.testing.server_only_http_filter"); - filter->set_is_optional(true); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - WaitForAllBackends(DEBUG_LOCATION); - auto response_state = balancer_->ads_service()->lds_response_state(); - ASSERT_TRUE(response_state.has_value()); - EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); -} - -// Test that we NACK non-zero xff_num_trusted_hops -TEST_P(LdsTest, RejectsNonZeroXffNumTrusterHops) { - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - http_connection_manager.set_xff_num_trusted_hops(1); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("'xff_num_trusted_hops' must be zero")); -} - -// Test that we NACK non-empty original_ip_detection_extensions -TEST_P(LdsTest, RejectsNonEmptyOriginalIpDetectionExtensions) { - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - http_connection_manager.add_original_ip_detection_extensions(); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - const auto response_state = WaitForLdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::HasSubstr("'original_ip_detection_extensions' must be empty")); -} - class LdsDeletionTest : public XdsEnd2endTest { protected: void SetUp() override {} // Individual tests call InitClient(). @@ -3126,24 +2748,6 @@ int main(int argc, char** argv) { grpc_core::SetEnv("grpc_cfstream", "0"); #endif grpc_init(); - grpc_core::XdsHttpFilterRegistry::RegisterFilter( - std::make_unique( - "grpc.testing.client_only_http_filter", - /* supported_on_clients = */ true, /* supported_on_servers = */ false, - /* is_terminal_filter */ false), - {"grpc.testing.client_only_http_filter"}); - grpc_core::XdsHttpFilterRegistry::RegisterFilter( - std::make_unique( - "grpc.testing.server_only_http_filter", - /* supported_on_clients = */ false, /* supported_on_servers = */ true, - /* is_terminal_filter */ false), - {"grpc.testing.server_only_http_filter"}); - grpc_core::XdsHttpFilterRegistry::RegisterFilter( - std::make_unique( - "grpc.testing.terminal_http_filter", - /* supported_on_clients = */ true, /* supported_on_servers = */ true, - /* is_terminal_filter */ true), - {"grpc.testing.terminal_http_filter"}); const auto result = RUN_ALL_TESTS(); grpc_shutdown(); return result; diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json index f2b594569bd..60193ff3cf4 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -8317,6 +8317,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": "xds_http_filters_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": false + }, { "args": [], "benchmark": false, @@ -8341,6 +8365,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": "xds_listener_resource_type_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": false + }, { "args": [], "benchmark": false,