[Security] Cherry Pick Spiffe Verification (#40515)
Cherry pick #40476 to 1.75
RELEASE NOTES:
* Adds support for SPIFFE Bundle Maps in as roots of trust per [gRFC
A87](https://github.com/grpc/proposal/blob/master/A87-mtls-spiffe-support.md)
and https://github.com/grpc/proposal/pull/506
diff --git a/CMakeLists.txt b/CMakeLists.txt
index cd7aad2..42cf9eb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1768,6 +1768,10 @@
add_dependencies(buildtests_cxx socket_utils_test)
endif()
add_dependencies(buildtests_cxx sorted_pack_test)
+ add_dependencies(buildtests_cxx spiffe_bundle_map_end2end_test)
+ if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
+ add_dependencies(buildtests_cxx spiffe_ssl_transport_security_test)
+ endif()
add_dependencies(buildtests_cxx spiffe_utils_test)
add_dependencies(buildtests_cxx spinlock_test)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
@@ -1951,6 +1955,9 @@
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
add_dependencies(buildtests_cxx xds_security_end2end_test)
endif()
+ if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
+ add_dependencies(buildtests_cxx xds_security_spiffe_end2end_test)
+ endif()
add_dependencies(buildtests_cxx xds_stats_watcher_test)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
add_dependencies(buildtests_cxx xds_wrr_end2end_test)
@@ -29770,6 +29777,127 @@
endif()
if(gRPC_BUILD_TESTS)
+add_executable(spiffe_bundle_map_end2end_test
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/api/annotations.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/api/annotations.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/api/annotations.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/api/annotations.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/api/http.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/api/http.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/api/http.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/api/http.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/rpc/status.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/rpc/status.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/rpc/status.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/rpc/status.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/validate/validate.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/validate/validate.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/validate/validate.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/validate/validate.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/data/orca/v3/orca_load_report.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/data/orca/v3/orca_load_report.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/data/orca/v3/orca_load_report.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/data/orca/v3/orca_load_report.grpc.pb.h
+ test/cpp/end2end/spiffe_bundle_map_end2end_test.cc
+ test/cpp/end2end/test_service_impl.cc
+)
+if(WIN32 AND MSVC)
+ if(BUILD_SHARED_LIBS)
+ target_compile_definitions(spiffe_bundle_map_end2end_test
+ PRIVATE
+ "GPR_DLL_IMPORTS"
+ "GRPC_DLL_IMPORTS"
+ "GRPCXX_DLL_IMPORTS"
+ )
+ endif()
+endif()
+target_compile_features(spiffe_bundle_map_end2end_test PUBLIC cxx_std_17)
+target_include_directories(spiffe_bundle_map_end2end_test
+ PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}/include
+ ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
+ ${_gRPC_RE2_INCLUDE_DIR}
+ ${_gRPC_SSL_INCLUDE_DIR}
+ ${_gRPC_UPB_GENERATED_DIR}
+ ${_gRPC_UPB_GRPC_GENERATED_DIR}
+ ${_gRPC_UPB_INCLUDE_DIR}
+ ${_gRPC_XXHASH_INCLUDE_DIR}
+ ${_gRPC_ZLIB_INCLUDE_DIR}
+ third_party/googletest/googletest/include
+ third_party/googletest/googletest
+ third_party/googletest/googlemock/include
+ third_party/googletest/googlemock
+ ${_gRPC_PROTO_GENS_DIR}
+)
+
+target_link_libraries(spiffe_bundle_map_end2end_test
+ ${_gRPC_ALLTARGETS_LIBRARIES}
+ gtest
+ grpc++_test_util
+)
+
+
+endif()
+if(gRPC_BUILD_TESTS)
+if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
+
+ add_executable(spiffe_ssl_transport_security_test
+ test/core/tsi/spiffe_ssl_transport_security_test.cc
+ test/core/tsi/transport_security_test_lib.cc
+ )
+ if(WIN32 AND MSVC)
+ if(BUILD_SHARED_LIBS)
+ target_compile_definitions(spiffe_ssl_transport_security_test
+ PRIVATE
+ "GPR_DLL_IMPORTS"
+ "GRPC_DLL_IMPORTS"
+ )
+ endif()
+ endif()
+ target_compile_features(spiffe_ssl_transport_security_test PUBLIC cxx_std_17)
+ target_include_directories(spiffe_ssl_transport_security_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(spiffe_ssl_transport_security_test
+ ${_gRPC_ALLTARGETS_LIBRARIES}
+ gtest
+ grpc_test_util
+ )
+
+
+endif()
+endif()
+if(gRPC_BUILD_TESTS)
+
add_executable(spiffe_utils_test
test/core/credentials/transport/tls/spiffe_utils_test.cc
)
@@ -46930,6 +47058,562 @@
endif()
endif()
if(gRPC_BUILD_TESTS)
+if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
+
+ add_executable(xds_security_spiffe_end2end_test
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/duplicate/echo_duplicate.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/duplicate/echo_duplicate.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/duplicate/echo_duplicate.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/duplicate/echo_duplicate.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/ads.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/ads.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/ads.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/ads.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/discovery.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/discovery.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/discovery.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/discovery.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/lrs.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/lrs.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/lrs.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/lrs.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/annotations/deprecation.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/annotations/deprecation.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/annotations/deprecation.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/annotations/deprecation.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/annotations/resource.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/annotations/resource.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/annotations/resource.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/annotations/resource.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/accesslog/v3/accesslog.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/accesslog/v3/accesslog.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/accesslog/v3/accesslog.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/accesslog/v3/accesslog.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/circuit_breaker.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/circuit_breaker.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/circuit_breaker.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/circuit_breaker.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/cluster.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/cluster.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/cluster.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/cluster.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/filter.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/filter.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/filter.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/filter.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/outlier_detection.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/outlier_detection.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/outlier_detection.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/cluster/v3/outlier_detection.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/address.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/address.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/address.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/address.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/backoff.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/backoff.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/backoff.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/backoff.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/base.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/base.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/base.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/base.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/config_source.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/config_source.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/config_source.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/config_source.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/event_service_config.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/event_service_config.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/event_service_config.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/event_service_config.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/extension.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/extension.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/extension.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/extension.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/grpc_method_list.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/grpc_method_list.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/grpc_method_list.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/grpc_method_list.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/grpc_service.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/grpc_service.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/grpc_service.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/grpc_service.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/health_check.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/health_check.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/health_check.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/health_check.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/http_service.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/http_service.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/http_service.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/http_service.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/http_uri.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/http_uri.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/http_uri.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/http_uri.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/protocol.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/protocol.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/protocol.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/protocol.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/proxy_protocol.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/proxy_protocol.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/proxy_protocol.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/proxy_protocol.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/resolver.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/resolver.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/resolver.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/resolver.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/socket_cmsg_headers.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/socket_cmsg_headers.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/socket_cmsg_headers.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/socket_cmsg_headers.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/socket_option.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/socket_option.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/socket_option.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/socket_option.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/substitution_format_string.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/substitution_format_string.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/substitution_format_string.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/substitution_format_string.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/udp_socket_config.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/udp_socket_config.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/udp_socket_config.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/core/v3/udp_socket_config.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/endpoint/v3/endpoint.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/endpoint/v3/endpoint.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/endpoint/v3/endpoint.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/endpoint/v3/endpoint.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/endpoint/v3/endpoint_components.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/endpoint/v3/endpoint_components.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/endpoint/v3/endpoint_components.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/endpoint/v3/endpoint_components.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/endpoint/v3/load_report.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/endpoint/v3/load_report.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/endpoint/v3/load_report.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/endpoint/v3/load_report.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/api_listener.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/api_listener.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/api_listener.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/api_listener.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/listener.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/listener.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/listener.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/listener.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/listener_components.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/listener_components.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/listener_components.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/listener_components.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/quic_config.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/quic_config.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/quic_config.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/quic_config.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/udp_listener_config.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/udp_listener_config.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/udp_listener_config.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/listener/v3/udp_listener_config.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/rbac/v3/rbac.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/rbac/v3/rbac.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/rbac/v3/rbac.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/rbac/v3/rbac.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/route/v3/route.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/route/v3/route.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/route/v3/route.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/route/v3/route.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/route/v3/route_components.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/route/v3/route_components.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/route/v3/route_components.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/route/v3/route_components.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/route/v3/scoped_route.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/route/v3/scoped_route.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/route/v3/scoped_route.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/route/v3/scoped_route.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/datadog.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/datadog.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/datadog.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/datadog.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/dynamic_ot.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/dynamic_ot.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/dynamic_ot.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/dynamic_ot.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/http_tracer.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/http_tracer.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/http_tracer.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/http_tracer.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/lightstep.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/lightstep.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/lightstep.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/lightstep.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/opentelemetry.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/opentelemetry.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/opentelemetry.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/opentelemetry.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/service.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/service.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/service.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/service.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/skywalking.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/skywalking.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/skywalking.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/skywalking.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/trace.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/trace.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/trace.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/trace.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/xray.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/xray.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/xray.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/xray.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/zipkin.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/zipkin.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/zipkin.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/config/trace/v3/zipkin.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/data/accesslog/v3/accesslog.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/data/accesslog/v3/accesslog.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/data/accesslog/v3/accesslog.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/data/accesslog/v3/accesslog.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/clusters/aggregate/v3/cluster.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/clusters/aggregate/v3/cluster.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/clusters/aggregate/v3/cluster.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/clusters/aggregate/v3/cluster.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/filters/http/rbac/v3/rbac.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/filters/http/rbac/v3/rbac.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/filters/http/rbac/v3/rbac.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/filters/http/rbac/v3/rbac.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/filters/http/router/v3/router.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/filters/http/router/v3/router.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/filters/http/router/v3/router.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/filters/http/router/v3/router.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/cert.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/cert.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/cert.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/cert.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/common.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/common.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/common.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/common.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/secret.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/secret.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/secret.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/secret.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/tls.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/tls.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/tls.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/tls.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/http/v3/cookie.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/http/v3/cookie.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/http/v3/cookie.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/http/v3/cookie.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/http/v3/path_transformation.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/http/v3/path_transformation.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/http/v3/path_transformation.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/http/v3/path_transformation.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/address.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/address.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/address.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/address.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/filter_state.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/filter_state.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/filter_state.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/filter_state.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/http_inputs.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/http_inputs.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/http_inputs.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/http_inputs.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/metadata.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/metadata.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/metadata.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/metadata.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/node.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/node.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/node.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/node.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/number.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/number.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/number.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/number.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/path.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/path.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/path.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/path.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/regex.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/regex.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/regex.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/regex.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/status_code_input.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/status_code_input.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/status_code_input.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/status_code_input.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/string.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/string.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/string.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/string.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/struct.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/struct.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/struct.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/struct.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/value.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/value.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/value.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/matcher/v3/value.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/metadata/v3/metadata.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/metadata/v3/metadata.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/metadata/v3/metadata.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/metadata/v3/metadata.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/tracing/v3/custom_tag.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/tracing/v3/custom_tag.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/tracing/v3/custom_tag.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/tracing/v3/custom_tag.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/hash_policy.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/hash_policy.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/hash_policy.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/hash_policy.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/http.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/http.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/http.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/http.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/http_status.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/http_status.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/http_status.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/http_status.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/percent.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/percent.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/percent.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/percent.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/range.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/range.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/range.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/range.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/ratelimit_strategy.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/ratelimit_strategy.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/ratelimit_strategy.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/ratelimit_strategy.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/ratelimit_unit.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/ratelimit_unit.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/ratelimit_unit.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/ratelimit_unit.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/semantic_version.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/semantic_version.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/semantic_version.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/semantic_version.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/token_bucket.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/token_bucket.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/token_bucket.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/envoy/type/v3/token_bucket.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/api/annotations.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/api/annotations.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/api/annotations.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/api/annotations.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/api/expr/v1alpha1/checked.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/api/expr/v1alpha1/checked.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/api/expr/v1alpha1/checked.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/api/expr/v1alpha1/checked.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/api/expr/v1alpha1/syntax.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/api/expr/v1alpha1/syntax.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/api/expr/v1alpha1/syntax.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/api/expr/v1alpha1/syntax.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/api/http.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/api/http.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/api/http.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/api/http.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/api/httpbody.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/api/httpbody.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/api/httpbody.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/api/httpbody.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/rpc/status.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/rpc/status.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/google/rpc/status.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/google/rpc/status.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/validate/validate.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/validate/validate.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/validate/validate.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/validate/validate.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/migrate.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/migrate.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/migrate.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/migrate.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/security.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/security.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/security.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/security.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/sensitive.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/sensitive.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/sensitive.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/sensitive.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/status.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/status.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/status.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/status.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/versioning.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/versioning.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/versioning.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/udpa/annotations/versioning.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/migrate.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/migrate.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/migrate.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/migrate.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/security.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/security.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/security.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/security.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/sensitive.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/sensitive.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/sensitive.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/sensitive.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/status.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/status.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/status.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/status.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/versioning.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/versioning.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/versioning.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/annotations/v3/versioning.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/authority.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/authority.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/authority.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/authority.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/cidr.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/cidr.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/cidr.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/cidr.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/collection_entry.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/collection_entry.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/collection_entry.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/collection_entry.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/context_params.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/context_params.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/context_params.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/context_params.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/extension.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/extension.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/extension.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/extension.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/resource.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/resource.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/resource.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/resource.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/resource_locator.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/resource_locator.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/resource_locator.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/resource_locator.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/resource_name.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/resource_name.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/resource_name.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/core/v3/resource_name.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/data/orca/v3/orca_load_report.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/data/orca/v3/orca_load_report.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/data/orca/v3/orca_load_report.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/data/orca/v3/orca_load_report.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/cel.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/cel.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/cel.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/cel.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/domain.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/domain.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/domain.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/domain.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/http_inputs.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/http_inputs.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/http_inputs.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/http_inputs.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/ip.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/ip.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/ip.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/ip.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/matcher.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/matcher.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/matcher.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/matcher.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/range.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/range.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/range.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/range.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/regex.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/regex.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/regex.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/regex.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/string.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/string.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/string.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/matcher/v3/string.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/v3/cel.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/v3/cel.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/v3/cel.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/v3/cel.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/v3/range.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/v3/range.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/v3/range.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/v3/range.grpc.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/v3/typed_struct.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/v3/typed_struct.grpc.pb.cc
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/v3/typed_struct.pb.h
+ ${_gRPC_PROTO_GENS_DIR}/xds/type/v3/typed_struct.grpc.pb.h
+ test/core/test_util/audit_logging_utils.cc
+ test/cpp/end2end/test_service_impl.cc
+ test/cpp/end2end/xds/xds_end2end_test_lib.cc
+ test/cpp/end2end/xds/xds_security_spiffe_end2end_test.cc
+ test/cpp/end2end/xds/xds_server.cc
+ test/cpp/end2end/xds/xds_utils.cc
+ test/cpp/util/tls_test_utils.cc
+ )
+ if(WIN32 AND MSVC)
+ if(BUILD_SHARED_LIBS)
+ target_compile_definitions(xds_security_spiffe_end2end_test
+ PRIVATE
+ "GPR_DLL_IMPORTS"
+ "GRPC_DLL_IMPORTS"
+ "GRPCXX_DLL_IMPORTS"
+ )
+ endif()
+ endif()
+ target_compile_features(xds_security_spiffe_end2end_test PUBLIC cxx_std_17)
+ target_include_directories(xds_security_spiffe_end2end_test
+ PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}/include
+ ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
+ ${_gRPC_RE2_INCLUDE_DIR}
+ ${_gRPC_SSL_INCLUDE_DIR}
+ ${_gRPC_UPB_GENERATED_DIR}
+ ${_gRPC_UPB_GRPC_GENERATED_DIR}
+ ${_gRPC_UPB_INCLUDE_DIR}
+ ${_gRPC_XXHASH_INCLUDE_DIR}
+ ${_gRPC_ZLIB_INCLUDE_DIR}
+ third_party/googletest/googletest/include
+ third_party/googletest/googletest
+ third_party/googletest/googlemock/include
+ third_party/googletest/googlemock
+ ${_gRPC_PROTO_GENS_DIR}
+ )
+
+ target_link_libraries(xds_security_spiffe_end2end_test
+ ${_gRPC_ALLTARGETS_LIBRARIES}
+ gtest
+ grpc++_test_config
+ grpc++_test_util
+ )
+
+
+endif()
+endif()
+if(gRPC_BUILD_TESTS)
add_executable(xds_stats_watcher_test
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/empty.pb.cc
diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml
index dd34653..9d42cfe 100644
--- a/build_autogenerated.yaml
+++ b/build_autogenerated.yaml
@@ -18949,6 +18949,42 @@
deps:
- gtest
uses_polling: false
+- name: spiffe_bundle_map_end2end_test
+ gtest: true
+ build: test
+ language: c++
+ headers:
+ - test/cpp/end2end/test_service_impl.h
+ src:
+ - src/proto/grpc/testing/echo.proto
+ - src/proto/grpc/testing/echo_messages.proto
+ - src/proto/grpc/testing/simple_messages.proto
+ - third_party/googleapis/google/api/annotations.proto
+ - third_party/googleapis/google/api/http.proto
+ - third_party/googleapis/google/rpc/status.proto
+ - third_party/protoc-gen-validate/validate/validate.proto
+ - third_party/xds/xds/data/orca/v3/orca_load_report.proto
+ - test/cpp/end2end/spiffe_bundle_map_end2end_test.cc
+ - test/cpp/end2end/test_service_impl.cc
+ deps:
+ - gtest
+ - grpc++_test_util
+- name: spiffe_ssl_transport_security_test
+ gtest: true
+ build: test
+ language: c++
+ headers:
+ - test/core/tsi/transport_security_test_lib.h
+ src:
+ - test/core/tsi/spiffe_ssl_transport_security_test.cc
+ - test/core/tsi/transport_security_test_lib.cc
+ deps:
+ - gtest
+ - grpc_test_util
+ platforms:
+ - linux
+ - posix
+ - mac
- name: spiffe_utils_test
gtest: true
build: test
@@ -24100,6 +24136,162 @@
- linux
- posix
- mac
+- name: xds_security_spiffe_end2end_test
+ gtest: true
+ build: test
+ language: c++
+ headers:
+ - test/core/test_util/audit_logging_utils.h
+ - test/core/test_util/scoped_env_var.h
+ - test/cpp/end2end/counted_service.h
+ - test/cpp/end2end/test_service_impl.h
+ - test/cpp/end2end/xds/xds_end2end_test_lib.h
+ - test/cpp/end2end/xds/xds_server.h
+ - test/cpp/end2end/xds/xds_utils.h
+ - test/cpp/util/tls_test_utils.h
+ - third_party/protoc-gen-validate/validate/validate.h
+ src:
+ - src/proto/grpc/testing/duplicate/echo_duplicate.proto
+ - src/proto/grpc/testing/echo.proto
+ - src/proto/grpc/testing/echo_messages.proto
+ - src/proto/grpc/testing/simple_messages.proto
+ - src/proto/grpc/testing/xds/v3/ads.proto
+ - src/proto/grpc/testing/xds/v3/discovery.proto
+ - src/proto/grpc/testing/xds/v3/lrs.proto
+ - third_party/envoy-api/envoy/annotations/deprecation.proto
+ - third_party/envoy-api/envoy/annotations/resource.proto
+ - third_party/envoy-api/envoy/config/accesslog/v3/accesslog.proto
+ - third_party/envoy-api/envoy/config/cluster/v3/circuit_breaker.proto
+ - third_party/envoy-api/envoy/config/cluster/v3/cluster.proto
+ - third_party/envoy-api/envoy/config/cluster/v3/filter.proto
+ - third_party/envoy-api/envoy/config/cluster/v3/outlier_detection.proto
+ - third_party/envoy-api/envoy/config/core/v3/address.proto
+ - third_party/envoy-api/envoy/config/core/v3/backoff.proto
+ - third_party/envoy-api/envoy/config/core/v3/base.proto
+ - third_party/envoy-api/envoy/config/core/v3/config_source.proto
+ - third_party/envoy-api/envoy/config/core/v3/event_service_config.proto
+ - third_party/envoy-api/envoy/config/core/v3/extension.proto
+ - third_party/envoy-api/envoy/config/core/v3/grpc_method_list.proto
+ - third_party/envoy-api/envoy/config/core/v3/grpc_service.proto
+ - third_party/envoy-api/envoy/config/core/v3/health_check.proto
+ - third_party/envoy-api/envoy/config/core/v3/http_service.proto
+ - third_party/envoy-api/envoy/config/core/v3/http_uri.proto
+ - third_party/envoy-api/envoy/config/core/v3/protocol.proto
+ - third_party/envoy-api/envoy/config/core/v3/proxy_protocol.proto
+ - third_party/envoy-api/envoy/config/core/v3/resolver.proto
+ - third_party/envoy-api/envoy/config/core/v3/socket_cmsg_headers.proto
+ - third_party/envoy-api/envoy/config/core/v3/socket_option.proto
+ - third_party/envoy-api/envoy/config/core/v3/substitution_format_string.proto
+ - third_party/envoy-api/envoy/config/core/v3/udp_socket_config.proto
+ - third_party/envoy-api/envoy/config/endpoint/v3/endpoint.proto
+ - third_party/envoy-api/envoy/config/endpoint/v3/endpoint_components.proto
+ - third_party/envoy-api/envoy/config/endpoint/v3/load_report.proto
+ - third_party/envoy-api/envoy/config/listener/v3/api_listener.proto
+ - third_party/envoy-api/envoy/config/listener/v3/listener.proto
+ - third_party/envoy-api/envoy/config/listener/v3/listener_components.proto
+ - third_party/envoy-api/envoy/config/listener/v3/quic_config.proto
+ - third_party/envoy-api/envoy/config/listener/v3/udp_listener_config.proto
+ - third_party/envoy-api/envoy/config/rbac/v3/rbac.proto
+ - third_party/envoy-api/envoy/config/route/v3/route.proto
+ - third_party/envoy-api/envoy/config/route/v3/route_components.proto
+ - third_party/envoy-api/envoy/config/route/v3/scoped_route.proto
+ - third_party/envoy-api/envoy/config/trace/v3/datadog.proto
+ - third_party/envoy-api/envoy/config/trace/v3/dynamic_ot.proto
+ - third_party/envoy-api/envoy/config/trace/v3/http_tracer.proto
+ - third_party/envoy-api/envoy/config/trace/v3/lightstep.proto
+ - third_party/envoy-api/envoy/config/trace/v3/opentelemetry.proto
+ - third_party/envoy-api/envoy/config/trace/v3/service.proto
+ - third_party/envoy-api/envoy/config/trace/v3/skywalking.proto
+ - third_party/envoy-api/envoy/config/trace/v3/trace.proto
+ - third_party/envoy-api/envoy/config/trace/v3/xray.proto
+ - third_party/envoy-api/envoy/config/trace/v3/zipkin.proto
+ - third_party/envoy-api/envoy/data/accesslog/v3/accesslog.proto
+ - third_party/envoy-api/envoy/extensions/clusters/aggregate/v3/cluster.proto
+ - third_party/envoy-api/envoy/extensions/filters/http/rbac/v3/rbac.proto
+ - third_party/envoy-api/envoy/extensions/filters/http/router/v3/router.proto
+ - third_party/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto
+ - third_party/envoy-api/envoy/extensions/transport_sockets/tls/v3/cert.proto
+ - third_party/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto
+ - third_party/envoy-api/envoy/extensions/transport_sockets/tls/v3/secret.proto
+ - third_party/envoy-api/envoy/extensions/transport_sockets/tls/v3/tls.proto
+ - third_party/envoy-api/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto
+ - third_party/envoy-api/envoy/type/http/v3/cookie.proto
+ - third_party/envoy-api/envoy/type/http/v3/path_transformation.proto
+ - third_party/envoy-api/envoy/type/matcher/v3/address.proto
+ - third_party/envoy-api/envoy/type/matcher/v3/filter_state.proto
+ - third_party/envoy-api/envoy/type/matcher/v3/http_inputs.proto
+ - third_party/envoy-api/envoy/type/matcher/v3/metadata.proto
+ - third_party/envoy-api/envoy/type/matcher/v3/node.proto
+ - third_party/envoy-api/envoy/type/matcher/v3/number.proto
+ - third_party/envoy-api/envoy/type/matcher/v3/path.proto
+ - third_party/envoy-api/envoy/type/matcher/v3/regex.proto
+ - third_party/envoy-api/envoy/type/matcher/v3/status_code_input.proto
+ - third_party/envoy-api/envoy/type/matcher/v3/string.proto
+ - third_party/envoy-api/envoy/type/matcher/v3/struct.proto
+ - third_party/envoy-api/envoy/type/matcher/v3/value.proto
+ - third_party/envoy-api/envoy/type/metadata/v3/metadata.proto
+ - third_party/envoy-api/envoy/type/tracing/v3/custom_tag.proto
+ - third_party/envoy-api/envoy/type/v3/hash_policy.proto
+ - third_party/envoy-api/envoy/type/v3/http.proto
+ - third_party/envoy-api/envoy/type/v3/http_status.proto
+ - third_party/envoy-api/envoy/type/v3/percent.proto
+ - third_party/envoy-api/envoy/type/v3/range.proto
+ - third_party/envoy-api/envoy/type/v3/ratelimit_strategy.proto
+ - third_party/envoy-api/envoy/type/v3/ratelimit_unit.proto
+ - third_party/envoy-api/envoy/type/v3/semantic_version.proto
+ - third_party/envoy-api/envoy/type/v3/token_bucket.proto
+ - third_party/googleapis/google/api/annotations.proto
+ - third_party/googleapis/google/api/expr/v1alpha1/checked.proto
+ - third_party/googleapis/google/api/expr/v1alpha1/syntax.proto
+ - third_party/googleapis/google/api/http.proto
+ - third_party/googleapis/google/api/httpbody.proto
+ - third_party/googleapis/google/rpc/status.proto
+ - third_party/protoc-gen-validate/validate/validate.proto
+ - third_party/xds/udpa/annotations/migrate.proto
+ - third_party/xds/udpa/annotations/security.proto
+ - third_party/xds/udpa/annotations/sensitive.proto
+ - third_party/xds/udpa/annotations/status.proto
+ - third_party/xds/udpa/annotations/versioning.proto
+ - third_party/xds/xds/annotations/v3/migrate.proto
+ - third_party/xds/xds/annotations/v3/security.proto
+ - third_party/xds/xds/annotations/v3/sensitive.proto
+ - third_party/xds/xds/annotations/v3/status.proto
+ - third_party/xds/xds/annotations/v3/versioning.proto
+ - third_party/xds/xds/core/v3/authority.proto
+ - third_party/xds/xds/core/v3/cidr.proto
+ - third_party/xds/xds/core/v3/collection_entry.proto
+ - third_party/xds/xds/core/v3/context_params.proto
+ - third_party/xds/xds/core/v3/extension.proto
+ - third_party/xds/xds/core/v3/resource.proto
+ - third_party/xds/xds/core/v3/resource_locator.proto
+ - third_party/xds/xds/core/v3/resource_name.proto
+ - third_party/xds/xds/data/orca/v3/orca_load_report.proto
+ - third_party/xds/xds/type/matcher/v3/cel.proto
+ - third_party/xds/xds/type/matcher/v3/domain.proto
+ - third_party/xds/xds/type/matcher/v3/http_inputs.proto
+ - third_party/xds/xds/type/matcher/v3/ip.proto
+ - third_party/xds/xds/type/matcher/v3/matcher.proto
+ - third_party/xds/xds/type/matcher/v3/range.proto
+ - third_party/xds/xds/type/matcher/v3/regex.proto
+ - third_party/xds/xds/type/matcher/v3/string.proto
+ - third_party/xds/xds/type/v3/cel.proto
+ - third_party/xds/xds/type/v3/range.proto
+ - third_party/xds/xds/type/v3/typed_struct.proto
+ - test/core/test_util/audit_logging_utils.cc
+ - test/cpp/end2end/test_service_impl.cc
+ - test/cpp/end2end/xds/xds_end2end_test_lib.cc
+ - test/cpp/end2end/xds/xds_security_spiffe_end2end_test.cc
+ - test/cpp/end2end/xds/xds_server.cc
+ - test/cpp/end2end/xds/xds_utils.cc
+ - test/cpp/util/tls_test_utils.cc
+ deps:
+ - gtest
+ - grpc++_test_config
+ - grpc++_test_util
+ platforms:
+ - linux
+ - posix
+ - mac
- name: xds_stats_watcher_test
gtest: true
build: test
diff --git a/include/grpc/credentials.h b/include/grpc/credentials.h
index f4a614d..eb6c06b 100644
--- a/include/grpc/credentials.h
+++ b/include/grpc/credentials.h
@@ -656,6 +656,11 @@
* be null if no identity credentials are needed.
* - root_cert_path is the file path to the root certificate bundle. This
* may be null if no root certs are needed.
+ * - spiffe_bundle_map_path is the file path to the SPIFFE Bundle Map. If
+ * configured, this will be used to find the roots of trust for a given SPIFFE
+ * domain during verification. See
+ * https://github.com/grpc/proposal/blob/master/A87-mtls-spiffe-support.md for
+ * more details on SPIFFE verification.
* - refresh_interval_sec is the refreshing interval that we will check the
* files for updates.
* It does not take ownership of parameters.
@@ -663,7 +668,8 @@
GRPCAPI grpc_tls_certificate_provider*
grpc_tls_certificate_provider_file_watcher_create(
const char* private_key_path, const char* identity_certificate_path,
- const char* root_cert_path, unsigned int refresh_interval_sec);
+ const char* root_cert_path, const char* spiffe_bundle_map_path,
+ unsigned int refresh_interval_sec);
/**
* EXPERIMENTAL API - Subject to change
diff --git a/include/grpcpp/security/tls_certificate_provider.h b/include/grpcpp/security/tls_certificate_provider.h
index 7912e4c..d85785c 100644
--- a/include/grpcpp/security/tls_certificate_provider.h
+++ b/include/grpcpp/security/tls_certificate_provider.h
@@ -109,19 +109,27 @@
FileWatcherCertificateProvider(const std::string& private_key_path,
const std::string& identity_certificate_path,
const std::string& root_cert_path,
+ const std::string& spiffe_bundle_map_path,
unsigned int refresh_interval_sec);
// Constructor to get credential updates from identity file paths only.
FileWatcherCertificateProvider(const std::string& private_key_path,
const std::string& identity_certificate_path,
unsigned int refresh_interval_sec)
: FileWatcherCertificateProvider(private_key_path,
- identity_certificate_path, "",
+ identity_certificate_path, "", "",
refresh_interval_sec) {}
// Constructor to get credential updates from root file path only.
FileWatcherCertificateProvider(const std::string& root_cert_path,
unsigned int refresh_interval_sec)
- : FileWatcherCertificateProvider("", "", root_cert_path,
+ : FileWatcherCertificateProvider("", "", root_cert_path, "",
refresh_interval_sec) {}
+ FileWatcherCertificateProvider(const std::string& private_key_path,
+ const std::string& identity_certificate_path,
+ const std::string& root_cert_path,
+ unsigned int refresh_interval_sec)
+ : FileWatcherCertificateProvider(
+ private_key_path, identity_certificate_path, root_cert_path, "",
+ refresh_interval_sec) {}
~FileWatcherCertificateProvider() override;
diff --git a/src/core/credentials/transport/tls/grpc_tls_certificate_provider.cc b/src/core/credentials/transport/tls/grpc_tls_certificate_provider.cc
index 940c6a0..2855c1e 100644
--- a/src/core/credentials/transport/tls/grpc_tls_certificate_provider.cc
+++ b/src/core/credentials/transport/tls/grpc_tls_certificate_provider.cc
@@ -509,17 +509,18 @@
std::move(root_cert_core), std::move(identity_pairs_core));
}
-// TODO(gtcooke94): Add a parameter to set the spiffe_bundle_map_path
grpc_tls_certificate_provider*
grpc_tls_certificate_provider_file_watcher_create(
const char* private_key_path, const char* identity_certificate_path,
- const char* root_cert_path, unsigned int refresh_interval_sec) {
+ const char* root_cert_path, const char* spiffe_bundle_map_path,
+ unsigned int refresh_interval_sec) {
grpc_core::ExecCtx exec_ctx;
return new grpc_core::FileWatcherCertificateProvider(
private_key_path == nullptr ? "" : private_key_path,
identity_certificate_path == nullptr ? "" : identity_certificate_path,
root_cert_path == nullptr ? "" : root_cert_path,
- /*spiffe_bundle_map_path=*/"", refresh_interval_sec);
+ spiffe_bundle_map_path == nullptr ? "" : spiffe_bundle_map_path,
+ refresh_interval_sec);
}
void grpc_tls_certificate_provider_release(
diff --git a/src/core/credentials/transport/tls/spiffe_utils.cc b/src/core/credentials/transport/tls/spiffe_utils.cc
index b12d7dc..4d9b16d 100644
--- a/src/core/credentials/transport/tls/spiffe_utils.cc
+++ b/src/core/credentials/transport/tls/spiffe_utils.cc
@@ -130,6 +130,11 @@
} // namespace
+std::string AddPemBlockWrapping(absl::string_view spiffe_bundle_root) {
+ return absl::StrCat(kCertificatePrefix, spiffe_bundle_root,
+ kCertificateSuffix);
+}
+
absl::StatusOr<SpiffeId> SpiffeId::FromString(absl::string_view input) {
GRPC_RETURN_IF_ERROR(DoInitialUriValidation(input));
if (!absl::StartsWithIgnoreCase(input, kSpiffePrefix)) {
@@ -202,8 +207,7 @@
}
if (!x5c->empty()) {
ValidationErrors::ScopedField field(errors, "[0]");
- std::string pem_cert =
- absl::StrCat(kCertificatePrefix, (*x5c)[0], kCertificateSuffix);
+ std::string pem_cert = AddPemBlockWrapping((*x5c)[0]);
auto certs = ParsePemCertificateChain(pem_cert);
if (!certs.ok()) {
errors->AddError(certs.status().ToString());
@@ -234,9 +238,53 @@
for (size_t i = 0; i < keys->size(); ++i) {
roots_.emplace_back((*keys)[i].GetRoot());
}
+ ValidationErrors::ScopedField field(errors, "keys");
+ absl::Status status = CreateX509Stack();
+ if (!status.ok()) {
+ errors->AddError(status.ToString());
+ }
}
-absl::Span<const std::string> SpiffeBundle::GetRoots() { return roots_; }
+SpiffeBundle::~SpiffeBundle() {
+ if (root_stack_ != nullptr) {
+ sk_X509_pop_free(*root_stack_, X509_free);
+ }
+}
+
+SpiffeBundle::SpiffeBundle(const SpiffeBundle& other) {
+ roots_ = other.roots_;
+ if (other.root_stack_ != nullptr) {
+ root_stack_ =
+ std::make_unique<STACK_OF(X509)*>(sk_X509_dup(*other.root_stack_));
+ for (size_t i = 0; i < sk_X509_num(*root_stack_); i++) {
+ X509* x = sk_X509_value(*root_stack_, i);
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ CHECK(X509_up_ref(x));
+#else
+ CRYPTO_add(&x->references, 1, CRYPTO_LOCK_X509);
+#endif
+ }
+ }
+}
+
+SpiffeBundle& SpiffeBundle::operator=(const SpiffeBundle& other) {
+ if (this != &other) {
+ roots_ = other.roots_;
+ if (other.root_stack_ != nullptr) {
+ root_stack_ =
+ std::make_unique<STACK_OF(X509)*>(sk_X509_dup(*other.root_stack_));
+ for (size_t i = 0; i < sk_X509_num(*root_stack_); i++) {
+ X509* x = sk_X509_value(*root_stack_, i);
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ CHECK(X509_up_ref(x));
+#else
+ CRYPTO_add(&x->references, 1, CRYPTO_LOCK_X509);
+#endif
+ }
+ }
+ }
+ return *this;
+}
const JsonLoaderInterface* SpiffeBundleMap::JsonLoader(const JsonArgs&) {
static const auto* kLoader =
@@ -246,6 +294,38 @@
return kLoader;
}
+absl::Span<const std::string> SpiffeBundle::GetRoots() { return roots_; }
+
+absl::StatusOr<STACK_OF(X509) *> SpiffeBundle::GetRootStack() {
+ if (root_stack_ == nullptr) {
+ return absl::FailedPreconditionError(
+ "root_stack_ has not been initialized");
+ }
+ return *root_stack_;
+}
+
+absl::Status SpiffeBundle::CreateX509Stack() {
+ root_stack_ = std::make_unique<STACK_OF(X509)*>(sk_X509_new_null());
+ absl::Status status = absl::OkStatus();
+ for (const auto& pem_cert : roots_) {
+ auto cert = ParsePemCertificateChain(AddPemBlockWrapping(pem_cert));
+ if (!cert.status().ok()) {
+ status = cert.status();
+ break;
+ }
+ if (cert->size() != 1) {
+ status = absl::InvalidArgumentError("Got a malformed root certificate.");
+ break;
+ }
+ sk_X509_push(*root_stack_, (*cert)[0]);
+ }
+ // If there was an error parsing we don't want a partially filled root stack.
+ if (!status.ok()) {
+ sk_X509_pop_free(*root_stack_, X509_free);
+ }
+ return status;
+}
+
void SpiffeBundleMap::JsonPostLoad(const Json&, const JsonArgs&,
ValidationErrors* errors) {
{
@@ -279,4 +359,13 @@
"No spiffe bundle found for trust domain %s", trust_domain));
}
-} // namespace grpc_core
\ No newline at end of file
+absl::StatusOr<STACK_OF(X509) *> SpiffeBundleMap::GetRootStack(
+ absl::string_view trust_domain) {
+ if (auto it = bundles_.find(trust_domain); it != bundles_.end()) {
+ return it->second.GetRootStack();
+ }
+ return absl::NotFoundError(absl::StrFormat(
+ "No spiffe bundle found for trust domain %s", trust_domain));
+}
+
+} // namespace grpc_core
diff --git a/src/core/credentials/transport/tls/spiffe_utils.h b/src/core/credentials/transport/tls/spiffe_utils.h
index 742fe4f..1c4f011 100644
--- a/src/core/credentials/transport/tls/spiffe_utils.h
+++ b/src/core/credentials/transport/tls/spiffe_utils.h
@@ -19,6 +19,9 @@
#ifndef GRPC_SRC_CORE_CREDENTIALS_TRANSPORT_TLS_SPIFFE_UTILS_H
#define GRPC_SRC_CORE_CREDENTIALS_TRANSPORT_TLS_SPIFFE_UTILS_H
+#include <openssl/stack.h>
+#include <openssl/x509.h>
+
#include <string>
#include "absl/status/statusor.h"
@@ -28,6 +31,10 @@
namespace grpc_core {
+// Adds the leading and trailing lines expected for a PEM formatted certificate
+// around the raw base64 certificate data stored in a SPIFFE bundle map.
+std::string AddPemBlockWrapping(absl::string_view spiffe_bundle_root);
+
// A representation of a SPIFFE ID per the spec:
// https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#the-spiffe-identity-and-verifiable-identity-document
class SpiffeId final {
@@ -72,8 +79,17 @@
// domain.
// https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Trust_Domain_and_Bundle.md#3-spiffe-bundles
// https://github.com/grpc/proposal/blob/master/A87-mtls-spiffe-support.md
+// Not thread-safe
class SpiffeBundle final {
public:
+ // Do not use - only exists to work with the JSON library.
+ // SpiffeBundles should be used by loading a SpiffeBundleMap via
+ // SpiffeBundleMap::FromFile
+ SpiffeBundle() = default;
+ ~SpiffeBundle();
+ SpiffeBundle(const SpiffeBundle& other);
+ SpiffeBundle& operator=(const SpiffeBundle& other);
+
static const JsonLoaderInterface* JsonLoader(const JsonArgs&);
void JsonPostLoad(const Json& json, const JsonArgs&,
ValidationErrors* errors);
@@ -81,7 +97,12 @@
// Returns a vector of the roots in this SPIFFE Bundle.
absl::Span<const std::string> GetRoots();
+ // The caller does not take ownership of the stack of roots.
+ // The caller MUST NOT mutate this value.
+ absl::StatusOr<STACK_OF(X509) *> GetRootStack();
+
bool operator==(const SpiffeBundle& other) const {
+ // For our purposes SPIFFE Bundles are equal if their roots are the same.
return roots_ == other.roots_;
}
@@ -90,7 +111,11 @@
}
private:
+ // Constructs the `root_stack_` OpenSSL representation of the roots.
+ absl::Status CreateX509Stack();
+
std::vector<std::string> roots_;
+ std::unique_ptr<STACK_OF(X509)*> root_stack_;
};
// A map of SPIFFE bundles keyed to trust domains. This functions as a map of a
@@ -99,6 +124,7 @@
// https://github.com/grpc/proposal/blob/master/A87-mtls-spiffe-support.md
// https://github.com/grpc/proposal/blob/master/A87-mtls-spiffe-support.md
// Only configuring X509 roots is supported.
+// Not thread-safe
class SpiffeBundleMap final {
public:
static const JsonLoaderInterface* JsonLoader(const JsonArgs&);
@@ -116,6 +142,9 @@
absl::StatusOr<absl::Span<const std::string>> GetRoots(
absl::string_view trust_domain);
+ // The caller does not take ownership of the stack of roots.
+ absl::StatusOr<STACK_OF(X509) *> GetRootStack(
+ const absl::string_view trust_domain);
size_t size() const { return bundles_.size(); }
bool operator==(const SpiffeBundleMap& other) const {
diff --git a/src/core/tsi/ssl_transport_security.cc b/src/core/tsi/ssl_transport_security.cc
index e763e51..8dd5b18 100644
--- a/src/core/tsi/ssl_transport_security.cc
+++ b/src/core/tsi/ssl_transport_security.cc
@@ -253,6 +253,7 @@
static gpr_once g_init_openssl_once = GPR_ONCE_INIT;
static int g_ssl_ctx_ex_factory_index = -1;
static int g_ssl_ctx_ex_crl_provider_index = -1;
+static int g_ssl_ctx_ex_spiffe_bundle_map_index = -1;
static const unsigned char kSslSessionIdContext[] = {'g', 'r', 'p', 'c'};
static int g_ssl_ex_verified_root_cert_index = -1;
#if !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_NO_ENGINE)
@@ -343,6 +344,10 @@
SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
CHECK_NE(g_ssl_ctx_ex_crl_provider_index, -1);
+ g_ssl_ctx_ex_spiffe_bundle_map_index =
+ SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
+ CHECK_NE(g_ssl_ctx_ex_spiffe_bundle_map_index, -1);
+
g_ssl_ex_verified_root_cert_index = SSL_get_ex_new_index(
0, nullptr, nullptr, nullptr, verified_root_cert_free);
CHECK_NE(g_ssl_ex_verified_root_cert_index, -1);
@@ -1234,6 +1239,113 @@
return 1;
}
+static grpc_core::SpiffeBundleMap* GetSpiffeBundleMap(X509_STORE_CTX* ctx) {
+ CHECK(ctx != nullptr);
+ ERR_clear_error();
+ int ssl_index = SSL_get_ex_data_X509_STORE_CTX_idx();
+ if (ssl_index < 0) {
+ char err_str[256];
+ ERR_error_string_n(ERR_get_error(), err_str, sizeof(err_str));
+ GRPC_TRACE_LOG(tsi, INFO)
+ << "error getting the SSL index from the X509_STORE_CTX while getting "
+ "the SPIFFE Bundle Map: "
+ << err_str;
+ return nullptr;
+ }
+ SSL* ssl = static_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, ssl_index));
+ if (ssl == nullptr) {
+ GRPC_TRACE_LOG(tsi, INFO)
+ << "error while fetching SPIFFE Bundle Map. SSL object is null";
+ return nullptr;
+ }
+ SSL_CTX* ssl_ctx = SSL_get_SSL_CTX(ssl);
+ return static_cast<grpc_core::SpiffeBundleMap*>(
+ SSL_CTX_get_ex_data(ssl_ctx, g_ssl_ctx_ex_spiffe_bundle_map_index));
+}
+
+static absl::StatusOr<std::string> GetSpiffeUriFromCert(X509* cert) {
+ CHECK(cert != nullptr);
+ GENERAL_NAMES* subject_alt_names = static_cast<GENERAL_NAMES*>(
+ X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
+ int uri_count = 0;
+ absl::StatusOr<std::string> spiffe_uri = absl::InvalidArgumentError(
+ "spiffe: no SPIFFE ID found in leaf certificate.");
+ if (subject_alt_names != nullptr) {
+ size_t subject_alt_name_count = sk_GENERAL_NAME_num(subject_alt_names);
+ for (size_t i = 0; i < subject_alt_name_count; i++) {
+ GENERAL_NAME* subject_alt_name =
+ sk_GENERAL_NAME_value(subject_alt_names, TSI_SIZE_AS_SIZE(i));
+ if (subject_alt_name == nullptr) {
+ continue;
+ }
+ if (subject_alt_name->type == GEN_URI) {
+ uri_count++;
+ if (uri_count > 1) {
+ sk_GENERAL_NAME_pop_free(subject_alt_names, GENERAL_NAME_free);
+ return absl::InvalidArgumentError(
+ "spiffe: more than one SAN URI found while doing SPIFFE "
+ "validation. Must "
+ "have exactly one URI SAN that is the SPIFFE ID.");
+ }
+ spiffe_uri = grpc_core::ParseUriString(subject_alt_name);
+ }
+ }
+ }
+ sk_GENERAL_NAME_pop_free(subject_alt_names, GENERAL_NAME_free);
+ GRPC_RETURN_IF_ERROR(spiffe_uri.status());
+ if (spiffe_uri->empty()) {
+ return absl::InvalidArgumentError(
+ "spiffe: no URI SAN found in leaf certificate");
+ }
+ return spiffe_uri;
+}
+
+static absl::StatusOr<std::string> SpiffeTrustDomainFromCert(X509* cert) {
+ CHECK(cert != nullptr);
+ auto subject_name = GetSpiffeUriFromCert(cert);
+ GRPC_RETURN_IF_ERROR(subject_name.status());
+ auto spiffe_id = grpc_core::SpiffeId::FromString(*subject_name);
+ GRPC_RETURN_IF_ERROR(spiffe_id.status());
+ return std::string(spiffe_id->trust_domain());
+}
+
+// Fills ctx's trusted roots with the roots in the SPIFFE Bundle Map that
+// are associated with the to-be-verified leaf certificate's trust domain.
+// For more detail see
+// https://github.com/grpc/proposal/blob/master/A87-mtls-spiffe-support.md
+absl::Status ConfigureSpiffeRoots(
+ X509_STORE_CTX* ctx, grpc_core::SpiffeBundleMap* spiffe_bundle_map) {
+ CHECK(ctx != nullptr);
+ if (spiffe_bundle_map == nullptr) {
+ return absl::InvalidArgumentError(
+ "cannot configure spiffe roots with a nullptr spiffe_bundle_map.");
+ }
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ X509* leaf_cert = X509_STORE_CTX_get0_cert(ctx);
+#else
+ X509* leaf_cert = ctx->cert;
+#endif
+ if (leaf_cert == nullptr) {
+ return absl::InvalidArgumentError(
+ "A SPIFFE bundle map was configured but the leaf cert is null");
+ }
+ absl::StatusOr<std::string> trust_domain =
+ SpiffeTrustDomainFromCert(leaf_cert);
+ GRPC_RETURN_IF_ERROR(trust_domain.status());
+ auto root_stack = spiffe_bundle_map->GetRootStack(*trust_domain);
+ GRPC_RETURN_IF_ERROR(root_stack.status());
+ if (*root_stack == nullptr) {
+ return absl::InvalidArgumentError(
+ "spiffe: root stack in the SPIFFE Bundle Map is nullptr.");
+ }
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ X509_STORE_CTX_set0_trusted_stack(ctx, *root_stack);
+#else
+ X509_STORE_CTX_trusted_stack(ctx, *root_stack);
+#endif
+ return absl::OkStatus();
+}
+
// The custom verification function to set in OpenSSL using
// X509_set_cert_verify_callback. This calls the standard OpenSSL procedure
// (X509_verify_cert), then also extracts the root certificate in the built
@@ -1241,12 +1353,24 @@
// returns 1 on success, indicating a trusted chain to a root of trust was
// found, 0 if a trusted chain could not be built.
static int CustomVerificationFunction(X509_STORE_CTX* ctx, void* arg) {
+ CHECK(ctx != nullptr);
+ grpc_core::SpiffeBundleMap* spiffe_bundle_map = GetSpiffeBundleMap(ctx);
+ if (spiffe_bundle_map != nullptr) {
+ // If a SPIFFE Bundle Map is configured, we'll use
+ // X509_STORE_CTX_set0_trusted_stack to then configure these as the roots
+ // for verification.
+ absl::Status status = ConfigureSpiffeRoots(ctx, spiffe_bundle_map);
+ if (!status.ok()) {
+ VLOG(2) << "Failed to configure SPIFFE roots: " << status;
+ return -1;
+ }
+ }
int ret = X509_verify_cert(ctx);
if (ret <= 0) {
VLOG(2) << "Failed to verify cert chain.";
// Verification failed. We shouldn't expect to have a verified chain, so
- // there is no need to attempt to extract the root cert from it, check for
- // revocation, or check anything else.
+ // there is no need to attempt to extract the root cert from it, check
+ // for revocation, or check anything else.
return ret;
}
grpc_core::experimental::CrlProvider* provider = GetCrlProvider(ctx);
@@ -1260,9 +1384,9 @@
return RootCertExtractCallback(ctx, arg);
}
-// Sets the min and max TLS version of |ssl_context| to |min_tls_version| and
-// |max_tls_version|, respectively. Calling this method is a no-op when using
-// OpenSSL versions < 1.1.
+// Sets the min and max TLS version of |ssl_context| to |min_tls_version|
+// and |max_tls_version|, respectively. Calling this method is a no-op when
+// using OpenSSL versions < 1.1.
static tsi_result tsi_set_min_and_max_tls_versions(
SSL_CTX* ssl_context, tsi_tls_version min_tls_version,
tsi_tls_version max_tls_version) {
@@ -2429,12 +2553,14 @@
ssl_context, pem_root_certs.c_str(), pem_root_certs.size(),
nullptr);
},
- [&](const grpc_core::SpiffeBundleMap&) {
- // TODO(gtcooke94) - implement SPIFFE Bundle Map verification. Crash
- // until this is done.
- grpc_core::Crash(
- "spiffe bundle maps were configured but are not fully "
- "implemented.");
+ [&](const grpc_core::SpiffeBundleMap& spiffe_bundle_map) {
+ X509_STORE* cert_store = SSL_CTX_get_cert_store(ssl_context);
+ X509_STORE_set_flags(cert_store, X509_V_FLAG_PARTIAL_CHAIN |
+ X509_V_FLAG_TRUSTED_FIRST);
+ const void* p = &spiffe_bundle_map;
+ void* map = const_cast<void*>(p);
+ SSL_CTX_set_ex_data(ssl_context,
+ g_ssl_ctx_ex_spiffe_bundle_map_index, map);
});
X509_STORE* cert_store = SSL_CTX_get_cert_store(ssl_context);
#if OPENSSL_VERSION_NUMBER >= 0x10100000
@@ -2657,12 +2783,15 @@
SSL_CTX_set_client_CA_list(impl->ssl_contexts[i], root_names);
}
},
- [&](const grpc_core::SpiffeBundleMap&) {
- // TODO(gtcooke94) - implement SPIFFE Bundle Map verification.
- // Crash until this is done.
- grpc_core::Crash(
- "spiffe bundle maps were configured but are not fully "
- "implemented.");
+ [&](const grpc_core::SpiffeBundleMap& spiffe_bundle_map) {
+ X509_STORE* cert_store =
+ SSL_CTX_get_cert_store(impl->ssl_contexts[i]);
+ X509_STORE_set_flags(cert_store, X509_V_FLAG_PARTIAL_CHAIN |
+ X509_V_FLAG_TRUSTED_FIRST);
+ const void* p = &spiffe_bundle_map;
+ void* map = const_cast<void*>(p);
+ SSL_CTX_set_ex_data(impl->ssl_contexts[i],
+ g_ssl_ctx_ex_spiffe_bundle_map_index, map);
});
if (result != TSI_OK) {
break;
diff --git a/src/core/tsi/ssl_transport_security_utils.cc b/src/core/tsi/ssl_transport_security_utils.cc
index 5c3b828..65301cf 100644
--- a/src/core/tsi/ssl_transport_security_utils.cc
+++ b/src/core/tsi/ssl_transport_security_utils.cc
@@ -428,4 +428,25 @@
return pkey;
}
+absl::StatusOr<std::string> ParseUriString(GENERAL_NAME* subject_alt_name) {
+ if (subject_alt_name == nullptr || subject_alt_name->type != GEN_URI) {
+ return absl::InvalidArgumentError("Could not parse ASN1 string to UTF8");
+ }
+ // This shouldn't be a possible if statement to enter because if the type is
+ // GEN_URI it then by definition should have a d.uniformResourceIdentifier.
+ // But we can still keep it for safety.
+ if (subject_alt_name->d.uniformResourceIdentifier == nullptr) {
+ return absl::InvalidArgumentError("Could not parse ASN1 string to UTF8");
+ }
+ unsigned char* name = nullptr;
+ int name_size =
+ ASN1_STRING_to_UTF8(&name, subject_alt_name->d.uniformResourceIdentifier);
+ if (name_size < 0 || name == nullptr) {
+ OPENSSL_free(name);
+ return absl::InvalidArgumentError("Could not parse ASN1 string to UTF8");
+ }
+ std::string ret(reinterpret_cast<char const*>(name), name_size);
+ OPENSSL_free(name);
+ return ret;
+}
} // namespace grpc_core
diff --git a/src/core/tsi/ssl_transport_security_utils.h b/src/core/tsi/ssl_transport_security_utils.h
index 038b40d..d71847a 100644
--- a/src/core/tsi/ssl_transport_security_utils.h
+++ b/src/core/tsi/ssl_transport_security_utils.h
@@ -23,6 +23,7 @@
#include <grpc/support/port_platform.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
+#include <openssl/x509v3.h>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
@@ -177,6 +178,9 @@
// Returns an EVP_PKEY instance parsed from the non-empty PEM private key block
// in private_key_pem. Caller takes ownership of the EVP_PKEY pointer.
absl::StatusOr<EVP_PKEY*> ParsePemPrivateKey(absl::string_view private_key_pem);
+
+// Safely parses a URI from OpenSSL's GENERAL_NAME to a string representation.
+absl::StatusOr<std::string> ParseUriString(GENERAL_NAME* subject_alt_name);
} // namespace grpc_core
#endif // GRPC_SRC_CORE_TSI_SSL_TRANSPORT_SECURITY_UTILS_H
diff --git a/src/cpp/common/tls_certificate_provider.cc b/src/cpp/common/tls_certificate_provider.cc
index da0d3b1..5c6123a 100644
--- a/src/cpp/common/tls_certificate_provider.cc
+++ b/src/cpp/common/tls_certificate_provider.cc
@@ -55,10 +55,13 @@
FileWatcherCertificateProvider::FileWatcherCertificateProvider(
const std::string& private_key_path,
const std::string& identity_certificate_path,
- const std::string& root_cert_path, unsigned int refresh_interval_sec) {
+ const std::string& root_cert_path,
+ const std::string& spiffe_bundle_map_path,
+ unsigned int refresh_interval_sec) {
c_provider_ = grpc_tls_certificate_provider_file_watcher_create(
private_key_path.c_str(), identity_certificate_path.c_str(),
- root_cert_path.c_str(), refresh_interval_sec);
+ root_cert_path.c_str(), spiffe_bundle_map_path.c_str(),
+ refresh_interval_sec);
CHECK_NE(c_provider_, nullptr);
};
diff --git a/src/ruby/ext/grpc/rb_grpc_imports.generated.h b/src/ruby/ext/grpc/rb_grpc_imports.generated.h
index 6f84be3..0a4baa2 100644
--- a/src/ruby/ext/grpc/rb_grpc_imports.generated.h
+++ b/src/ruby/ext/grpc/rb_grpc_imports.generated.h
@@ -207,7 +207,7 @@
typedef grpc_tls_certificate_provider*(*grpc_tls_certificate_provider_static_data_create_type)(const char* root_certificate, grpc_tls_identity_pairs* pem_key_cert_pairs);
extern grpc_tls_certificate_provider_static_data_create_type grpc_tls_certificate_provider_static_data_create_import;
#define grpc_tls_certificate_provider_static_data_create grpc_tls_certificate_provider_static_data_create_import
-typedef grpc_tls_certificate_provider*(*grpc_tls_certificate_provider_file_watcher_create_type)(const char* private_key_path, const char* identity_certificate_path, const char* root_cert_path, unsigned int refresh_interval_sec);
+typedef grpc_tls_certificate_provider*(*grpc_tls_certificate_provider_file_watcher_create_type)(const char* private_key_path, const char* identity_certificate_path, const char* root_cert_path, const char* spiffe_bundle_map_path, unsigned int refresh_interval_sec);
extern grpc_tls_certificate_provider_file_watcher_create_type grpc_tls_certificate_provider_file_watcher_create_import;
#define grpc_tls_certificate_provider_file_watcher_create grpc_tls_certificate_provider_file_watcher_create_import
typedef void(*grpc_tls_certificate_provider_release_type)(grpc_tls_certificate_provider* provider);
diff --git a/test/core/credentials/transport/tls/spiffe_utils_test.cc b/test/core/credentials/transport/tls/spiffe_utils_test.cc
index 8c981b0..87bb9d6 100644
--- a/test/core/credentials/transport/tls/spiffe_utils_test.cc
+++ b/test/core/credentials/transport/tls/spiffe_utils_test.cc
@@ -68,6 +68,37 @@
}
return cert;
}
+
+bool CompareX509(const X509* a, const X509* b) {
+ if (a == b) return true; // both null or same ptr
+ if (a == nullptr || b == nullptr) {
+ return false; // One is null, the other is not, not equal
+ }
+ return X509_cmp(a, b) == 0; // Uses internal comparison
+}
+
+// Function to compare two STACK_OF(X509)
+bool X509StacksEqual(const STACK_OF(X509) * stack1,
+ const STACK_OF(X509) * stack2) {
+ if (stack1 == stack2) return true; // both null or same ptr
+ if (stack1 == nullptr || stack2 == nullptr) return false;
+
+ size_t num1 = sk_X509_num(stack1);
+ size_t num2 = sk_X509_num(stack2);
+
+ if (num1 != num2) {
+ return false; // Different number of elements
+ }
+
+ for (size_t i = 0; i < num1; ++i) {
+ X509* cert1 = sk_X509_value(stack1, i);
+ X509* cert2 = sk_X509_value(stack2, i);
+ if (!CompareX509(cert1, cert2)) {
+ return false; // Certificates at this index are not equal
+ }
+ }
+ return true; // All certificates are equal
+}
} // namespace
TEST(SpiffeId, EmptyFails) {
@@ -426,8 +457,13 @@
"spiffe_cert.pem");
ASSERT_TRUE(expected_certificate.ok()) << expected_certificate.status();
EXPECT_EQ(X509_cmp(*certificate, *expected_certificate), 0);
+ STACK_OF(X509)* expected_stack = sk_X509_new_null();
+ sk_X509_push(expected_stack, *expected_certificate);
+ auto actual_root_stack = bundle_map->GetRootStack("example.com");
+ ASSERT_TRUE(actual_root_stack.ok());
+ EXPECT_TRUE(X509StacksEqual(*actual_root_stack, expected_stack));
+ sk_X509_pop_free(expected_stack, X509_free);
X509_free(*certificate);
- X509_free(*expected_certificate);
}
{
// check the test.example.com bundle
@@ -441,8 +477,13 @@
"server1_spiffe.pem");
ASSERT_TRUE(expected_certificate.ok()) << expected_certificate.status();
EXPECT_EQ(X509_cmp(*certificate, *expected_certificate), 0);
+ STACK_OF(X509)* expected_stack = sk_X509_new_null();
+ sk_X509_push(expected_stack, *expected_certificate);
+ auto actual_root_stack = bundle_map->GetRootStack("test.example.com");
+ ASSERT_TRUE(actual_root_stack.ok());
+ EXPECT_TRUE(X509StacksEqual(*actual_root_stack, expected_stack));
+ sk_X509_pop_free(expected_stack, X509_free);
X509_free(*certificate);
- X509_free(*expected_certificate);
}
}
@@ -459,30 +500,39 @@
auto roots = bundle_map->GetRoots("example.com");
ASSERT_TRUE(roots.ok());
EXPECT_EQ(roots->size(), 2);
- {
- // Check the first root
- auto certificate = ReadCertificate((*roots)[0]);
- ASSERT_TRUE(certificate.ok()) << certificate.status();
- auto expected_certificate = ReadCertificateFromFile(
- "test/core/credentials/transport/tls/test_data/spiffe/"
- "spiffe_cert.pem");
- ASSERT_TRUE(expected_certificate.ok()) << expected_certificate.status();
- EXPECT_EQ(X509_cmp(*certificate, *expected_certificate), 0);
- X509_free(*certificate);
- X509_free(*expected_certificate);
- }
- {
- // Check the second root
- auto certificate = ReadCertificate((*roots)[1]);
- ASSERT_TRUE(certificate.ok()) << certificate.status();
- auto expected_certificate = ReadCertificateFromFile(
- "test/core/credentials/transport/tls/test_data/spiffe/test_bundles/"
- "server1_spiffe.pem");
- ASSERT_TRUE(expected_certificate.ok()) << expected_certificate.status();
- EXPECT_EQ(X509_cmp(*certificate, *expected_certificate), 0);
- X509_free(*certificate);
- X509_free(*expected_certificate);
- }
+ // Check the first root
+ auto certificate = ReadCertificate((*roots)[0]);
+ ASSERT_TRUE(certificate.ok()) << certificate.status();
+ auto expected_certificate = ReadCertificateFromFile(
+ "test/core/credentials/transport/tls/test_data/spiffe/"
+ "spiffe_cert.pem");
+ ASSERT_TRUE(expected_certificate.ok()) << expected_certificate.status();
+ EXPECT_EQ(X509_cmp(*certificate, *expected_certificate), 0);
+ // Check the second root
+ auto certificate2 = ReadCertificate((*roots)[1]);
+ ASSERT_TRUE(certificate.ok()) << certificate.status();
+ auto expected_certificate2 = ReadCertificateFromFile(
+ "test/core/credentials/transport/tls/test_data/spiffe/test_bundles/"
+ "server1_spiffe.pem");
+ ASSERT_TRUE(expected_certificate2.ok()) << expected_certificate2.status();
+ EXPECT_EQ(X509_cmp(*certificate2, *expected_certificate2), 0);
+
+ STACK_OF(X509)* expected_stack = sk_X509_new_null();
+ sk_X509_push(expected_stack, *expected_certificate);
+ sk_X509_push(expected_stack, *expected_certificate2);
+ auto actual_root_stack = bundle_map->GetRootStack("example.com");
+ ASSERT_TRUE(actual_root_stack.ok());
+ EXPECT_TRUE(X509StacksEqual(*actual_root_stack, expected_stack));
+ sk_X509_pop_free(expected_stack, X509_free);
+ X509_free(*certificate);
+ X509_free(*certificate2);
+}
+
+TEST(SpiffeBundle, BundleRootToPem) {
+ const absl::string_view base = "foo";
+ EXPECT_EQ(AddPemBlockWrapping(base),
+ absl::StrCat("-----BEGIN CERTIFICATE-----\n", base,
+ "\n-----END CERTIFICATE-----"));
}
} // namespace testing
diff --git a/test/core/end2end/fixtures/h2_tls_common.h b/test/core/end2end/fixtures/h2_tls_common.h
index cb95b77..71d43bf 100644
--- a/test/core/end2end/fixtures/h2_tls_common.h
+++ b/test/core/end2end/fixtures/h2_tls_common.h
@@ -104,9 +104,11 @@
}
case SecurityPrimitives::ProviderType::FILE_PROVIDER: {
client_provider_ = grpc_tls_certificate_provider_file_watcher_create(
- SERVER_KEY_PATH, SERVER_CERT_PATH, CA_CERT_PATH, 1);
+ SERVER_KEY_PATH, SERVER_CERT_PATH, CA_CERT_PATH,
+ /*spiffe_bundle_map_path=*/"", 1);
server_provider_ = grpc_tls_certificate_provider_file_watcher_create(
- SERVER_KEY_PATH, SERVER_CERT_PATH, CA_CERT_PATH, 1);
+ SERVER_KEY_PATH, SERVER_CERT_PATH, CA_CERT_PATH,
+ /*spiffe_bundle_map_path=*/"", 1);
break;
}
}
diff --git a/test/core/tsi/BUILD b/test/core/tsi/BUILD
index 8537542..352bdb2 100644
--- a/test/core/tsi/BUILD
+++ b/test/core/tsi/BUILD
@@ -165,6 +165,46 @@
)
grpc_cc_test(
+ name = "spiffe_ssl_transport_security_test",
+ srcs = ["spiffe_ssl_transport_security_test.cc"],
+ data = [
+ "//test/core/tsi/test_creds/crl_data:ca.pem",
+ "//test/core/tsi/test_creds/crl_data:valid.key",
+ "//test/core/tsi/test_creds/crl_data:valid.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:ca.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:client.key",
+ "//test/core/tsi/test_creds/spiffe_end2end:client_spiffe.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:client_spiffebundle.json",
+ "//test/core/tsi/test_creds/spiffe_end2end:intermediate.cnf",
+ "//test/core/tsi/test_creds/spiffe_end2end:intermediate_ca.key",
+ "//test/core/tsi/test_creds/spiffe_end2end:intermediate_ca.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:intermediate_gen.sh",
+ "//test/core/tsi/test_creds/spiffe_end2end:invalid_utf8_san.key",
+ "//test/core/tsi/test_creds/spiffe_end2end:invalid_utf8_san_spiffe.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:leaf_and_intermediate_chain.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:leaf_signed_by_intermediate.key",
+ "//test/core/tsi/test_creds/spiffe_end2end:leaf_signed_by_intermediate.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:multi_san.key",
+ "//test/core/tsi/test_creds/spiffe_end2end:multi_san_spiffe.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:server.key",
+ "//test/core/tsi/test_creds/spiffe_end2end:server_spiffe.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:server_spiffebundle.json",
+ ],
+ external_deps = [
+ "absl/log:check",
+ "gtest",
+ ],
+ tags = ["no_windows"],
+ deps = [
+ ":transport_security_test_lib",
+ "//:gpr",
+ "//:grpc",
+ "//:tsi",
+ "//test/core/test_util:grpc_test_util",
+ ],
+)
+
+grpc_cc_test(
name = "transport_security_test",
srcs = ["transport_security_test.cc"],
external_deps = [
diff --git a/test/core/tsi/spiffe_ssl_transport_security_test.cc b/test/core/tsi/spiffe_ssl_transport_security_test.cc
new file mode 100644
index 0000000..93e75bb
--- /dev/null
+++ b/test/core/tsi/spiffe_ssl_transport_security_test.cc
@@ -0,0 +1,445 @@
+// Copyright 2025 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 <grpc/grpc.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/string_util.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <memory>
+#include <utility>
+
+#include "absl/log/check.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "src/core/credentials/transport/security_connector.h"
+#include "src/core/tsi/ssl_transport_security.h"
+#include "src/core/tsi/transport_security.h"
+#include "src/core/tsi/transport_security_interface.h"
+#include "src/core/util/crash.h"
+#include "test/core/test_util/test_config.h"
+#include "test/core/test_util/tls_utils.h"
+#include "test/core/tsi/transport_security_test_lib.h"
+
+extern "C" {
+#include <openssl/crypto.h>
+#include <openssl/pem.h>
+}
+
+namespace {
+constexpr absl::string_view kCaPemPath =
+ "test/core/tsi/test_creds/spiffe_end2end/ca.pem";
+constexpr absl::string_view kClientKeyPath =
+ "test/core/tsi/test_creds/spiffe_end2end/client.key";
+constexpr absl::string_view kClientCertPath =
+ "test/core/tsi/test_creds/spiffe_end2end/client_spiffe.pem";
+constexpr absl::string_view kServerKeyPath =
+ "test/core/tsi/test_creds/spiffe_end2end/server.key";
+constexpr absl::string_view kServerCertPath =
+ "test/core/tsi/test_creds/spiffe_end2end/server_spiffe.pem";
+constexpr absl::string_view kServerChainKeyPath =
+ "test/core/tsi/test_creds/spiffe_end2end/leaf_signed_by_intermediate.key";
+constexpr absl::string_view kServerChainCertPath =
+ "test/core/tsi/test_creds/spiffe_end2end/leaf_and_intermediate_chain.pem";
+constexpr absl::string_view kClientSpiffeBundleMapPath =
+ "test/core/tsi/test_creds/spiffe_end2end/client_spiffebundle.json";
+constexpr absl::string_view kServerSpiffeBundleMapPath =
+ "test/core/tsi/test_creds/spiffe_end2end/server_spiffebundle.json";
+
+constexpr absl::string_view kNonSpiffeKeyPath =
+ "test/core/tsi/test_creds/crl_data/valid.key";
+constexpr absl::string_view kNonSpiffeCertPath =
+ "test/core/tsi/test_creds/crl_data/valid.pem";
+constexpr absl::string_view kMultiSanKeyPath =
+ "test/core/tsi/test_creds/spiffe_end2end/multi_san.key";
+constexpr absl::string_view kMultiSanCertPath =
+ "test/core/tsi/test_creds/spiffe_end2end/multi_san_spiffe.pem";
+constexpr absl::string_view kInvalidUtf8SanKeyPath =
+ "test/core/tsi/test_creds/spiffe_end2end/invalid_utf8_san.key";
+constexpr absl::string_view kInvalidUtf8SanCertPath =
+ "test/core/tsi/test_creds/spiffe_end2end/invalid_utf8_san_spiffe.pem";
+
+class SpiffeSslTransportSecurityTest
+ : public testing::TestWithParam<tsi_tls_version> {
+ protected:
+ // A tsi_test_fixture implementation.
+ class SslTsiTestFixture {
+ public:
+ SslTsiTestFixture(
+ absl::string_view server_key_path, absl::string_view server_cert_path,
+ absl::string_view client_key_path, absl::string_view client_cert_path,
+ absl::string_view server_spiffe_bundle_map_path,
+ absl::string_view client_spiffe_bundle_map_path,
+ std::optional<absl::string_view> ca_path, bool expect_server_success,
+ bool expect_client_success_1_2, bool expect_client_success_1_3) {
+ tsi_test_fixture_init(&base_);
+ base_.test_unused_bytes = true;
+ base_.vtable = &kVtable;
+ server_key_ = grpc_core::testing::GetFileContents(server_key_path.data());
+ server_cert_ =
+ grpc_core::testing::GetFileContents(server_cert_path.data());
+ client_key_ = grpc_core::testing::GetFileContents(client_key_path.data());
+ client_cert_ =
+ grpc_core::testing::GetFileContents(client_cert_path.data());
+ // We set this and it shouldn't matter if we set spiffe bundles
+ if (ca_path.has_value()) {
+ ca_certificates_ = grpc_core::testing::GetFileContents(ca_path->data());
+ }
+ if (!server_spiffe_bundle_map_path.empty()) {
+ auto server_map =
+ grpc_core::SpiffeBundleMap::FromFile(server_spiffe_bundle_map_path);
+ CHECK(server_map.ok());
+ server_spiffe_bundle_map_ = std::make_shared<RootCertInfo>(*server_map);
+ }
+ if (!client_spiffe_bundle_map_path.empty()) {
+ auto client_map =
+ grpc_core::SpiffeBundleMap::FromFile(client_spiffe_bundle_map_path);
+ CHECK(client_map.ok());
+ client_spiffe_bundle_map_ = std::make_shared<RootCertInfo>(*client_map);
+ }
+ expect_server_success_ = expect_server_success;
+ expect_client_success_1_2_ = expect_client_success_1_2;
+ expect_client_success_1_3_ = expect_client_success_1_3;
+
+ server_pem_key_cert_pairs_ = static_cast<tsi_ssl_pem_key_cert_pair*>(
+ gpr_malloc(sizeof(tsi_ssl_pem_key_cert_pair)));
+ server_pem_key_cert_pairs_[0].private_key = server_key_.c_str();
+ server_pem_key_cert_pairs_[0].cert_chain = server_cert_.c_str();
+ client_pem_key_cert_pairs_ = static_cast<tsi_ssl_pem_key_cert_pair*>(
+ gpr_malloc(sizeof(tsi_ssl_pem_key_cert_pair)));
+ client_pem_key_cert_pairs_[0].private_key = client_key_.c_str();
+ client_pem_key_cert_pairs_[0].cert_chain = client_cert_.c_str();
+ }
+
+ void Run() {
+ tsi_test_do_handshake(&base_);
+ tsi_test_fixture_destroy(&base_);
+ }
+
+ ~SslTsiTestFixture() {
+ gpr_free(server_pem_key_cert_pairs_);
+ gpr_free(client_pem_key_cert_pairs_);
+
+ tsi_ssl_server_handshaker_factory_unref(server_handshaker_factory_);
+ tsi_ssl_client_handshaker_factory_unref(client_handshaker_factory_);
+ }
+
+ private:
+ static void SetupHandshakers(tsi_test_fixture* fixture) {
+ CHECK_NE(fixture, nullptr);
+ auto* self = reinterpret_cast<SslTsiTestFixture*>(fixture);
+ self->SetupHandshakers();
+ }
+
+ void SetupHandshakers() {
+ // Create client handshaker factory.
+ tsi_ssl_client_handshaker_options client_options;
+ client_options.pem_key_cert_pair = client_pem_key_cert_pairs_;
+ if (client_spiffe_bundle_map_ != nullptr) {
+ client_options.root_cert_info = client_spiffe_bundle_map_;
+ } else {
+ client_options.root_cert_info =
+ std::make_shared<RootCertInfo>(ca_certificates_);
+ }
+ client_options.min_tls_version = GetParam();
+ client_options.max_tls_version = GetParam();
+ EXPECT_EQ(tsi_create_ssl_client_handshaker_factory_with_options(
+ &client_options, &client_handshaker_factory_),
+ TSI_OK);
+ // Create server handshaker factory.
+ tsi_ssl_server_handshaker_options server_options;
+ server_options.pem_key_cert_pairs = server_pem_key_cert_pairs_;
+ server_options.num_key_cert_pairs = 1;
+ if (server_spiffe_bundle_map_ != nullptr) {
+ server_options.root_cert_info = server_spiffe_bundle_map_;
+ } else {
+ server_options.root_cert_info =
+ std::make_shared<RootCertInfo>(ca_certificates_);
+ }
+ server_options.client_certificate_request =
+ TSI_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY;
+ server_options.session_ticket_key = nullptr;
+ server_options.session_ticket_key_size = 0;
+ server_options.min_tls_version = GetParam();
+ server_options.max_tls_version = GetParam();
+ EXPECT_EQ(tsi_create_ssl_server_handshaker_factory_with_options(
+ &server_options, &server_handshaker_factory_),
+ TSI_OK);
+ // Create server and client handshakers.
+ EXPECT_EQ(tsi_ssl_client_handshaker_factory_create_handshaker(
+ client_handshaker_factory_, nullptr, 0, 0,
+ /*alpn_preferred_protocol_list=*/std::nullopt,
+ &base_.client_handshaker),
+ TSI_OK);
+ EXPECT_EQ(tsi_ssl_server_handshaker_factory_create_handshaker(
+ server_handshaker_factory_, 0, 0, &base_.server_handshaker),
+ TSI_OK);
+ }
+
+ static void CheckHandshakerPeers(tsi_test_fixture* fixture) {
+ CHECK_NE(fixture, nullptr);
+ auto* self = reinterpret_cast<SslTsiTestFixture*>(fixture);
+ self->CheckHandshakerPeers();
+ }
+
+ void CheckHandshakerPeers() {
+ bool expect_server_success = expect_server_success_;
+ bool expect_client_success = false;
+#if OPENSSL_VERSION_NUMBER >= 0x10100000
+ expect_client_success = GetParam() == tsi_tls_version::TSI_TLS1_2
+ ? expect_client_success_1_2_
+ : expect_client_success_1_3_;
+#else
+ // If using OpenSSL version < 1.1, the CRL revocation won't
+ // be enabled anyways, so we always expect the connection to
+ // be successful.
+ expect_server_success = true;
+ expect_client_success = expect_server_success;
+#endif
+ tsi_peer peer;
+ if (expect_client_success) {
+ EXPECT_EQ(
+ tsi_handshaker_result_extract_peer(base_.client_result, &peer),
+ TSI_OK);
+ tsi_peer_destruct(&peer);
+ } else {
+ EXPECT_EQ(base_.client_result, nullptr);
+ }
+ if (expect_server_success) {
+ EXPECT_EQ(
+ tsi_handshaker_result_extract_peer(base_.server_result, &peer),
+ TSI_OK);
+ tsi_peer_destruct(&peer);
+ } else {
+ EXPECT_EQ(base_.server_result, nullptr);
+ }
+ }
+
+ static void Destruct(tsi_test_fixture* fixture) {
+ auto* self = reinterpret_cast<SslTsiTestFixture*>(fixture);
+ delete self;
+ }
+
+ static struct tsi_test_fixture_vtable kVtable;
+
+ tsi_test_fixture base_;
+ std::string ca_certificates_;
+ tsi_ssl_server_handshaker_factory* server_handshaker_factory_;
+ tsi_ssl_client_handshaker_factory* client_handshaker_factory_;
+ std::shared_ptr<RootCertInfo> server_spiffe_bundle_map_;
+ std::shared_ptr<RootCertInfo> client_spiffe_bundle_map_;
+
+ std::string server_key_;
+ std::string server_cert_;
+ std::string client_key_;
+ std::string client_cert_;
+ bool expect_server_success_;
+ bool expect_client_success_1_2_;
+ bool expect_client_success_1_3_;
+ tsi_ssl_pem_key_cert_pair* client_pem_key_cert_pairs_;
+ tsi_ssl_pem_key_cert_pair* server_pem_key_cert_pairs_;
+ };
+};
+
+struct tsi_test_fixture_vtable
+ SpiffeSslTransportSecurityTest::SslTsiTestFixture::kVtable = {
+ &SpiffeSslTransportSecurityTest::SslTsiTestFixture::SetupHandshakers,
+ &SpiffeSslTransportSecurityTest::SslTsiTestFixture::
+ CheckHandshakerPeers,
+ &SpiffeSslTransportSecurityTest::SslTsiTestFixture::Destruct};
+
+// Valid SPIFFE Bundles on both sides with the root configured for the
+// appropriate trust domain
+TEST_P(SpiffeSslTransportSecurityTest, MTLSSpiffe) {
+ auto* fixture = new SslTsiTestFixture(
+ kServerKeyPath, kServerCertPath, kClientKeyPath, kClientCertPath,
+ kServerSpiffeBundleMapPath, kClientSpiffeBundleMapPath, std::nullopt,
+ /*expect_server_success=*/true,
+ /*expect_client_success_1_2=*/true, /*expect_client_success_1_3=*/true);
+ fixture->Run();
+}
+
+// Valid SPIFFE Bundles on both sides with the root configured for the
+// appropriate trust domain, and a certificate chain with an intermediate CA on
+// the server side
+TEST_P(SpiffeSslTransportSecurityTest, MTLSSpiffeChain) {
+ auto* fixture = new SslTsiTestFixture(
+ kServerChainKeyPath, kServerChainCertPath, kClientKeyPath,
+ kClientCertPath, kServerSpiffeBundleMapPath, kClientSpiffeBundleMapPath,
+ std::nullopt,
+ /*expect_server_success=*/true,
+ /*expect_client_success_1_2=*/true,
+ /*expect_client_success_1_3=*/true);
+ fixture->Run();
+}
+
+// Valid SPIFFE bundle on the client side, but the server side has a flat list
+// of CA certificates.
+TEST_P(SpiffeSslTransportSecurityTest, ClientSideSpiffeBundle) {
+ auto* fixture = new SslTsiTestFixture(kServerKeyPath, kServerCertPath,
+ kClientKeyPath, kClientCertPath, "",
+ kClientSpiffeBundleMapPath, kCaPemPath,
+ /*expect_server_success=*/true,
+ /*expect_client_success_1_2=*/true,
+ /*expect_client_success_1_3=*/true);
+ fixture->Run();
+}
+
+// Valid SPIFFE bundle on the server side, but the client side has a flat list
+// of CA certificates.
+TEST_P(SpiffeSslTransportSecurityTest, ServerSideSpiffeBundle) {
+ auto* fixture = new SslTsiTestFixture(
+ kServerKeyPath, kServerCertPath, kClientKeyPath, kClientCertPath,
+ kServerSpiffeBundleMapPath, "", kCaPemPath,
+ /*expect_server_success=*/true,
+ /*expect_client_success_1_2=*/true,
+ /*expect_client_success_1_3=*/true);
+ fixture->Run();
+}
+
+// Valid SPIFFE bundle on the client side, but the server side has a SPIFFE
+// bundle that does not have a trust domain that will match the client leaf
+// certificate. When negotiating TLS 1.3, the client-side handshake succeeds
+// because server verification of the client certificate occurs after the
+// client-side handshake is complete.
+TEST_P(SpiffeSslTransportSecurityTest, MTLSSpiffeServerMismatchFail) {
+ auto* fixture = new SslTsiTestFixture(
+ kServerKeyPath, kServerCertPath, kClientKeyPath, kClientCertPath,
+ kClientSpiffeBundleMapPath, kClientSpiffeBundleMapPath, std::nullopt,
+ /*expect_server_success=*/false,
+ /*expect_client_success_1_2=*/false,
+ /*expect_client_success_1_3=*/true);
+ fixture->Run();
+}
+
+// Valid SPIFFE bundle on the server side, but the client side has a SPIFFE
+// bundle that does not have a trust domain that will match the server leaf
+// certificate.
+TEST_P(SpiffeSslTransportSecurityTest, MTLSSpiffeClientMismatchFail) {
+ auto* fixture = new SslTsiTestFixture(
+ kServerKeyPath, kServerCertPath, kClientKeyPath, kClientCertPath,
+ kServerSpiffeBundleMapPath, kServerSpiffeBundleMapPath, std::nullopt,
+ /*expect_server_success=*/false,
+ /*expect_client_success_1_2=*/false,
+ /*expect_client_success_1_3=*/false);
+ fixture->Run();
+}
+
+// The client side is configured with only a SPIFFE bundle, but the server leaf
+// certificate does not have a SPIFFE ID.
+TEST_P(SpiffeSslTransportSecurityTest, NonSpiffeServerCertFail) {
+ auto* fixture = new SslTsiTestFixture(
+ kNonSpiffeKeyPath, kNonSpiffeCertPath, kClientKeyPath, kClientCertPath,
+ kServerSpiffeBundleMapPath, kClientSpiffeBundleMapPath, std::nullopt,
+ /*expect_server_success=*/false,
+ /*expect_client_success_1_2=*/false,
+ /*expect_client_success_1_3=*/false);
+ fixture->Run();
+}
+
+// The server side is configured with only a SPIFFE bundle, but the client leaf
+// certificate does not have a SPIFFE ID. When negotiating TLS 1.3, the
+// client-side handshake succeeds because server verification of the client
+// certificate occurs after the client-side handshake is complete.
+TEST_P(SpiffeSslTransportSecurityTest, NonSpiffeClientCertFail) {
+ // TLS1.3 client will pass because it validates the server
+ auto* fixture = new SslTsiTestFixture(
+ kServerKeyPath, kServerCertPath, kNonSpiffeKeyPath, kNonSpiffeCertPath,
+ kServerSpiffeBundleMapPath, kClientSpiffeBundleMapPath, std::nullopt,
+ /*expect_server_success=*/false,
+ /*expect_client_success_1_2=*/false,
+ /*expect_client_success_1_3=*/true);
+ fixture->Run();
+}
+
+// The server side is configued with a SPIFFE bundle, but the client side has a
+// certificate with multiple URI SANs which should fail SPIFFE verification. The
+// client's certificate is otherwise valid. This specific failure should show up
+// in logs. If SPIFFE verification is NOT done, we would expect this to pass -
+// it's a function of the SPIFFE spec to fail on multiple URI SANs. We verify
+// that the certificates used here would otherwise succeed when the root CA is
+// used directly rather than the SPIFFE Bundle Map, then that same setup fails
+// when a SPIFFE Bundle Map is used.
+TEST_P(SpiffeSslTransportSecurityTest, MultiSanSpiffeCertFails) {
+ // Passes because SPIFFE verification is not done, and this would be valid in
+ // that case.
+ auto* fixture_pass =
+ new SslTsiTestFixture(kServerKeyPath, kServerCertPath, kMultiSanKeyPath,
+ kMultiSanCertPath, "", "", kCaPemPath,
+ /*expect_server_success=*/true,
+ /*expect_client_success_1_2=*/true,
+ /*expect_client_success_1_3=*/true);
+ fixture_pass->Run();
+ // Should fail SPIFFE verification because of multiple URI SANs.
+ auto* fixture_fail = new SslTsiTestFixture(
+ kServerKeyPath, kServerCertPath, kMultiSanKeyPath, kMultiSanCertPath,
+ kServerSpiffeBundleMapPath, "", kCaPemPath,
+ /*expect_server_success=*/false,
+ /*expect_client_success_1_2=*/false,
+ /*expect_client_success_1_3=*/true);
+ fixture_fail->Run();
+}
+
+// The server side is configued with a SPIFFE bundle, but the client side has a
+// certificate with multiple URI SANs which should fail SPIFFE verification. The
+// client's certificate is otherwise valid. This specific failure should show up
+// in logs. If SPIFFE verification is NOT done, we would expect this to pass -
+// it's a function of the SPIFFE spec to fail on multiple URI SANs. We verify
+// that the certificates used here would otherwise succeed when the root CA is
+// used directly rather than the SPIFFE Bundle Map, then that same setup fails
+// when a SPIFFE Bundle Map is used.
+TEST_P(SpiffeSslTransportSecurityTest, InvalidUTF8Fails) {
+ // Passes because SPIFFE verification is not done, and this would be valid in
+ // that case.
+ auto* fixture_pass = new SslTsiTestFixture(
+ kServerKeyPath, kServerCertPath, kInvalidUtf8SanKeyPath,
+ kInvalidUtf8SanCertPath, "", "", kCaPemPath,
+ /*expect_server_success=*/true,
+ /*expect_client_success_1_2=*/true,
+ /*expect_client_success_1_3=*/true);
+ fixture_pass->Run();
+ // Should fail SPIFFE verification because of multiple URI SANs.
+ auto* fixture_fail = new SslTsiTestFixture(
+ kServerKeyPath, kServerCertPath, kInvalidUtf8SanKeyPath,
+ kInvalidUtf8SanCertPath, kServerSpiffeBundleMapPath, "", kCaPemPath,
+ /*expect_server_success=*/false,
+ /*expect_client_success_1_2=*/false,
+ /*expect_client_success_1_3=*/true);
+ fixture_fail->Run();
+}
+
+std::string TestNameSuffix(
+ const ::testing::TestParamInfo<tsi_tls_version>& version) {
+ if (version.param == tsi_tls_version::TSI_TLS1_2) return "TLS_1_2";
+ CHECK(version.param == tsi_tls_version::TSI_TLS1_3);
+ return "TLS_1_3";
+}
+
+INSTANTIATE_TEST_SUITE_P(TLSVersionsTest, SpiffeSslTransportSecurityTest,
+ testing::Values(tsi_tls_version::TSI_TLS1_2,
+ tsi_tls_version::TSI_TLS1_3),
+ &TestNameSuffix);
+
+} // namespace
+
+int main(int argc, char** argv) {
+ grpc::testing::TestEnvironment env(&argc, argv);
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/test/core/tsi/ssl_transport_security_utils_test.cc b/test/core/tsi/ssl_transport_security_utils_test.cc
index f65f96b..b97706a 100644
--- a/test/core/tsi/ssl_transport_security_utils_test.cc
+++ b/test/core/tsi/ssl_transport_security_utils_test.cc
@@ -835,6 +835,60 @@
EXPECT_NE(*pkey, nullptr);
EVP_PKEY_free(*pkey);
}
+
+TEST(ParseUriString, ValidUri) {
+ GENERAL_NAME* subject_alt_name = GENERAL_NAME_new();
+ ASN1_IA5STRING* uri = ASN1_IA5STRING_new();
+ ASN1_STRING_set(uri, "spiffe://foo.bar/path", -1);
+ GENERAL_NAME_set0_value(subject_alt_name, GEN_URI, uri);
+ absl::StatusOr<std::string> parsed_uri = ParseUriString(subject_alt_name);
+ ASSERT_EQ(parsed_uri.status(), absl::OkStatus());
+ EXPECT_EQ(*parsed_uri, "spiffe://foo.bar/path");
+ GENERAL_NAME_free(subject_alt_name);
+}
+
+TEST(ParseUriString, EmptyUri) {
+ GENERAL_NAME* subject_alt_name = GENERAL_NAME_new();
+ ASN1_IA5STRING* uri = ASN1_IA5STRING_new();
+ ASN1_STRING_set(uri, "", -1);
+ GENERAL_NAME_set0_value(subject_alt_name, GEN_URI, uri);
+ absl::StatusOr<std::string> parsed_uri = ParseUriString(subject_alt_name);
+ ASSERT_EQ(parsed_uri.status(), absl::OkStatus());
+ EXPECT_EQ(*parsed_uri, "");
+ GENERAL_NAME_free(subject_alt_name);
+}
+
+TEST(ParseUriString, InvalidUtf8) {
+ GENERAL_NAME* subject_alt_name = GENERAL_NAME_new();
+ ASN1_UTF8STRING* uri = ASN1_UTF8STRING_new();
+ // This sequence is invalid UTF8.
+ const unsigned char invalid_utf8[] = {0xc0};
+ ASN1_STRING_set(reinterpret_cast<ASN1_STRING*>(uri), invalid_utf8,
+ sizeof(invalid_utf8));
+ GENERAL_NAME_set0_value(subject_alt_name, GEN_URI, uri);
+ absl::StatusOr<std::string> parsed_uri = ParseUriString(subject_alt_name);
+ EXPECT_EQ(parsed_uri.status().code(), absl::StatusCode::kInvalidArgument);
+ GENERAL_NAME_free(subject_alt_name);
+}
+
+TEST(ParseUriString, WrongType) {
+ GENERAL_NAME* subject_alt_name = GENERAL_NAME_new();
+ ASN1_UTF8STRING* other = ASN1_UTF8STRING_new();
+ ASN1_STRING_set(other, "foo", -1);
+ GENERAL_NAME_set0_value(subject_alt_name, GEN_DNS, other);
+ absl::StatusOr<std::string> parsed_uri = ParseUriString(subject_alt_name);
+ EXPECT_EQ(parsed_uri.status().code(), absl::StatusCode::kInvalidArgument);
+ GENERAL_NAME_free(subject_alt_name);
+}
+
+TEST(ParseUriString, DontSetASN1String) {
+ GENERAL_NAME* subject_alt_name = GENERAL_NAME_new();
+ ASN1_UTF8STRING* other = ASN1_UTF8STRING_new();
+ GENERAL_NAME_set0_value(subject_alt_name, GEN_DNS, other);
+ absl::StatusOr<std::string> parsed_uri = ParseUriString(subject_alt_name);
+ EXPECT_EQ(parsed_uri.status().code(), absl::StatusCode::kInvalidArgument);
+ GENERAL_NAME_free(subject_alt_name);
+}
} // namespace testing
} // namespace grpc_core
diff --git a/test/core/tsi/test_creds/spiffe_end2end/BUILD b/test/core/tsi/test_creds/spiffe_end2end/BUILD
new file mode 100644
index 0000000..1ec7688
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/BUILD
@@ -0,0 +1,37 @@
+# Copyright 2025 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.
+
+licenses(["notice"])
+
+exports_files([
+ "ca.key",
+ "ca.pem",
+ "client.key",
+ "client_spiffebundle.json",
+ "client_spiffe.pem",
+ "intermediate_ca.key",
+ "intermediate_ca.pem",
+ "intermediate.cnf",
+ "intermediate_gen.sh",
+ "invalid_utf8_san.key",
+ "invalid_utf8_san_spiffe.pem",
+ "leaf_and_intermediate_chain.pem",
+ "leaf_signed_by_intermediate.key",
+ "leaf_signed_by_intermediate.pem",
+ "multi_san.key",
+ "multi_san_spiffe.pem",
+ "server.key",
+ "server_spiffebundle.json",
+ "server_spiffe.pem",
+])
diff --git a/test/core/tsi/test_creds/spiffe_end2end/README.md b/test/core/tsi/test_creds/spiffe_end2end/README.md
new file mode 100644
index 0000000..e2ca443
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/README.md
@@ -0,0 +1,38 @@
+All of the following files in this directory except `server_spiffebundle.json`
+and `client_spiffebundle.json` are generated with the `generate.sh` and
+`generate_intermediate.sh` script in this directory.
+
+These comprise a root trust certificate authority (CA) that signs two
+certificates - `client_spiffe.pem` and `server_spiffe.pem`. These are valid
+SPIFFE certificates (via the configuration in `spiffe-openssl.cnf`), and the
+`*_spiffebundle.json` files are SPIFFE Bundle Maps for the client and server
+respectively.
+
+The SPIFFE trust bundle map files (`*_spiffebundle.json`) are manually created
+for end to end testing. The `server_spiffebundle.json` contains the
+`foo.bar.com` trust domain (only this entry is used in e2e tests) matching URI
+SAN of `client_spiffe.pem`, and the CA certificate is `ca.pem`. The client
+`spiffebundle.json` file contains `example.com` trust domain matching the URI
+SAN of `server_spiffe.pem`, and the CA certificate there is also `ca.pem`.
+
+`leaf_and_intermediate_chain.pem` is a certificate chain whose leaf is a valid
+SPIFFE cert that is signed by an intermediate CA (`intermediate_ca.pem`). The
+intermediate CA is signed by the root CA (`ca.pem`). Thus, this setup yields a
+valid chain to the root of trust `ca.pem`.
+
+If updating these files, the `x5c` field in the json is the raw PEM CA
+certificate and can be copy pasted from the certificate file `ca.pem`. `n` and
+`e` are values from the public key attached to this certificate. `e` should
+*probably* be `AQAB` as it is the exponent. `n` can be fetched from the
+certificate by getting the RSA key from the cert and extracting the value. This
+can be done in golang with the following codeblock:
+
+```
+func(GetBase64ModulusFromPublicKey(key *rsa.PublicKey) string {
+ return base64.RawURLEncoding.EncodeToString(key.N.Bytes())
+}
+
+block, _ := pem.Decode(rawPemCert) cert, _ := x509.ParseCertificate(block.Bytes)
+publicKey := cert.PublicKey.(*rsa.PublicKey)
+fmt.Println(GetBase64ModulusFromPublicKey(publicKey))
+```
diff --git a/test/core/tsi/test_creds/spiffe_end2end/ca.key b/test/core/tsi/test_creds/spiffe_end2end/ca.key
new file mode 100644
index 0000000..bb60bf9
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/ca.key
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC19PImArmxbwgq
+o2QG2si8BU6E69Bvyqxz8Je3swxBIGwZ9uIobMSBLeTCYyXuf+o90Zf0kMwzmrAK
+eLEeky5W/j07zGXAtgUBPA7L1Uk0TxOdJXBCUvEm5Oc4GxubfO7F+pdJKZ+XkRVq
+bnStGe5qX6KNs5rcJfFUhewbtM0snGEIf2yhaA0mNNuGtlIm4VB7jmiyHNU5YTow
+ByVCVrV3/t2RI9+T4ya0AlkW93rU0M0qQauJ35LkJIXifbzrnLxmztEyb+mnVUB+
+GJgz01E4teWo/PJb1aNJ/ojf/UONsQ5IFRdza6RhaQB7C+Dxlnt/SJ3MMaxHgVyc
+YuJeVtJQuncGRSuQ2YrmW9b36HVnxa0xBDeSluUjv48hMRlLNaXaH4yuK6oc8TNJ
+ie++/ir6Kb4H+0RjcKMGqxZYfotU1obxa+5N3wzGSjUDUrhofzlfvqbp+NCwdFH+
+qczM4IZPL8YMMh6goKr9BRN9/xRIieotyH6rfKNcnkUgDp750U0cZ7P2eRUpldyc
+9hZS5AlF4cKQXgLIrv1LrZHkiIietetInUEBAa/PF2YHRLXUyI1PCSBKBu7wdwAU
+15J9dVFC9jkmOLYhoRdPfrobpWhs5+FfPJumSoiusdGXd7x4l313xi2V02YXz5mR
+GbT2lCb6aJPweuziiEBZn+5KV++DkQIDAQABAoICAADWyAO4mVHKyGYpOdNT36do
+4HZZAir2+CCTv9z8wLnrF2cF36+xvNp+an5PoYdjoxL+HP5EVT4fFWLQ22VQw5N5
+uEKAoZDGBXUpNJ/UKO1rFWA8U+iHFmDYsxlzX7lrjSkBk4fOw1rJ12HcFZo1580V
+J/GO1TmVcf0Z2jLX9Q6+TD2xTzsWFIMWp/PPMP5FqokXeBQij3LluW4Wsbf2qEBu
+/a2g5HjL6QhBgxqci6eX7b3b7sbHjLp66G+kM6n4JIFE4+Z1+sypdVBYoUESRjlB
+9qIF5Gb53QTgLYgn93AKD/hGtJ7jUcrESliAoCgWbdkYDm/vkStkX6vWdXmX01fl
+hfv6c1eYTkDhOboVZL0p0DxZwHU4Ci6UF0PflrHmospKo12bLoa2dea+5LfQVgB0
+icijZ1ZF4oePzFOtIdDYmNDfTkBe7q52HKNIPbLytXTFv9gHj/XpRdDUDgNzS7TO
+wXP1k4zY96H9YEgXNr/Ze7IQt711/Szj/bIPfuCyJTSMRh40wPW+1TzGkdQepBDO
+lHk1uVs185lJd3TryUGhfXu9X49hXOpvq1nhOy3UW/FoDPLGckOoQGe9PikP3zQC
+6poTttdmfmTy7D3vniZUjDmbztyml3twM50+KGhGJTbjEKDqs9dASbn5B69LbnOA
+kq3SzgJOGgTiTT8EFwjzAoIBAQD8LjTqiEB8TY9qQjihtcidDWBA7TcBjIQ/Wm4p
+3ff0NdGPa6/qaCPX7769wBW5/fJfoJu9ZhebWfLLYacK2Dz1BYn5EqoAf4mF4C9S
+ZDY4NO3tl8t902bjlQs+ehojyzOOVHxuATZ4mBrCFUYuFn1YPXR0qIvvXsvhRM4k
+LCf9lGl1ldUpWNbdxTxUl7NZVuZKevwBvfsGB9SPgnvDCgg0Mh4pBGUDP0WZqwaS
+frvB2iP4hVH5e/lUl2XxtlJIil8Eb1rGH2O6CmbvkSc6Sceg67/E0N6u4w9i+g1G
+uu+FFdPQSve3jUrOrhQjO7tqjh6p8J858H7CvoJo7Uojk8yjAoIBAQC4tnUCvlns
+VJQ9SVqabVuIWPJvgDf/EVHncVFwJZtdsFRchs+QFtUWyJRD6mRtHtBdyIpsQCBb
+YxPYbMoQClpEC7VNC9Y6RDKVxRM+A1cQ1JiP4PQTyzYIVfoU/RbWuixXHOvGOYsb
+LwjGZ7WQtaG8Pyqt8P97OIqZzeuQK5y+ZuixDZpBsy3xhYyQcC1oowLLgf7ip1QU
+ueEF/fi3OZCd7oIbZX10EYr/QINnlyYstRxnHz2AOO4kilQEt4Yq2YKxo/GiXVp+
+HlDD53xl/1HfT1sV9BnnoRQksmt61LO3qz6hI7TgQtFt4+Z3TsXB4siWoFtmfGRo
+njuShRAdjt47AoIBADo2EFDzEq+e17QXhSsO4zgdllJmb4QUA5L8NOHFMy9UHQOW
+QA1D7MIzvTvf0yQT0pbwZALcRia+JKV97Sk7sRZ+KNMt08A5sG0Oyt2h9EIVNbba
+i0aIPf/ar7XESbRpe4UnS3G5JfHAaqzSbFjHKDfuN6uWHHcs0rM1RsBi0PhgAdf6
+tjHNKAwZM0vzmJrOzGn06acdS4yg0hgubpMfBbXGu9+L/CU7vb0zFVlLl9uvlw4O
++FU2oN8983JQO74y8qv7aGDDbinLrOcwAnYaAdLLmIC+FikYkLywK9Bf5lo6J6K1
+K2lbXCFJXaykZSa7l6hyus6NDTW29DN0UuDThTECggEBAIlRU0G74zv3UNETBGFD
+pUWC4pCT4I0mAh6uNp3Jx2vqF31F127GGpo6eUbYLNqWis79tDJvpd5qlKlTjxBH
+sf1eOuVRRGzCoFyJSxnR/9sKQXTNBobkjoG58RXDZUQy2Z3VaU5PHyQRXlOpa+ht
+rxQ0XXU8caHYUemvlUzzry45cQmXFDN9kk04PFxwmMFuVjiWprc3MCHMYgZDXbBR
+Ajr2whC97XrpQ80o18eeLYoVBk1/gwgcyqWtlHKBQSP/2dJYD93lys1xmhdZszhc
+jDAYXZ2/f7acIE57ub1x/T0w+HlxAxmpcupf/8h4bjoKb5bTFb/l//tqjt0JQQbI
+XFsCggEBALd+S5rIRYz3YpXCnt+uT6ELbD7RfDtoGdWtLavm3vQb3yZCXGwlHesP
+ud1xXZ5w1Ky3GSqvXJ4F+UiVd+DLEoq6u0Y8adkKmNpwElJffG7+/9ZgDgAUQDhr
+i4yqH5wYHs2tSdc0hHEqmLjn0uylX+y4pWojHD1l0w7KrepUzj9SNVrOyzl8D7D2
+xXFNb+rSY4B9ZwPofYqxGxM6GGo47GFFOGqFsCtwp8lcbSMgzx0j36Ibg9uPmoeM
+53ea7Fbqv+fWVzhu+6r7BE7mp0rYBWPMRg/euciYl2X3FBRjhqt1/GENDwejYkFS
+qLeaSJiKSNSUyCl91Qu0eb4FbE5qLwI=
+-----END PRIVATE KEY-----
diff --git a/test/core/tsi/test_creds/spiffe_end2end/ca.pem b/test/core/tsi/test_creds/spiffe_end2end/ca.pem
new file mode 100644
index 0000000..c086690
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/ca.pem
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFlTCCA32gAwIBAgIUCD73sHXu5IURolSTiJ127a/xUO4wDQYJKoZIhvcNAQEL
+BQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMSEwHwYDVQQKDBhJbnRlcm5l
+dCBXaWRnaXRzIFB0eSBMdGQxGzAZBgNVBAMMEmZvby5iYXIuaG9vLmNhLmNvbTAe
+Fw0yNTAzMDcxOTM0NDZaFw0yNjAzMDcxOTM0NDZaMFoxCzAJBgNVBAYTAlVTMQsw
+CQYDVQQIDAJWQTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRsw
+GQYDVQQDDBJmb28uYmFyLmhvby5jYS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4IC
+DwAwggIKAoICAQC19PImArmxbwgqo2QG2si8BU6E69Bvyqxz8Je3swxBIGwZ9uIo
+bMSBLeTCYyXuf+o90Zf0kMwzmrAKeLEeky5W/j07zGXAtgUBPA7L1Uk0TxOdJXBC
+UvEm5Oc4GxubfO7F+pdJKZ+XkRVqbnStGe5qX6KNs5rcJfFUhewbtM0snGEIf2yh
+aA0mNNuGtlIm4VB7jmiyHNU5YTowByVCVrV3/t2RI9+T4ya0AlkW93rU0M0qQauJ
+35LkJIXifbzrnLxmztEyb+mnVUB+GJgz01E4teWo/PJb1aNJ/ojf/UONsQ5IFRdz
+a6RhaQB7C+Dxlnt/SJ3MMaxHgVycYuJeVtJQuncGRSuQ2YrmW9b36HVnxa0xBDeS
+luUjv48hMRlLNaXaH4yuK6oc8TNJie++/ir6Kb4H+0RjcKMGqxZYfotU1obxa+5N
+3wzGSjUDUrhofzlfvqbp+NCwdFH+qczM4IZPL8YMMh6goKr9BRN9/xRIieotyH6r
+fKNcnkUgDp750U0cZ7P2eRUpldyc9hZS5AlF4cKQXgLIrv1LrZHkiIietetInUEB
+Aa/PF2YHRLXUyI1PCSBKBu7wdwAU15J9dVFC9jkmOLYhoRdPfrobpWhs5+FfPJum
+SoiusdGXd7x4l313xi2V02YXz5mRGbT2lCb6aJPweuziiEBZn+5KV++DkQIDAQAB
+o1MwUTAdBgNVHQ4EFgQUL/YTEXKZRjbOv82CwGMuBeNwErIwHwYDVR0jBBgwFoAU
+L/YTEXKZRjbOv82CwGMuBeNwErIwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B
+AQsFAAOCAgEAIYtNERVMj4dS/KgoXD3a6mF6KD+IuxDTlVbVTwDJMzdNW5UPB2qC
+iDKbirK8Eedh90r3qxKzBiQPE5AHDZrbWSBAxDhlpQqCXgx8Z+nCB38K9i8hbKM/
+ablm9DkeHiHohoP1kJEN2HtPz1K6OabbKbPtVGt8y+QTIlZDodPPzKPmQ6dUTm9t
+WlU1oIxl4pPVR6WXDr0qAmNRvdW7+8/Ai+gMDi6fKQJCe/r/meVrI9lSrnn8VQyO
+4xXvyolHEROTUiomlJ1QE9IWAM9LNFOuWFwQjayo2d6O+zHqjGUtbzBHFb8/BLJc
+QzogWYT0/+rEboJhR+/OGe0ntPPkudmLL0HTx0Q1aajnChMVlaOd2wucePZSa6Gq
+GEVA+lCqwbteqwJCnL7deVHo+UlORFgQwYir2CQbyN9Yd4MtQT7VZuDiVC6tlGbj
+1ogrpOnW8n49jXrNPWuPz2hcPQb2gzYFGI9WRKIX4SjvvS3QcHUyigQkjAQ12Ldj
+5CXBYTjSmjcgsa2QfUY5qrXsHLz7e4uXD9XYnB/XEEfxQfQTFEy5CtzTrTxnT+cw
+RuBooIr6InjqdbdJ+UbkWcIY6w+c7ndFT849pUgtrojbVpm9ZzsmHhAahR/+iHYH
++LGeHYIpq71o/YDgM4vV9z3sWic7u1YE9JULBdlaDw8Xik82zjvw4IA=
+-----END CERTIFICATE-----
diff --git a/test/core/tsi/test_creds/spiffe_end2end/client.key b/test/core/tsi/test_creds/spiffe_end2end/client.key
new file mode 100644
index 0000000..d3f694a
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/client.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC/Y4KWLto/B3f3
+YRlwJXn2l+hlre80L36k0dNmyvZW6nY0zZfXGJBHV3UlIT3YB+vIsePu3KtpfJak
+xjBuyL65LtZphxkR+1mfL6ucVqgroI6b7WFt/Zo6vjElTbJGMaNcprItlLNjBxKw
+d0elYWS4q/TkhUw9cPfywE+FCNy3OPLPbI677lD1C29AlI8ToTHfjY1PqJ+9dS2j
+xg7qAskWUsOTjfXO8SiQbF15DkD2hZDg9I2/BBC9YhB5xUOCywb5U9Ucomp7Q9aH
+J3OkvsN1n0Un7GHkEn1iyXaa61vh94uk7M1e/ewvBdmC+JUgLaU0tKdg7dFIgc4g
+KF4ewvITAgMBAAECggEAGNHb1oQe83uReDhUZzHE/lC257DFy5u5LcC0TKlXhvAD
+AlgDL/bUhxv1aipEbeahEjbXm1tss1Jf6uB02XkZGsTTM348pxNUjQtOkwi96GJ6
+/Aet0ejIfoO5td+8ldPMHrDTfIvDM71wFAhz9yOJP1W1fYk32OP0k7uS1GrQzMXQ
+6y5pA+Mvn1lXb9zsufkrvyp4F+QGUiXnPNEPXC+9u5pNqY0OkNvudn3cfU6t2jJO
+lUies9xiX5GatWQ4kp/i26HoTGvefuM3mnNmm37dSt/5zalfrECC9e3mKkba1StY
+XPavOtXkjA1X51w5MqVL1wEBYQCQTlr+KSATR44y2QKBgQD98MID26fNYSrNuTxN
+I9YHGqFmwB3qe2jOec9dqmOtdh8ji+UEX1z10Sm6Vrdk75LwwCp066LBxyYVTJo2
+PgN6Dn0ARHEF0j4GgJiYWKhOJk+mwVHDENuQFSpGgP+mLXGGyo8irCz/HP6g5uww
+70h+QDPtNEccDHLAUaaA92DdfQKBgQDA8OEtKPtgWZBdTEvcKQbn49m1aihqjV/q
+kr8Nn28Y39i90vyGoO67muTuSAvDNgW6ne/W2Ic5VhSicIVyVHV0p3cUB2NGwdnp
+RvT+7wd22pVUMW9Lys4hAg1O4TtoO5Th/lRDXf28IAmxQPz7jc3alr2AEgaawrEH
+t/gyph5izwKBgAm/uHMVd7e2x0Hmnp4Hhiv3pcKgFYXu/vMOUhuycukx47glosEs
+OBFn2kZBFgFWATok5nxen3z4/ElNC46RiuigrcPY3M3waFkvjqwqmyuE5QHt4gmv
+F/7METdrtGTY0cbu99fI7GFO56edteubBBl0tYmhvCkjgTRI7i99T8axAoGADvGZ
+PzoxXiERwWgrZ/31fvhy4E48myQgDbcX/SIgJLkRvIIe9P55/yXcHWiPIKuKfg3F
+icpzjslQB9TXQSexgTuHSZHY6p4RFrRcPXKeDWKYtlCt4hzvIjdaeIWRaF1VBrwA
+Iseg43VHyubRsVomW4vZWZYL1OjHdWWWX7XmHJkCgYEA2LUeWNkdqABJ9Yta7kKu
+02gkOsjnQyyYIeFCteGSM9oejE4zDM+WSCpVCLzq8DcRK8M/xBhCIf+mVZRroHAz
+g5YpFrg5a7kuuTgQJv/6ju+Mbd+DGpsul/N2zIPFzy6T27gIzL8+QExUK0y1XkL/
+7yGUwV4OO2kuwh8YT+TiCts=
+-----END PRIVATE KEY-----
diff --git a/test/core/tsi/test_creds/spiffe_end2end/client_spiffe.pem b/test/core/tsi/test_creds/spiffe_end2end/client_spiffe.pem
new file mode 100644
index 0000000..475b490
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/client_spiffe.pem
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIEvjCCAqagAwIBAgIUBoJ396S7DO0kRqDUn4TB6zKKIIkwDQYJKoZIhvcNAQEL
+BQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMSEwHwYDVQQKDBhJbnRlcm5l
+dCBXaWRnaXRzIFB0eSBMdGQxGzAZBgNVBAMMEmZvby5iYXIuaG9vLmNhLmNvbTAe
+Fw0yNTAzMDcxOTM0NDZaFw0zNTAzMDUxOTM0NDZaMEwxCzAJBgNVBAYTAlVTMQsw
+CQYDVQQIDAJDQTEMMAoGA1UEBwwDU1ZMMQ0wCwYDVQQKDARnUlBDMRMwEQYDVQQD
+DAp0ZXN0Y2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2OC
+li7aPwd392EZcCV59pfoZa3vNC9+pNHTZsr2Vup2NM2X1xiQR1d1JSE92AfryLHj
+7tyraXyWpMYwbsi+uS7WaYcZEftZny+rnFaoK6COm+1hbf2aOr4xJU2yRjGjXKay
+LZSzYwcSsHdHpWFkuKv05IVMPXD38sBPhQjctzjyz2yOu+5Q9QtvQJSPE6Ex342N
+T6ifvXUto8YO6gLJFlLDk431zvEokGxdeQ5A9oWQ4PSNvwQQvWIQecVDgssG+VPV
+HKJqe0PWhydzpL7DdZ9FJ+xh5BJ9Ysl2mutb4feLpOzNXv3sLwXZgviVIC2lNLSn
+YO3RSIHOICheHsLyEwIDAQABo4GJMIGGMEQGA1UdEQQ9MDuGOXNwaWZmZTovL2Zv
+by5iYXIuY29tLzllZWJjY2QyLTEyYmYtNDBhNi1iMjYyLTY1ZmUwNDg3ZDQ1MzAd
+BgNVHQ4EFgQUVHpZYzO6JCGBTQwzWbj/CVC6sTAwHwYDVR0jBBgwFoAUL/YTEXKZ
+RjbOv82CwGMuBeNwErIwDQYJKoZIhvcNAQELBQADggIBACa1C7Zn0rXXIXC9SI8G
+MPSTyebo2s/OZCQ2sKQEK90yL33Ae97beiBVgFlt2TTXeEW91iZMZMfn9BlRDYkZ
+j8qdcNbG1QQEco2ZT3Ti13psVoSIgX2yMbds5DFkalJhXCx21xxNHLVUtd1xRDhu
+JvFm+V4TNFi1ka0U5fNcxRAGeVkR7tzpvUy8La+aNHOOAEaOX4oCQKueDXhGJGcL
+EXMz6N/KyLq4otaKGO67D2q5or6hH1afgrIcdZV0Gb7Cf+nR18QNWvH0Nm1cXdkT
+OL/iKlDqbhLGznGazz6T1EWlbynGjupFWdXDIPjsgN0xrAJS7NWjkUSgWcA94J7Z
+JwtIsjgP0DWg7fQsYa4cR9rxS+gYtkiKZuRTONSGYxQLAxJEb5NCCXE6fBC6jfHZ
+xtsY1ExlA6f6t+E7mS0OkRqbb6KZqTFGgSyx99jDL46FUH3zqA4IOs8Q4loo/Zkh
+LNjeG/3MoO7RvyY9g58y+yf3ZOp92zoyKMSqCBsvdTMRP0hWGq4BqoW/3Sn3ujf1
+uf5KEE+peuYzEqK1mtuZQHe/+1Vm7h88/TlER7mijMN3/MFZzmor890vBbzqf7KQ
+Q2QCRwIISj3a0puskpZM13Jt0r8cM+npkk1evfgmpBjNLrOinIQgWy30Qlxt5fIh
+jYaOBLHtSFI01OHnivQ1dFWG
+-----END CERTIFICATE-----
diff --git a/test/core/tsi/test_creds/spiffe_end2end/client_spiffebundle.json b/test/core/tsi/test_creds/spiffe_end2end/client_spiffebundle.json
new file mode 100644
index 0000000..18f81da
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/client_spiffebundle.json
@@ -0,0 +1,18 @@
+{
+ "trust_domains": {
+ "example.com": {
+ "spiffe_sequence": 12035488,
+ "keys": [
+ {
+ "kty": "RSA",
+ "use": "x509-svid",
+ "x5c": [
+ "MIIFlTCCA32gAwIBAgIUCD73sHXu5IURolSTiJ127a/xUO4wDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxGzAZBgNVBAMMEmZvby5iYXIuaG9vLmNhLmNvbTAeFw0yNTAzMDcxOTM0NDZaFw0yNjAzMDcxOTM0NDZaMFoxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJWQTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRswGQYDVQQDDBJmb28uYmFyLmhvby5jYS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC19PImArmxbwgqo2QG2si8BU6E69Bvyqxz8Je3swxBIGwZ9uIobMSBLeTCYyXuf+o90Zf0kMwzmrAKeLEeky5W/j07zGXAtgUBPA7L1Uk0TxOdJXBCUvEm5Oc4GxubfO7F+pdJKZ+XkRVqbnStGe5qX6KNs5rcJfFUhewbtM0snGEIf2yhaA0mNNuGtlIm4VB7jmiyHNU5YTowByVCVrV3/t2RI9+T4ya0AlkW93rU0M0qQauJ35LkJIXifbzrnLxmztEyb+mnVUB+GJgz01E4teWo/PJb1aNJ/ojf/UONsQ5IFRdza6RhaQB7C+Dxlnt/SJ3MMaxHgVycYuJeVtJQuncGRSuQ2YrmW9b36HVnxa0xBDeSluUjv48hMRlLNaXaH4yuK6oc8TNJie++/ir6Kb4H+0RjcKMGqxZYfotU1obxa+5N3wzGSjUDUrhofzlfvqbp+NCwdFH+qczM4IZPL8YMMh6goKr9BRN9/xRIieotyH6rfKNcnkUgDp750U0cZ7P2eRUpldyc9hZS5AlF4cKQXgLIrv1LrZHkiIietetInUEBAa/PF2YHRLXUyI1PCSBKBu7wdwAU15J9dVFC9jkmOLYhoRdPfrobpWhs5+FfPJumSoiusdGXd7x4l313xi2V02YXz5mRGbT2lCb6aJPweuziiEBZn+5KV++DkQIDAQABo1MwUTAdBgNVHQ4EFgQUL/YTEXKZRjbOv82CwGMuBeNwErIwHwYDVR0jBBgwFoAUL/YTEXKZRjbOv82CwGMuBeNwErIwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAIYtNERVMj4dS/KgoXD3a6mF6KD+IuxDTlVbVTwDJMzdNW5UPB2qCiDKbirK8Eedh90r3qxKzBiQPE5AHDZrbWSBAxDhlpQqCXgx8Z+nCB38K9i8hbKM/ablm9DkeHiHohoP1kJEN2HtPz1K6OabbKbPtVGt8y+QTIlZDodPPzKPmQ6dUTm9tWlU1oIxl4pPVR6WXDr0qAmNRvdW7+8/Ai+gMDi6fKQJCe/r/meVrI9lSrnn8VQyO4xXvyolHEROTUiomlJ1QE9IWAM9LNFOuWFwQjayo2d6O+zHqjGUtbzBHFb8/BLJcQzogWYT0/+rEboJhR+/OGe0ntPPkudmLL0HTx0Q1aajnChMVlaOd2wucePZSa6GqGEVA+lCqwbteqwJCnL7deVHo+UlORFgQwYir2CQbyN9Yd4MtQT7VZuDiVC6tlGbj1ogrpOnW8n49jXrNPWuPz2hcPQb2gzYFGI9WRKIX4SjvvS3QcHUyigQkjAQ12Ldj5CXBYTjSmjcgsa2QfUY5qrXsHLz7e4uXD9XYnB/XEEfxQfQTFEy5CtzTrTxnT+cwRuBooIr6InjqdbdJ+UbkWcIY6w+c7ndFT849pUgtrojbVpm9ZzsmHhAahR/+iHYH+LGeHYIpq71o/YDgM4vV9z3sWic7u1YE9JULBdlaDw8Xik82zjvw4IA="
+ ],
+ "n": "tfTyJgK5sW8IKqNkBtrIvAVOhOvQb8qsc_CXt7MMQSBsGfbiKGzEgS3kwmMl7n_qPdGX9JDMM5qwCnixHpMuVv49O8xlwLYFATwOy9VJNE8TnSVwQlLxJuTnOBsbm3zuxfqXSSmfl5EVam50rRnual-ijbOa3CXxVIXsG7TNLJxhCH9soWgNJjTbhrZSJuFQe45oshzVOWE6MAclQla1d_7dkSPfk-MmtAJZFvd61NDNKkGrid-S5CSF4n2865y8Zs7RMm_pp1VAfhiYM9NROLXlqPzyW9WjSf6I3_1DjbEOSBUXc2ukYWkAewvg8ZZ7f0idzDGsR4FcnGLiXlbSULp3BkUrkNmK5lvW9-h1Z8WtMQQ3kpblI7-PITEZSzWl2h-MriuqHPEzSYnvvv4q-im-B_tEY3CjBqsWWH6LVNaG8WvuTd8Mxko1A1K4aH85X76m6fjQsHRR_qnMzOCGTy_GDDIeoKCq_QUTff8USInqLch-q3yjXJ5FIA6e-dFNHGez9nkVKZXcnPYWUuQJReHCkF4CyK79S62R5IiInrXrSJ1BAQGvzxdmB0S11MiNTwkgSgbu8HcAFNeSfXVRQvY5Jji2IaEXT366G6VobOfhXzybpkqIrrHRl3e8eJd9d8YtldNmF8-ZkRm09pQm-miT8Hrs4ohAWZ_uSlfvg5E",
+ "e": "AQAB"
+ }
+ ]
+ }
+ }
+}
diff --git a/test/core/tsi/test_creds/spiffe_end2end/generate.sh b/test/core/tsi/test_creds/spiffe_end2end/generate.sh
new file mode 100755
index 0000000..44a33b7
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/generate.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+# Copyright 2025 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.
+
+# Generate client/server self signed CAs and certs.
+openssl req -x509 -newkey rsa:4096 -keyout ca.key -out ca.pem -days 365 -nodes -subj "/C=US/ST=VA/O=Internet Widgits Pty Ltd/CN=foo.bar.hoo.ca.com"
+
+# The SPIFFE related extensions are listed in spiffe-openssl.cnf config. Both
+# client_spiffe.pem and server_spiffe.pem are generated in the same way with
+# original client.pem and server.pem but with using that config. Here are the
+# exact commands (we pass "-subj" as argument in this case):
+openssl genrsa -out client.key.rsa 2048
+openssl pkcs8 -topk8 -in client.key.rsa -out client.key -nocrypt
+openssl req -new -key client.key -out spiffe-cert.csr \
+ -subj /C=US/ST=CA/L=SVL/O=gRPC/CN=testclient/ \
+ -config spiffe-openssl.cnf -reqexts spiffe_client_e2e
+openssl x509 -req -CA ca.pem -CAkey ca.key -CAcreateserial \
+ -in spiffe-cert.csr -out client_spiffe.pem -extensions spiffe_client_e2e \
+ -extfile spiffe-openssl.cnf -days 3650 -sha256
+
+openssl genrsa -out server.key.rsa 2048
+openssl pkcs8 -topk8 -in server.key.rsa -out server.key -nocrypt
+openssl req -new -key server.key -out spiffe-cert.csr \
+ -subj "/C=US/ST=CA/L=SVL/O=gRPC/CN=*.test.google.com/" \
+ -config spiffe-openssl.cnf -reqexts spiffe_server_e2e
+openssl x509 -req -CA ca.pem -CAkey ca.key -CAcreateserial \
+ -in spiffe-cert.csr -out server_spiffe.pem -extensions spiffe_server_e2e \
+ -extfile spiffe-openssl.cnf -days 3650 -sha256
+
+openssl genrsa -out multi_san.key.rsa 2048
+openssl pkcs8 -topk8 -in multi_san.key.rsa -out multi_san.key -nocrypt
+openssl req -new -key multi_san.key -out spiffe-cert.csr \
+ -subj /C=US/ST=CA/L=SVL/O=gRPC/CN=testclient/ \
+ -config spiffe-openssl.cnf -reqexts spiffe_server_e2e
+openssl x509 -req -CA ca.pem -CAkey ca.key -CAcreateserial \
+ -in spiffe-cert.csr -out multi_san_spiffe.pem -extensions spiffe_client_multi \
+ -extfile spiffe-openssl.cnf -days 3650 -sha256
+
+openssl genrsa -out invalid_utf8_san.key.rsa 2048
+openssl pkcs8 -topk8 -in invalid_utf8_san.key.rsa -out invalid_utf8_san.key -nocrypt
+openssl req -new -key invalid_utf8_san.key -out spiffe-cert.csr \
+ -subj /C=US/ST=CA/L=SVL/O=gRPC/CN=testclient/ \
+ -config spiffe-openssl.cnf -reqexts spiffe_server_e2e
+openssl x509 -req -CA ca.pem -CAkey ca.key -CAcreateserial \
+ -in spiffe-cert.csr -out invalid_utf8_san_spiffe.pem -extensions spiffe_client_non_utf8 \
+ -extfile spiffe-openssl.cnf -days 3650 -sha256
+
+rm ./*.rsa
+rm ./*.csr
+rm ./*.srl
diff --git a/test/core/tsi/test_creds/spiffe_end2end/intermediate.cnf b/test/core/tsi/test_creds/spiffe_end2end/intermediate.cnf
new file mode 100644
index 0000000..eeeb3af
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/intermediate.cnf
@@ -0,0 +1,38 @@
+[ca]
+default_ca = CA_intermediate
+
+[CA_intermediate]
+dir = .
+certs = $dir/certs
+crl_dir = $dir/crl
+new_certs_dir = $dir/newcerts
+database = $dir/index.txt
+serial = $dir/serial
+RANDFILE = $dir/private/.rand
+private_key = $dir/intermediate_ca.key
+certificate = $dir/intermediate_ca.pem
+crl = $dir/intermediate.crl
+
+# For certificate revocation lists.
+crlnumber = $dir/crlnumber
+crl = $dir/crl/intermediate.crl
+crl_extensions = crl_ext
+default_crl_days = 3650
+
+default_md = sha256
+
+[req]
+distinguished_name = req_distinguished_name
+req_extensions = v3_req
+prompt = no
+
+[req_distinguished_name]
+CN = intermediatecert.example.com
+
+[crl_ext]
+authorityKeyIdentifier=keyid:always
+
+[v3_req]
+keyUsage = critical, digitalSignature, keyEncipherment, keyCertSign, cRLSign
+extendedKeyUsage = clientAuth, serverAuth
+basicConstraints = critical, CA:true
diff --git a/test/core/tsi/test_creds/spiffe_end2end/intermediate_ca.key b/test/core/tsi/test_creds/spiffe_end2end/intermediate_ca.key
new file mode 100644
index 0000000..3e5fe81
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/intermediate_ca.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC7XOGe2r5KNdo7
+ELwJ7GRAIVqxAkT9bB6+7rUyDhJnxWeI7E9vr+I4clDRYREoxnuXq6oGTICKlWiv
+vrxBASSdA0PpjLsMNnNU1n9XHXEBEa3YM7w85UBvf/8IdlpiyHgPkg+1DdvuToOa
+0Xhejnq/aCcNyErjjNN26k/06joO3+feYvsn1tToXomif4HUU+SE5xIGiuPHcDwJ
+tjk/Iugh+pNPdltOguxq2E7m8upQvwShe2A6azD1wV9HiyFtkf7tlp4f900MmRyb
+kHb7zXfgXBwlf7waa2RP3UDZbJAZZZkOFQmPcujNvkAz/ghaf0Uf2Ix/P0Zh69LU
+notPVa35AgMBAAECggEADMnb740UMHoJBX+Qqie7twHf0BWs0lRjK9qcvQgnWvRn
+KvogOOobKYmp+QSkWHFlxbY2onvtySfGD7rX3CMBFgN/Su1rZfvAdwG2CuLsT+ue
+P1PWrRxjv8iPhosXg8cVpNBuKIhD4vnycyqJigC1IaPxCHiLngkCdQssvnWPVWz2
+nXp0SKcDKJu/sU8Mp0UW490DIUi8iSQHkjbpXu+RExAPAjhe+vvEuExZFs8dN0Rp
+g3oj8l6pLkfJ/M4tfib7WiQxxtZWEEuEYsz3JDpxySFWR59SRZ2tYKv4k9TNbQXA
+qX8jqVGT+YubWKY4HtRxd2L3yy443ERQdwQecLsYIQKBgQDgPpRts1pz9UnrXnr8
+ee8nO4SSxsJxge4OX5/yX7Lm/JwOPyk6/nCH3LKqxOWMOZpOpgI3ithA1Qvb1JuM
+vDQo13z4Xi5RMJm8h3B5O0f9brw3L2h+hrImJfYuozj0icyFSbT/PjO1pGB/QAAF
+ie6G3JQLRlBFF0dclcqb4tpEEQKBgQDV5UAYKGz+sdDpzgarTmsyd0YVTRitdBMv
+S7eddG/gCBMaISXXXksYXX+Y74zbs2LCfkLz+i4j8BDoRFPfPlK/daCOMOriJ++Z
+LNBClmkBiwqlZXs9d1dEWmJis5GbJEiAneoNskAy6UspFMCLu4QJJHfB5aZsAphm
+4509tRqTaQKBgF8gDOTwVWmlXyMaZD+gFiRlahq9eBSgknTEedxeXk0AUc71Wi8t
+al0n8R7iAaJXeS7t2zjmjFAMUEiDyyyLVfERYHEXurw0SrMgHUVAMYy/odYjJUev
+Kflm1yT3lpydrAXKu54fK95dCZZRdvDijy3kemTCAiEc++e5n0Y7mG2RAoGAeMtN
+5ha2byPWeqOqaoYPzeFEB/WRMSuzRet514cRQzDsML51k2oh+LGcxK7MGqr05CfQ
+Ad7QveTud21W/GVC7/Mq6AJCM9Qf4J5JQsGUrZVrPrrP3YePFgABPgJxAW99Ln1/
+15pJ3TaZBqs8je0pBMy+gRcDZE4W6Uwz2cx0JLkCgYAn6/C71GXvLkjDdmRHS2zA
+K+gOTCb4YdGDCnv5Cez64pega6mUy+EIznJYpI3KwHbAsUFkYgNJ7VvUtVIu2z9U
+UHSBHilN/cMWRCgN3KXyYOHr50g0P+gei0bhiWddIh8+tC1J/627g2r2Jwowuarn
+1JLr0ZYgh3SXglOkgyvIKA==
+-----END PRIVATE KEY-----
diff --git a/test/core/tsi/test_creds/spiffe_end2end/intermediate_ca.pem b/test/core/tsi/test_creds/spiffe_end2end/intermediate_ca.pem
new file mode 100644
index 0000000..84cabe6
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/intermediate_ca.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEkzCCAnugAwIBAgIUa4ZkytupQZ5OwDMPw/vRY8deFc4wDQYJKoZIhvcNAQEL
+BQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMSEwHwYDVQQKDBhJbnRlcm5l
+dCBXaWRnaXRzIFB0eSBMdGQxGzAZBgNVBAMMEmZvby5iYXIuaG9vLmNhLmNvbTAe
+Fw0yNTAzMTgxNjE4NTFaFw0zNTAzMTYxNjE4NTFaMCcxJTAjBgNVBAMMHGludGVy
+bWVkaWF0ZWNlcnQuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQC7XOGe2r5KNdo7ELwJ7GRAIVqxAkT9bB6+7rUyDhJnxWeI7E9vr+I4
+clDRYREoxnuXq6oGTICKlWivvrxBASSdA0PpjLsMNnNU1n9XHXEBEa3YM7w85UBv
+f/8IdlpiyHgPkg+1DdvuToOa0Xhejnq/aCcNyErjjNN26k/06joO3+feYvsn1tTo
+Xomif4HUU+SE5xIGiuPHcDwJtjk/Iugh+pNPdltOguxq2E7m8upQvwShe2A6azD1
+wV9HiyFtkf7tlp4f900MmRybkHb7zXfgXBwlf7waa2RP3UDZbJAZZZkOFQmPcujN
+vkAz/ghaf0Uf2Ix/P0Zh69LUnotPVa35AgMBAAGjgYMwgYAwDgYDVR0PAQH/BAQD
+AgGmMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTAD
+AQH/MB0GA1UdDgQWBBTozd97no7w/qAJQZxNFwSYq6ydVjAfBgNVHSMEGDAWgBQv
+9hMRcplGNs6/zYLAYy4F43ASsjANBgkqhkiG9w0BAQsFAAOCAgEApXU0I4IWvGYo
+qpU0EdDcja7qAV3MaXQRk5hgViaSd+iAd3I04EMDeWomTVb3ZFSPBR2cwPy3vJ6Y
+cLx1gxRCH2gJNj85EjwQ44Fj4/gbSnx2rNVeTmzjyQ4bt6dzdaorGW6ydZgqfX3f
+n++wx9an7Q56NuBOLihInTMHOocVnizHrse5zXN3dXMZxBOU1EHCgKi9xAGMZRmR
+r+AYYXcWB6PRxxwCX7VG4STGYz+GRb4rx0hEl/oQatMqxatN1nAvXCwBASWp47UX
+3n3Tl0/jq0c7DSbL5i+VQridDaifRozBbOkDowS+JDbV1BzYQ/oo9C3kzjraRCXI
+ZJrO9pMNTyLHOXwC9LdlN/30Pe1j40jVo1CR29uONeqmUeJKHfPFBtucVE7ohqHu
+ESHuxZWb0VP7uVWaVIOQ9l8XZcLXtnmSQpHW+4aYbE4q9L0abZ312yRAtyOM/NSH
+wdJn8436jJnu1t8Va85M6lzQHY1E0gxuT9cw02PuS65odFseW7H9gGtlqlOzfVV2
+/psCLlAjVSRQ78zIH6y8DvWGMqabyznIAgZf/PVj9cydPA1he9We6V//Kdcv/Bb8
+RQyryujLBalUTyA9AtDGijp+NWkb1h1uyX0vhiIjJubEu6KKQ5YrZkLXjz+E4I20
+E5GmOYoJhLGkeoUjoTC2wuNFsLFJJHs=
+-----END CERTIFICATE-----
diff --git a/test/core/tsi/test_creds/spiffe_end2end/intermediate_gen.sh b/test/core/tsi/test_creds/spiffe_end2end/intermediate_gen.sh
new file mode 100644
index 0000000..6fa8698
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/intermediate_gen.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+# Copyright 2025 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.
+
+set -e
+
+# Meant to be run from testdata/spiffe_end2end/
+# Sets up an intermediate ca, generates certificates, then copies then up and deletes unnecessary files
+
+rm -rf intermediate_ca
+mkdir intermediate_ca
+cp intermediate.cnf intermediate_ca/
+cp spiffe-openssl.cnf intermediate_ca/
+pushd intermediate_ca
+
+# Generating the intermediate CA
+openssl genrsa -out temp.rsa 2048
+openssl pkcs8 -topk8 -in temp.rsa -out intermediate_ca.key -nocrypt
+rm temp.rsa
+openssl req -key intermediate_ca.key -new -out temp.csr -config intermediate.cnf
+openssl x509 -req -days 3650 -in temp.csr -CA "../ca.pem" -CAkey "../ca.key" -CAcreateserial -out intermediate_ca.pem -extfile intermediate.cnf -extensions 'v3_req'
+
+# Generating the leaf and chain
+openssl genrsa -out temp.rsa 2048
+openssl pkcs8 -topk8 -in temp.rsa -out leaf_signed_by_intermediate.key -nocrypt
+openssl req -new -key leaf_signed_by_intermediate.key -out spiffe-cert.csr \
+ -subj /C=US/ST=CA/L=SVL/O=gRPC/CN=testserver/ \
+ -config spiffe-openssl.cnf -reqexts spiffe_server_e2e
+openssl x509 -req -CA intermediate_ca.pem -CAkey intermediate_ca.key -CAcreateserial \
+ -in spiffe-cert.csr -out leaf_signed_by_intermediate.pem -extensions spiffe_server_e2e \
+ -extfile spiffe-openssl.cnf -days 3650 -sha256
+cat leaf_signed_by_intermediate.pem intermediate_ca.pem > leaf_and_intermediate_chain.pem
+
+popd
+
+# Copy files up to the higher directory
+cp "./intermediate_ca/leaf_signed_by_intermediate.key" ./
+cp "./intermediate_ca/leaf_signed_by_intermediate.pem" ./
+cp "./intermediate_ca/leaf_and_intermediate_chain.pem" ./
+cp "./intermediate_ca/intermediate_ca.key" ./
+cp "./intermediate_ca/intermediate_ca.pem" ./
+
+rm ca.srl
+rm -rf intermediate_ca
diff --git a/test/core/tsi/test_creds/spiffe_end2end/invalid_utf8_san.key b/test/core/tsi/test_creds/spiffe_end2end/invalid_utf8_san.key
new file mode 100644
index 0000000..53884a2
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/invalid_utf8_san.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCsHcgPDkXqAKpD
+HdrTbsm5yXBXZPvNPc1ME5wgXdh1BWwSY6SIgmHcsdIHOP+6wZtp+NDZVcsVT+8M
+gTbE3a19dvJ6idU8n8vytuF5TwQ8L26QxeJtMTTkmn8M2zz3Jw8cb2UJnP5ZAyOa
+S3b77+NZ6UYHk8eoXx5InoRzRgf7h1IxaUHNu89CA7sprgMRI50WuUV+9hzxNpUG
+FtYpWODfj17QJUUfdJVlz/6Gr3tEEEybsKoDFmM0F2TVe9pueP+VwQ0QU93siu8G
+1g1YL0lrsCakQKnbjAJtFpSaVU1LPjr6WKaz8Y0e6kBmTNHpa0AG231FFYWgcuAH
+M9/ZrLmPAgMBAAECggEAEJe2Z60qlEqIH3xLUkLF88E1sVcs5zZkCkeBr6Ucbvuc
+1ZEBlZ9sK8iAxO17yddMUkUIbUtWDrHsC7qROx1b0lKbQbDz+6o0ESwMRJOXhu3T
+rOVG1Nv/i2JRiT3BfNopHMaoaOrXomEpF4Da8aLIuixA8g4PG25ADyQpBKvlrRvP
+26pSzi1yQqajCD3ilZzx+aBukboBZvz727iQw2ylQSj8BqLZFM7hrpWf6QtYeILc
+iqz9xMAwGV9OOavkpMIeVliAl6pSTG0AZpz1eQqcpJ4UKdmK26u0RlwjXidk2w05
+sbyxDQ4exsxb9pH2FtNBw5u+W1l2ZwGeD9660MlBcQKBgQDqLBd0ejhgokyixg99
+AUEilgrUoWfa+Kv+ZooiLrJ4ZWesvryWqf0ytOYn1J0W4otUIeZwY3W+pAMf2IC4
+Ftx4AcsiOoOIxDdKEQ0PiRM2tiY3hGxKMIk1Rszv52KvhybxPm8I7pDQ+ftAGZx7
+ss+/gzRl7Qy1Q5h0SIpK0n5xgwKBgQC8KONT6RmUD0TDWCfZogQLib2/ylOKTD0A
+G0cnDcyMtYvkU75uc9jaz7xv6XN+4uIz+gLvFCmQKLLL9VvM0geJbEyCXrtDn3s9
+Fai8FkIDBNiXM18p4/VFKikodJkwq5020CgQnM+u5db7pImS4Q6279vZOWjF+Ibo
+e8YUcJvWBQKBgFO7lF933HThU9nYTG18Z1gAorrJRCOOS6HZ9Iaof/SA/gNM6I5P
+FlRtTubrJYirVPqubIG/brjOWLeoaM5qjkbzDTmeBq5+yu5XDn6BmzczBpNPbJFS
+rZ05Rcp6yFTPVONqWbWt6GZxHKAdCFFK7PIj7jaL/sFJ/FeIILsEbWYVAoGBAKdM
+d6whI1QJK2vBE1Wnf48+vo8r95GM7eQNXPt1gn1N4n92kmik6pjLglHDMaUNWHE8
+h6VJuRbriOZeV2xLBtaCi5iTYE/lGm+atXffFnyWr3no/GFDxc3kicnluluTC2s0
+M5tpwiNRSxBeINCO+UVhCYogSG9V5aYKf3mnEnh9AoGAYi0UmjbiKZYWjVI7hNn6
+dqUwXJk8y3Bq0bsp1hLzn7OOChGhLR4hrrMLQHQDDlPHg8pK9UJnovGW80nOyNrw
+IPLHW8keC3FPwUaEQN05gSc3HQ3scY5d/E7nf2eNk3HHjEdRQS3meWIhH1P2dR1y
+M1AO/1HkIycWAliDtT/987c=
+-----END PRIVATE KEY-----
diff --git a/test/core/tsi/test_creds/spiffe_end2end/invalid_utf8_san_spiffe.pem b/test/core/tsi/test_creds/spiffe_end2end/invalid_utf8_san_spiffe.pem
new file mode 100644
index 0000000..419a93b
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/invalid_utf8_san_spiffe.pem
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIErTCCApWgAwIBAgIUDZpLwN2bBsy5lvMkeCC7DgYl95kwDQYJKoZIhvcNAQEL
+BQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMSEwHwYDVQQKDBhJbnRlcm5l
+dCBXaWRnaXRzIFB0eSBMdGQxGzAZBgNVBAMMEmZvby5iYXIuaG9vLmNhLmNvbTAe
+Fw0yNTA2MjUyMDQ1MzhaFw0zNTA2MjMyMDQ1MzhaMEwxCzAJBgNVBAYTAlVTMQsw
+CQYDVQQIDAJDQTEMMAoGA1UEBwwDU1ZMMQ0wCwYDVQQKDARnUlBDMRMwEQYDVQQD
+DAp0ZXN0Y2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArB3I
+Dw5F6gCqQx3a027JuclwV2T7zT3NTBOcIF3YdQVsEmOkiIJh3LHSBzj/usGbafjQ
+2VXLFU/vDIE2xN2tfXbyeonVPJ/L8rbheU8EPC9ukMXibTE05Jp/DNs89ycPHG9l
+CZz+WQMjmkt2++/jWelGB5PHqF8eSJ6Ec0YH+4dSMWlBzbvPQgO7Ka4DESOdFrlF
+fvYc8TaVBhbWKVjg349e0CVFH3SVZc/+hq97RBBMm7CqAxZjNBdk1Xvabnj/lcEN
+EFPd7IrvBtYNWC9Ja7AmpECp24wCbRaUmlVNSz46+lims/GNHupAZkzR6WtABtt9
+RRWFoHLgBzPf2ay5jwIDAQABo3kwdzA1BgNVHREELjAshipzcGlmZmU6Ly9mb28u
+YmFyLmNvbS9jbGllbnQvd29ya2xvYWQvMVx4YzAwHQYDVR0OBBYEFBwEmOn47l9h
+nK4rCTbox85Au0c5MB8GA1UdIwQYMBaAFC/2ExFymUY2zr/NgsBjLgXjcBKyMA0G
+CSqGSIb3DQEBCwUAA4ICAQCqgy3ZgqsPI2KO4+VBHU0hdpxR86fTKYslRbvC9i8G
+03I0+SJwI3YpXZpG1Mc3xwVYqRY8VuY9y0Ku2zKW5p7ako/wvcV1KhI/Ueq8qzCR
+vlL0Fse5DnXmwFZBS/hEVc97d0+ERXveyKBjaU+tC7KwHGfFefDUt+pyijYNALpK
+WpDwXhAEy9p6qmv2bjyMtVhj4nP/gG1jXozRwmdPOQcDe97trfQcq/n1zr9x5eUe
+qGD2iQhFBlgR+4Rf/w+M5kpPOQ8hz4dKySz5nF/u8+tvc32S9RivxHA4t5GaF5xg
+AOI3lSyoC6SJBt75ADzRLpbVYkharUTA5McitM1fIKeBtzJ5H3vqiHh2L1ESv/Ud
+2FjD1siLlrUjpffoUgtppr4bWSWXMxCiaKlWgqbMvgBaAIm1xZdkk3ZH36J4jQnZ
+/svRU5jpJpZu7K8/w2TmSj7l//16h+XmYOHE2BmVHliFqy2aBUi1YYiNhJi+xDdL
+mrJzn4KEgVY7fGmD3iP0oM5+gh0cKDdZ4zi/KgYFhyt8o+qLTe3EXq1pwnOXQn1p
+2LNymeGg0A69rhYot6AOgq6PecXFVvY7RLkrOqfex86Lk8aUl5+prcNRvlOgdmoh
+x1NAfGm0Ae4Bd7/t0WuZaEYtUtY+qfVX68rLP58jZXQlN+XwuDqX4bl1UWkfGCjc
+fw==
+-----END CERTIFICATE-----
diff --git a/test/core/tsi/test_creds/spiffe_end2end/leaf_and_intermediate_chain.pem b/test/core/tsi/test_creds/spiffe_end2end/leaf_and_intermediate_chain.pem
new file mode 100644
index 0000000..a1cd901
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/leaf_and_intermediate_chain.pem
@@ -0,0 +1,50 @@
+-----BEGIN CERTIFICATE-----
+MIIDvjCCAqagAwIBAgIUSuuNuIcMCQ1UZrOtCL689eeZx9EwDQYJKoZIhvcNAQEL
+BQAwJzElMCMGA1UEAwwcaW50ZXJtZWRpYXRlY2VydC5leGFtcGxlLmNvbTAeFw0y
+NTAzMTgxNjE4NTFaFw0zNTAzMTYxNjE4NTFaMEwxCzAJBgNVBAYTAlVTMQswCQYD
+VQQIDAJDQTEMMAoGA1UEBwwDU1ZMMQ0wCwYDVQQKDARnUlBDMRMwEQYDVQQDDAp0
+ZXN0c2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAue2BY6B4
+JNz5SnYr0b4IUsXfpSxqXzpx3CymRzNiPL+ZYERQd+5PPbwyj/K0ZUPNcdGtgj0i
+jCSpKl6fGNx11vY/Z5BS7DN9Z6Fqf8/sBr5BIko33UafdqB/s1avrIzFNRD1n6Fu
+X1EwlQpXwjiTMLswuf6X6mJVJ2VhA/xtyO4S6fTeK2jUlFn4MYHVczJBDTiMCUPX
+1Z/9SXkcwZbQTbRaWvd6c0OEWZKSWxSzWG6d4qOmq9C0Tse39cmjuDpeBvtiGik4
+pYrTmmswiQ8lgLbOY1/YGjvOD+qlxXkX9Y/cYjdygrQoY7mqnjMcUFvWK/OW0XCB
+CJ1jeSflrYUd4QIDAQABo4G8MIG5MHcGA1UdEQRwMG6CECoudGVzdC5nb29nbGUu
+ZnKCGHdhdGVyem9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29t
+hwTAqAEDhiZzcGlmZmU6Ly9leGFtcGxlLmNvbS93b3JrbG9hZC85ZWViY2NkMjAd
+BgNVHQ4EFgQUsMpVfuYdrW5+i5mFW/hCaOZiKKcwHwYDVR0jBBgwFoAU6M3fe56O
+8P6gCUGcTRcEmKusnVYwDQYJKoZIhvcNAQELBQADggEBABReEQ2cIaZv5wZKOjFS
+5b5QfpkbR29JTQQxrkiqdXcnonX+Q/dCH+tnzUp9LCCIsVjp6is4pdKTvPNg1ZPT
+WB0KEFpVG9LAOmfcgu9pwIsKUCS384PeVhffMKXsKWfgfRGP1E7XeEIdSgpwPKzK
+Defa7kwTG276xre/36WOAu9RZ32s0ltXyvHtTi0pfaGuOmryX+XeHtVpxQSjhdDB
+tpLvolS+7cRsvKNKIL5X/789IKbdShMgpwPDDe6Z3riBjGPBydnA40IofRc8y3jL
+0TKiBiKYOz8nj6DLAiVPqQ2vo5NzihNoqgu/HpkhSG26OAJq2NDYurMsT+Myiuq4
+YkE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEkzCCAnugAwIBAgIUa4ZkytupQZ5OwDMPw/vRY8deFc4wDQYJKoZIhvcNAQEL
+BQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMSEwHwYDVQQKDBhJbnRlcm5l
+dCBXaWRnaXRzIFB0eSBMdGQxGzAZBgNVBAMMEmZvby5iYXIuaG9vLmNhLmNvbTAe
+Fw0yNTAzMTgxNjE4NTFaFw0zNTAzMTYxNjE4NTFaMCcxJTAjBgNVBAMMHGludGVy
+bWVkaWF0ZWNlcnQuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQC7XOGe2r5KNdo7ELwJ7GRAIVqxAkT9bB6+7rUyDhJnxWeI7E9vr+I4
+clDRYREoxnuXq6oGTICKlWivvrxBASSdA0PpjLsMNnNU1n9XHXEBEa3YM7w85UBv
+f/8IdlpiyHgPkg+1DdvuToOa0Xhejnq/aCcNyErjjNN26k/06joO3+feYvsn1tTo
+Xomif4HUU+SE5xIGiuPHcDwJtjk/Iugh+pNPdltOguxq2E7m8upQvwShe2A6azD1
+wV9HiyFtkf7tlp4f900MmRybkHb7zXfgXBwlf7waa2RP3UDZbJAZZZkOFQmPcujN
+vkAz/ghaf0Uf2Ix/P0Zh69LUnotPVa35AgMBAAGjgYMwgYAwDgYDVR0PAQH/BAQD
+AgGmMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTAD
+AQH/MB0GA1UdDgQWBBTozd97no7w/qAJQZxNFwSYq6ydVjAfBgNVHSMEGDAWgBQv
+9hMRcplGNs6/zYLAYy4F43ASsjANBgkqhkiG9w0BAQsFAAOCAgEApXU0I4IWvGYo
+qpU0EdDcja7qAV3MaXQRk5hgViaSd+iAd3I04EMDeWomTVb3ZFSPBR2cwPy3vJ6Y
+cLx1gxRCH2gJNj85EjwQ44Fj4/gbSnx2rNVeTmzjyQ4bt6dzdaorGW6ydZgqfX3f
+n++wx9an7Q56NuBOLihInTMHOocVnizHrse5zXN3dXMZxBOU1EHCgKi9xAGMZRmR
+r+AYYXcWB6PRxxwCX7VG4STGYz+GRb4rx0hEl/oQatMqxatN1nAvXCwBASWp47UX
+3n3Tl0/jq0c7DSbL5i+VQridDaifRozBbOkDowS+JDbV1BzYQ/oo9C3kzjraRCXI
+ZJrO9pMNTyLHOXwC9LdlN/30Pe1j40jVo1CR29uONeqmUeJKHfPFBtucVE7ohqHu
+ESHuxZWb0VP7uVWaVIOQ9l8XZcLXtnmSQpHW+4aYbE4q9L0abZ312yRAtyOM/NSH
+wdJn8436jJnu1t8Va85M6lzQHY1E0gxuT9cw02PuS65odFseW7H9gGtlqlOzfVV2
+/psCLlAjVSRQ78zIH6y8DvWGMqabyznIAgZf/PVj9cydPA1he9We6V//Kdcv/Bb8
+RQyryujLBalUTyA9AtDGijp+NWkb1h1uyX0vhiIjJubEu6KKQ5YrZkLXjz+E4I20
+E5GmOYoJhLGkeoUjoTC2wuNFsLFJJHs=
+-----END CERTIFICATE-----
diff --git a/test/core/tsi/test_creds/spiffe_end2end/leaf_signed_by_intermediate.key b/test/core/tsi/test_creds/spiffe_end2end/leaf_signed_by_intermediate.key
new file mode 100644
index 0000000..23c07a7
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/leaf_signed_by_intermediate.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC57YFjoHgk3PlK
+divRvghSxd+lLGpfOnHcLKZHM2I8v5lgRFB37k89vDKP8rRlQ81x0a2CPSKMJKkq
+Xp8Y3HXW9j9nkFLsM31noWp/z+wGvkEiSjfdRp92oH+zVq+sjMU1EPWfoW5fUTCV
+ClfCOJMwuzC5/pfqYlUnZWED/G3I7hLp9N4raNSUWfgxgdVzMkENOIwJQ9fVn/1J
+eRzBltBNtFpa93pzQ4RZkpJbFLNYbp3io6ar0LROx7f1yaO4Ol4G+2IaKTilitOa
+azCJDyWAts5jX9gaO84P6qXFeRf1j9xiN3KCtChjuaqeMxxQW9Yr85bRcIEInWN5
+J+WthR3hAgMBAAECggEACGVwarbpIAyK1TUQkeVnl7HUAX/3yoa/lDBfvq7ESp8i
+KnPRULYuRcjfPWsJ4JoTD7cW418WhZ7E9SruWzt+9pL6nmcZCI3DdbPnVN/nTi9m
+rnlMrCljm4JAXBFfx+54PCcQuYmLLAKlC5D60TR3+KsWZmrX8kuoIeohC0KZtFlw
+xHC1fz6k9qbStDW8UBWUY8I1N2Zh9U5kYGNG9j5rLYzs7zJ23xWmiGOwywGlLSii
+2i9vC+XwQATFmgFyVM9F9W4P//ElOADDXDhBU8R3UXXoK5+s+Bx12gyTeBfSLgy1
+I5/ee2rFJdlGUsOvEO39npHDCeFDKe0MjeJY8FcSUQKBgQDcXJYnxM6ULY3T9jrf
+rSiVi4S7Rewq7KdFDKo32Lqr85VJ3+nqOtLjdyTnHpsRYQSBDbMluMjImfcWxLym
+e/3ldNdRR4W6YSdL8kvnKvxsCYR/XC5wihfgKLY3yU7iydrJjFdQZlmFLbCAom7U
+P26OJXo8STCD9BURFKlzm24W0QKBgQDX/0lBWuWNhF8h65WK7Lp8zj2LzZFfNOB2
+YysKKteYxDmyV7dKZogG9KDDbDpiylDhg58i8c949/73diCGbZHRMCFFeTO6SOjx
++2Z0iLgMs1IxqLCLhv/H/nfyNii+/G80JDHKR0d0xFQRfAzIVbBaxjL6JiI9aJdl
+Y+yJYHZ6EQKBgQCTtPftVk1GI7bSolTaQ35VpzxkLz1blF+WuCOM5ZhS1CZ4az+n
+AqEDWYSB6xD0ODzFqIIxwcfMNu/Z8iulyKzqVkRRtghcYuztSk84d6bVYBfRKt/y
+DY5tcbyabjSTbcxalticCs7spzCNONPjL1WSsGpb6I89k6lfVqMy27eSUQKBgQCG
+rFdidmg2Klieb6LX8e38ryLUriF7uR5S8lX9iuTODVrkBaj0rKUXoSMzdaGZwwdW
+9JgeU6LGi+nfJTn2Vw7Z3SaBiKZl+du0NMmW0z/eO1h+Oe2JsWx9p/3leTtCiWZU
+nlSlCHhXJ6o7FJtrtXG1x2o1ad1jaHks8Hak2Q5F4QKBgQCQpMwv662qJai06bpQ
+Q1A3rzn/LbDIPZmkadRE9+Lv1KDHp3uhkxX53DK7K560adwUYurzfsyp3VV763h4
+73Lk/z6wOTjmXNO3b/+asKx9z063XZc0vUxct7Del5+C28s026XfUOy30IMaIQqr
+ei17qa7SQn3GwetSxblr+IQKHg==
+-----END PRIVATE KEY-----
diff --git a/test/core/tsi/test_creds/spiffe_end2end/leaf_signed_by_intermediate.pem b/test/core/tsi/test_creds/spiffe_end2end/leaf_signed_by_intermediate.pem
new file mode 100644
index 0000000..e9c9f06
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/leaf_signed_by_intermediate.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDvjCCAqagAwIBAgIUSuuNuIcMCQ1UZrOtCL689eeZx9EwDQYJKoZIhvcNAQEL
+BQAwJzElMCMGA1UEAwwcaW50ZXJtZWRpYXRlY2VydC5leGFtcGxlLmNvbTAeFw0y
+NTAzMTgxNjE4NTFaFw0zNTAzMTYxNjE4NTFaMEwxCzAJBgNVBAYTAlVTMQswCQYD
+VQQIDAJDQTEMMAoGA1UEBwwDU1ZMMQ0wCwYDVQQKDARnUlBDMRMwEQYDVQQDDAp0
+ZXN0c2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAue2BY6B4
+JNz5SnYr0b4IUsXfpSxqXzpx3CymRzNiPL+ZYERQd+5PPbwyj/K0ZUPNcdGtgj0i
+jCSpKl6fGNx11vY/Z5BS7DN9Z6Fqf8/sBr5BIko33UafdqB/s1avrIzFNRD1n6Fu
+X1EwlQpXwjiTMLswuf6X6mJVJ2VhA/xtyO4S6fTeK2jUlFn4MYHVczJBDTiMCUPX
+1Z/9SXkcwZbQTbRaWvd6c0OEWZKSWxSzWG6d4qOmq9C0Tse39cmjuDpeBvtiGik4
+pYrTmmswiQ8lgLbOY1/YGjvOD+qlxXkX9Y/cYjdygrQoY7mqnjMcUFvWK/OW0XCB
+CJ1jeSflrYUd4QIDAQABo4G8MIG5MHcGA1UdEQRwMG6CECoudGVzdC5nb29nbGUu
+ZnKCGHdhdGVyem9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29t
+hwTAqAEDhiZzcGlmZmU6Ly9leGFtcGxlLmNvbS93b3JrbG9hZC85ZWViY2NkMjAd
+BgNVHQ4EFgQUsMpVfuYdrW5+i5mFW/hCaOZiKKcwHwYDVR0jBBgwFoAU6M3fe56O
+8P6gCUGcTRcEmKusnVYwDQYJKoZIhvcNAQELBQADggEBABReEQ2cIaZv5wZKOjFS
+5b5QfpkbR29JTQQxrkiqdXcnonX+Q/dCH+tnzUp9LCCIsVjp6is4pdKTvPNg1ZPT
+WB0KEFpVG9LAOmfcgu9pwIsKUCS384PeVhffMKXsKWfgfRGP1E7XeEIdSgpwPKzK
+Defa7kwTG276xre/36WOAu9RZ32s0ltXyvHtTi0pfaGuOmryX+XeHtVpxQSjhdDB
+tpLvolS+7cRsvKNKIL5X/789IKbdShMgpwPDDe6Z3riBjGPBydnA40IofRc8y3jL
+0TKiBiKYOz8nj6DLAiVPqQ2vo5NzihNoqgu/HpkhSG26OAJq2NDYurMsT+Myiuq4
+YkE=
+-----END CERTIFICATE-----
diff --git a/test/core/tsi/test_creds/spiffe_end2end/multi_san.key b/test/core/tsi/test_creds/spiffe_end2end/multi_san.key
new file mode 100644
index 0000000..3e6f46f
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/multi_san.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5Ri7ktF7OC/w1
+EH37GBcrUWQ9bIcThAeGWMSvOGsu/BxU3H7nVFxDi901wlrii+Asj3HnhQQVFxtW
++zGbNhC2ksQHHJCPFYqSyaeQt4GroQ8I5itiHnrRDXB8z1fJDLa6ddmkQORlgpxz
+7kH1ak/pkXM05vq9YifDXrYDgWl0awTbkPNFbUZ/yWcmUJ8qdtuz21WDN491NNtf
+Folpoicm3iT6vVspUUA+2PAwB2DDzma5avXzJ/OkE1QUl1Eko7V0JKkYjon6m2Ix
+nDge81Er4JLhz5KQEbOdbfLPNjtxTMzD0mJLRm5sTqtqqgRWi7ieVumcUOljRdtP
+mrZuAsAJAgMBAAECgf9EHqnfLPIuj/mLCSVIzPK32pUggGappkGeA8VNJ1FBVTCV
+gvyl0T6bFzGeMnd4FDW4LIWUujNmDbjfojv69UXv2sZ4VuhPs3vfvIT/kHj8wZrl
+irsouEwv0OErApEkOqh0ph//ZFyVdhBQD6nwscXv+qqBKOvH9e7oxknlwA8l3hLk
+DkZw6VhHm0TZZK2Zo0zZ4i3P0pKdv1P374F4Gk08HAV8X8Mi2btYnGTDjCDBHvv7
+Chxr9RAqRU4wQFAxt8QkP/AKzxC9iSdziQHmXwaKpz/GVS98p//FcwM7h9rNR2/9
+aW1VY6CFd3x7DUl+a2Iekgt4y+TU6MzWt62OsSECgYEA5mk8gMxitxKSd5R9Jvbm
+KaLXEf2G95PbkyyrR1N3L+v8SvuwTlZH+TWV8UfiZFCF1FDos1lkNW/TO8oTv8SP
+nMKvjJ2NnTb0V14mTmg4EkcBDCZFQ4uc6kMB+eCpfqFPyhfGUdj7k9WuTrVBTH8c
+/GLUk+QajRVZ44cSfCzGO6ECgYEAzdmrXmxLZcUPXddrXQV9P4Z832D39jgw+hiL
+SSUr7dAIQGi6Gg45kqfG7u2+m8HR87RCGIyo7SCkzqC2jyTWq0OaSvFS597qTdYB
+ag90ZN5EYzslyODZdOWcfxEb+BVRsV3sVSI37CuepD7+mR6a7RU3xiK3PFadwWSI
+LqDIa2kCgYAyF573wXeUpY0CQl4TIopXcvefCadWG+cY0BVMpHC9Emj/REjIPynW
+nDfFBQPLG1Z9Gjn9A+otU+pwjTZN8sXmJs1TnM006MWmWOmrEi1ei+FGauuUC75J
+j9fWI2m2Bgv6u8B5+tp3AXWr1uwjXV9H35SnsmezVHj+Zlc2d+WBAQKBgQCLIfx6
+LagoeTERtHCUxwkIztXIBbqTaRdxcvVx0vYGHv30ytQ+mC7BuTT0eSzEChTmK4gh
+H7Ft1QqPvoCl77hIucCntsTahAjJ40PBdACvzKtchrXbOF7CbA+CswW8gGxe/aSn
+RiGWN0VarllXFSCR7i1sdjpzRfgKpvCE05ejQQKBgQCSxGbweGR4eZefTqYd/4vn
+WJapI9siN95SKg0e5ENRYVBvPS+tRZH6PjCep/DNlU3IMaAmCIH7j8vdV326OXDc
+VfGCUcbxAmPDo9bqmEc/0ceC4IrWTn0ejGOZIYefHVSiHQogHO/dn/GgMydqLtcs
+OXw/IVWtzvL4iUN8V3m5Ng==
+-----END PRIVATE KEY-----
diff --git a/test/core/tsi/test_creds/spiffe_end2end/multi_san_spiffe.pem b/test/core/tsi/test_creds/spiffe_end2end/multi_san_spiffe.pem
new file mode 100644
index 0000000..a99eaf3
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/multi_san_spiffe.pem
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIE0zCCArugAwIBAgIUeFNyqxRU+RxnKwrjen0Zil6qZJswDQYJKoZIhvcNAQEL
+BQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMSEwHwYDVQQKDBhJbnRlcm5l
+dCBXaWRnaXRzIFB0eSBMdGQxGzAZBgNVBAMMEmZvby5iYXIuaG9vLmNhLmNvbTAe
+Fw0yNTA2MjUxOTUwMThaFw0zNTA2MjMxOTUwMThaMEwxCzAJBgNVBAYTAlVTMQsw
+CQYDVQQIDAJDQTEMMAoGA1UEBwwDU1ZMMQ0wCwYDVQQKDARnUlBDMRMwEQYDVQQD
+DAp0ZXN0Y2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuUYu
+5LRezgv8NRB9+xgXK1FkPWyHE4QHhljErzhrLvwcVNx+51RcQ4vdNcJa4ovgLI9x
+54UEFRcbVvsxmzYQtpLEBxyQjxWKksmnkLeBq6EPCOYrYh560Q1wfM9XyQy2unXZ
+pEDkZYKcc+5B9WpP6ZFzNOb6vWInw162A4FpdGsE25DzRW1Gf8lnJlCfKnbbs9tV
+gzePdTTbXxaJaaInJt4k+r1bKVFAPtjwMAdgw85muWr18yfzpBNUFJdRJKO1dCSp
+GI6J+ptiMZw4HvNRK+CS4c+SkBGznW3yzzY7cUzMw9JiS0ZubE6raqoEVou4nlbp
+nFDpY0XbT5q2bgLACQIDAQABo4GeMIGbMFkGA1UdEQRSMFCGJnNwaWZmZTovL2Zv
+by5iYXIuY29tL2NsaWVudC93b3JrbG9hZC8xhiZzcGlmZmU6Ly9mb28uYmFyLmNv
+bS9jbGllbnQvd29ya2xvYWQvMjAdBgNVHQ4EFgQUZzYg1h15LWXJsHFO2X2DqskW
+SfwwHwYDVR0jBBgwFoAUL/YTEXKZRjbOv82CwGMuBeNwErIwDQYJKoZIhvcNAQEL
+BQADggIBAAHXGB9qEt/q/6JClvcbvt19l1C7YabLJ8HBYo9zIPnjDE306AKK9umK
+/ZJ4BovfMcR3xbeHrem3h4nIuS6s84vAhKTKVHIZZl9ix4NrkPPtriQLsRxRxRoi
+Ivk5BgylVj8TxMLMfoOinzrxXX7nQ+Yw6nBLlpo8atPS7qRXpene2p8814Eqk2UA
+svicIlGFm2YnizjluYyICf8PuVXFxwFDRMvrTicKD8i/WiFPKAlLsaH0WdL36hRt
+PnA7Pd7Ska7S7xgcYeJ2RQ2bMUzKtRL67tXHIrUKd/5l4kb843TIoA11JNO9vT+J
+iyAYpJErAHK3mczS7GGMDr5SIOuhZlLJV1XA3p7eqhlw5B2W77255WCIuHX3Mhm3
+4gq/ObivSwMnR/ZPV0PRDKoqMy5TmeZHa8TUC455cHm9HXnF6owpanLoI+tdV7tN
+Em0p0x4u2jXqTFbacLX3KENodm+4d6ESHGucARBo6d4b693qyNj7nxDUYmRB70W4
+aX5JVTJ/J6sN3Df741VvJX5sjNID3Z7B/ZFR+VQIpp6GqdhrQT1W6JuMymLAGQRi
+CJeeFKukArX35hwaXtWziPqp2QDOHa8d20GBMJSdVRhoG2LkAwxzzA15F2UEj+de
+AVXizkiJ2+uRtAs4uHmVV8oSl1L2UAMFTTvKX//r7XSj/TkR3x4G
+-----END CERTIFICATE-----
diff --git a/test/core/tsi/test_creds/spiffe_end2end/server.key b/test/core/tsi/test_creds/spiffe_end2end/server.key
new file mode 100644
index 0000000..6bc006c
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/server.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCpfq2SvTp7uxmk
+ux2D9fM3yBOum3YjRx3HLplC0GkBOHCwvsvw03ipQk8DJOSz0enun6SaLEhOZJ3Z
+9Y24qEeMEeWfm2rWrCzULmf2qH9lmJ1fYdj3V/v8x+ulgUWiSRB9LiwJzSFt0uTK
+L+PZZLjfkze8SFB6Zn6NBnTKzWbLs/ZxjiJcin8YLMoAnS5FfEVMfaDwE3RO9kED
+Jt0YG8bf6p8Tv773aOrDW2WszxWSUwoVMe2ByHajf6vC7/oyh1lrq0QIUy0AnXst
+3DKPolenY+Qt+17XP9HZf52cLVolzuhjw+yjEKMxbwHtXewayMHkoV5ySck5VrC9
+iF34jyTfAgMBAAECggEABxYYw63NqCamZ9C65y19QbPHvuEZC1iJ0i4MklKMUVS+
+wWT4udTdkpYHH6aHOU1pbmSWSSvYyg5C2z388t92PlDxPL8OtHL2XNij2BgrW2AK
+FFbO72hqTKM/IuhItQRBADrR9IbzAthyYFGoQQqJAC/zgJe1ZairWdIPR/tWdOW7
+1+ctVWIMCKpTpomuoI1oK7PJos6ciIQ5yCWPhMVyQ4EInG4YHF4r2agxWyaF5MLT
+gu/qH7tKvkCgo+jjofEntCrw7QYqW8HSFLGm5wxus4/vxoyrwRAgwSotm13tpbg8
+ODleK3u8S50zXuVk/+smskHv1QMGVAxuVFi7ZpPHAQKBgQDU6KpvYOZPR5+XdQNn
+BBGbkgaw0qQ7E+1w3pbcaEpglcLQRQn2fjwUOc7YWV8SpMXIneqfhvGLPJXKExXK
+coCdjyPAy77zAt7HOVJwAfYaLaoyxnAxnFIgTCLUOchvysWuvauYOkBF5d1GCbXK
+v5XUqbLiWnQoLPESubm/IJEEfwKBgQDLzJ/wmEn8vCZk6saEJoFkfG43oJaGrDOT
+kKE5pgLSNOtBB6ML4xliU1rwbHz+gpmQKBpb1zQedSyB9rd5G+NzPFQ14RoTsR10
+71S6o8jacIJxo6vZWrS3yOJSBcEhU2K8yFRGgI3cy53JtT5gZIckyINnovdTZpR7
+7vdhPngvoQKBgQC4FH4flGEsVJnIYrRRYjQzFLtSMJ0sMxuNBfbblBQSlAl+9uIZ
+S6V4O076wbKwtOQIg3iy+wJmRzifIYcqpXtMPtFDxSVQiL/C8m2zSA7XLXpFMksY
+qbZcFFfctCT3exMV9QmisWKWzJpXDtgt9x19ZEe0604Y2lqIvSm69VZM0wKBgHHG
+I8gijLOOqnSOaRMfl/7sRG/DtCUs/4IzLP7NAiguKOPeCpU5TFOLK2qrdkBz0p0a
+9lQtWUhjq9xGSvlOq3UKygxmRbOWnlZIwmmLxDbGttwQPoESVW+As3CNC9u5/JZd
+1EajwA7ykX0pNNCFbrkHQ+zYmvCxhNWHfA2K3XfhAoGAUtwFJFNRCYIGiFVrQ19n
+nwCt4+3zYT8wTL1EbyWa0pKhADFSwnwdhJ1ZNev1hKu663+iQO5KdlBeoicicpOA
+hNkrw5Al4pvJBp+WV6pqCLtMbuJUBYAWOpUipGnnJsEYTC9SxDnIqh+6Qc7tsEYv
+xDykqFvrNxQXLahWavYW8Lg=
+-----END PRIVATE KEY-----
diff --git a/test/core/tsi/test_creds/spiffe_end2end/server_spiffe.pem b/test/core/tsi/test_creds/spiffe_end2end/server_spiffe.pem
new file mode 100644
index 0000000..f607d7c
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/server_spiffe.pem
@@ -0,0 +1,29 @@
+-----BEGIN CERTIFICATE-----
+MIIE+DCCAuCgAwIBAgIUBoJ396S7DO0kRqDUn4TB6zKKIIowDQYJKoZIhvcNAQEL
+BQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMSEwHwYDVQQKDBhJbnRlcm5l
+dCBXaWRnaXRzIFB0eSBMdGQxGzAZBgNVBAMMEmZvby5iYXIuaG9vLmNhLmNvbTAe
+Fw0yNTAzMDcxOTM0NDZaFw0zNTAzMDUxOTM0NDZaMFMxCzAJBgNVBAYTAlVTMQsw
+CQYDVQQIDAJDQTEMMAoGA1UEBwwDU1ZMMQ0wCwYDVQQKDARnUlBDMRowGAYDVQQD
+DBEqLnRlc3QuZ29vZ2xlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAKl+rZK9Onu7GaS7HYP18zfIE66bdiNHHccumULQaQE4cLC+y/DTeKlCTwMk
+5LPR6e6fpJosSE5kndn1jbioR4wR5Z+batasLNQuZ/aof2WYnV9h2PdX+/zH66WB
+RaJJEH0uLAnNIW3S5Mov49lkuN+TN7xIUHpmfo0GdMrNZsuz9nGOIlyKfxgsygCd
+LkV8RUx9oPATdE72QQMm3Rgbxt/qnxO/vvdo6sNbZazPFZJTChUx7YHIdqN/q8Lv
++jKHWWurRAhTLQCdey3cMo+iV6dj5C37Xtc/0dl/nZwtWiXO6GPD7KMQozFvAe1d
+7BrIweShXnJJyTlWsL2IXfiPJN8CAwEAAaOBvDCBuTB3BgNVHREEcDBughAqLnRl
+c3QuZ29vZ2xlLmZyghh3YXRlcnpvb2kudGVzdC5nb29nbGUuYmWCEioudGVzdC55
+b3V0dWJlLmNvbYcEwKgBA4Ymc3BpZmZlOi8vZXhhbXBsZS5jb20vd29ya2xvYWQv
+OWVlYmNjZDIwHQYDVR0OBBYEFDJn3wGxfbvBrS/dTLOScNtTMi9EMB8GA1UdIwQY
+MBaAFC/2ExFymUY2zr/NgsBjLgXjcBKyMA0GCSqGSIb3DQEBCwUAA4ICAQCaWp4p
+qmG5il353PrAedmk7kk5+YigeX/uUBYCdOm/j/M4IpJpsmkfFStYIhJAq3H2KViQ
+x7MNRLd15Z2fcdx9qWJ7aHq9fp9W7L1dINbiFD0KTp9c9oD2HpXnldy8r1EIDDaQ
+VxIlJDd3rUsebLT4oPZ13HQDp5clJ44WdcybqFgbvcJoxhXYMlBXOKwsLlm+abDO
+0XWEfubO/irsrsByUmaoGc/0Adf2O+wgDo9xSB4d2b601K02dYpl71NNelRvBrB5
+FubExuAef+4xgha/eMn+qcRBhH2jv7Fjc1HuKH8srukcRJHblQy96reJj2bcDhp/
+bPJ2xBzc13UZ2do9A209qGRCHONd5VH5NY++484XlFf12jJUEQiUCT7jOReFbUbR
+FkiHbyapVEmqnI8RifPbRIov/XzCqkaCZuMW+64cr6eBpuoi8YV+xV37zHYhT2bf
+Wpk83uyuvw6qZApQPLc2MQ6MpRv3740S/0+lKjNoG5xi2loNCMTGCgtt7EQT93cZ
+z/picgGigC1qIPonukQsdf+cCD3eC0Kd45VEj8bon4MxmjbROUdVzVwG4wmptF7l
+jB3GjnqHfEJl1RuDsLFEtBSqjESQTeeOuNMcPKSfNVbePLf4CxuRmIXumpwAxicw
+aeSaOwEPybd7LBd8qJHSRgfKMMgKnpZgzV4SZQ==
+-----END CERTIFICATE-----
diff --git a/test/core/tsi/test_creds/spiffe_end2end/server_spiffebundle.json b/test/core/tsi/test_creds/spiffe_end2end/server_spiffebundle.json
new file mode 100644
index 0000000..928d15b
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/server_spiffebundle.json
@@ -0,0 +1,18 @@
+{
+ "trust_domains": {
+ "foo.bar.com": {
+ "spiffe_sequence": 12035488,
+ "keys": [
+ {
+ "kty": "RSA",
+ "use": "x509-svid",
+ "x5c": [
+ "MIIFlTCCA32gAwIBAgIUCD73sHXu5IURolSTiJ127a/xUO4wDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxGzAZBgNVBAMMEmZvby5iYXIuaG9vLmNhLmNvbTAeFw0yNTAzMDcxOTM0NDZaFw0yNjAzMDcxOTM0NDZaMFoxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJWQTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRswGQYDVQQDDBJmb28uYmFyLmhvby5jYS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC19PImArmxbwgqo2QG2si8BU6E69Bvyqxz8Je3swxBIGwZ9uIobMSBLeTCYyXuf+o90Zf0kMwzmrAKeLEeky5W/j07zGXAtgUBPA7L1Uk0TxOdJXBCUvEm5Oc4GxubfO7F+pdJKZ+XkRVqbnStGe5qX6KNs5rcJfFUhewbtM0snGEIf2yhaA0mNNuGtlIm4VB7jmiyHNU5YTowByVCVrV3/t2RI9+T4ya0AlkW93rU0M0qQauJ35LkJIXifbzrnLxmztEyb+mnVUB+GJgz01E4teWo/PJb1aNJ/ojf/UONsQ5IFRdza6RhaQB7C+Dxlnt/SJ3MMaxHgVycYuJeVtJQuncGRSuQ2YrmW9b36HVnxa0xBDeSluUjv48hMRlLNaXaH4yuK6oc8TNJie++/ir6Kb4H+0RjcKMGqxZYfotU1obxa+5N3wzGSjUDUrhofzlfvqbp+NCwdFH+qczM4IZPL8YMMh6goKr9BRN9/xRIieotyH6rfKNcnkUgDp750U0cZ7P2eRUpldyc9hZS5AlF4cKQXgLIrv1LrZHkiIietetInUEBAa/PF2YHRLXUyI1PCSBKBu7wdwAU15J9dVFC9jkmOLYhoRdPfrobpWhs5+FfPJumSoiusdGXd7x4l313xi2V02YXz5mRGbT2lCb6aJPweuziiEBZn+5KV++DkQIDAQABo1MwUTAdBgNVHQ4EFgQUL/YTEXKZRjbOv82CwGMuBeNwErIwHwYDVR0jBBgwFoAUL/YTEXKZRjbOv82CwGMuBeNwErIwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAIYtNERVMj4dS/KgoXD3a6mF6KD+IuxDTlVbVTwDJMzdNW5UPB2qCiDKbirK8Eedh90r3qxKzBiQPE5AHDZrbWSBAxDhlpQqCXgx8Z+nCB38K9i8hbKM/ablm9DkeHiHohoP1kJEN2HtPz1K6OabbKbPtVGt8y+QTIlZDodPPzKPmQ6dUTm9tWlU1oIxl4pPVR6WXDr0qAmNRvdW7+8/Ai+gMDi6fKQJCe/r/meVrI9lSrnn8VQyO4xXvyolHEROTUiomlJ1QE9IWAM9LNFOuWFwQjayo2d6O+zHqjGUtbzBHFb8/BLJcQzogWYT0/+rEboJhR+/OGe0ntPPkudmLL0HTx0Q1aajnChMVlaOd2wucePZSa6GqGEVA+lCqwbteqwJCnL7deVHo+UlORFgQwYir2CQbyN9Yd4MtQT7VZuDiVC6tlGbj1ogrpOnW8n49jXrNPWuPz2hcPQb2gzYFGI9WRKIX4SjvvS3QcHUyigQkjAQ12Ldj5CXBYTjSmjcgsa2QfUY5qrXsHLz7e4uXD9XYnB/XEEfxQfQTFEy5CtzTrTxnT+cwRuBooIr6InjqdbdJ+UbkWcIY6w+c7ndFT849pUgtrojbVpm9ZzsmHhAahR/+iHYH+LGeHYIpq71o/YDgM4vV9z3sWic7u1YE9JULBdlaDw8Xik82zjvw4IA="
+ ],
+ "n": "tfTyJgK5sW8IKqNkBtrIvAVOhOvQb8qsc_CXt7MMQSBsGfbiKGzEgS3kwmMl7n_qPdGX9JDMM5qwCnixHpMuVv49O8xlwLYFATwOy9VJNE8TnSVwQlLxJuTnOBsbm3zuxfqXSSmfl5EVam50rRnual-ijbOa3CXxVIXsG7TNLJxhCH9soWgNJjTbhrZSJuFQe45oshzVOWE6MAclQla1d_7dkSPfk-MmtAJZFvd61NDNKkGrid-S5CSF4n2865y8Zs7RMm_pp1VAfhiYM9NROLXlqPzyW9WjSf6I3_1DjbEOSBUXc2ukYWkAewvg8ZZ7f0idzDGsR4FcnGLiXlbSULp3BkUrkNmK5lvW9-h1Z8WtMQQ3kpblI7-PITEZSzWl2h-MriuqHPEzSYnvvv4q-im-B_tEY3CjBqsWWH6LVNaG8WvuTd8Mxko1A1K4aH85X76m6fjQsHRR_qnMzOCGTy_GDDIeoKCq_QUTff8USInqLch-q3yjXJ5FIA6e-dFNHGez9nkVKZXcnPYWUuQJReHCkF4CyK79S62R5IiInrXrSJ1BAQGvzxdmB0S11MiNTwkgSgbu8HcAFNeSfXVRQvY5Jji2IaEXT366G6VobOfhXzybpkqIrrHRl3e8eJd9d8YtldNmF8-ZkRm09pQm-miT8Hrs4ohAWZ_uSlfvg5E",
+ "e": "AQAB"
+ }
+ ]
+ }
+ }
+}
diff --git a/test/core/tsi/test_creds/spiffe_end2end/spiffe-openssl.cnf b/test/core/tsi/test_creds/spiffe_end2end/spiffe-openssl.cnf
new file mode 100644
index 0000000..6bd2128
--- /dev/null
+++ b/test/core/tsi/test_creds/spiffe_end2end/spiffe-openssl.cnf
@@ -0,0 +1,34 @@
+[spiffe_client]
+subjectAltName = @alt_names
+
+[spiffe_client_multi]
+subjectAltName = @alt_names_multi
+
+[spiffe_client_non_utf8]
+subjectAltName = @alt_names_non_utf8
+
+[spiffe_server_e2e]
+subjectAltName = @alt_names_server_e2e
+
+[spiffe_client_e2e]
+subjectAltName = @alt_names_client_e2e
+
+[alt_names]
+URI = spiffe://foo.bar.com/client/workload/1
+
+[alt_names_multi]
+URI.1 = spiffe://foo.bar.com/client/workload/1
+URI.2 = spiffe://foo.bar.com/client/workload/2
+
+[alt_names_non_utf8]
+URI.1 = spiffe://foo.bar.com/client/workload/1\\xc0
+
+[alt_names_server_e2e]
+DNS.1 = *.test.google.fr
+DNS.2 = waterzooi.test.google.be
+DNS.3 = *.test.youtube.com
+IP.1 = "192.168.1.3"
+URI = spiffe://example.com/workload/9eebccd2
+
+[alt_names_client_e2e]
+URI = spiffe://foo.bar.com/9eebccd2-12bf-40a6-b262-65fe0487d453
diff --git a/test/cpp/end2end/BUILD b/test/cpp/end2end/BUILD
index a2e8f14..59e7398 100644
--- a/test/cpp/end2end/BUILD
+++ b/test/cpp/end2end/BUILD
@@ -1240,3 +1240,39 @@
"//test/cpp/util:test_util",
],
)
+
+grpc_cc_test(
+ name = "spiffe_bundle_map_end2end_test",
+ srcs = ["spiffe_bundle_map_end2end_test.cc"],
+ data = [
+ "//test/core/tsi/test_creds/spiffe_end2end:ca.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:client.key",
+ "//test/core/tsi/test_creds/spiffe_end2end:client_spiffe.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:client_spiffebundle.json",
+ "//test/core/tsi/test_creds/spiffe_end2end:intermediate.cnf",
+ "//test/core/tsi/test_creds/spiffe_end2end:intermediate_ca.key",
+ "//test/core/tsi/test_creds/spiffe_end2end:intermediate_ca.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:intermediate_gen.sh",
+ "//test/core/tsi/test_creds/spiffe_end2end:leaf_and_intermediate_chain.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:leaf_signed_by_intermediate.key",
+ "//test/core/tsi/test_creds/spiffe_end2end:leaf_signed_by_intermediate.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:server.key",
+ "//test/core/tsi/test_creds/spiffe_end2end:server_spiffe.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:server_spiffebundle.json",
+ ],
+ external_deps = [
+ "absl/log:check",
+ "gtest",
+ ],
+ tags = ["crl_provider_test"],
+ deps = [
+ ":test_service_impl",
+ "//:gpr",
+ "//:grpc",
+ "//:grpc++",
+ "//src/proto/grpc/testing:echo_cc_grpc",
+ "//src/proto/grpc/testing:echo_messages_cc_proto",
+ "//test/core/test_util:grpc_test_util",
+ "//test/cpp/util:test_util",
+ ],
+)
diff --git a/test/cpp/end2end/spiffe_bundle_map_end2end_test.cc b/test/cpp/end2end/spiffe_bundle_map_end2end_test.cc
new file mode 100644
index 0000000..30e87db
--- /dev/null
+++ b/test/cpp/end2end/spiffe_bundle_map_end2end_test.cc
@@ -0,0 +1,473 @@
+//
+//
+// Copyright 2025 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 <grpc/grpc_security.h>
+#include <grpcpp/channel.h>
+#include <grpcpp/client_context.h>
+#include <grpcpp/create_channel.h>
+#include <grpcpp/security/credentials.h>
+#include <grpcpp/security/server_credentials.h>
+#include <grpcpp/security/tls_certificate_provider.h>
+#include <grpcpp/security/tls_certificate_verifier.h>
+#include <grpcpp/security/tls_credentials_options.h>
+#include <grpcpp/server.h>
+#include <grpcpp/server_builder.h>
+#include <grpcpp/support/channel_arguments.h>
+#include <grpcpp/support/status.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "absl/log/check.h"
+#include "absl/log/log.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "absl/synchronization/notification.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "src/cpp/client/secure_credentials.h"
+#include "src/proto/grpc/testing/echo_messages.pb.h"
+#include "test/core/test_util/port.h"
+#include "test/core/test_util/test_config.h"
+#include "test/core/test_util/tls_utils.h"
+#include "test/cpp/end2end/test_service_impl.h"
+
+namespace grpc {
+namespace testing {
+namespace {
+
+constexpr absl::string_view kMessage = "Hello";
+constexpr absl::string_view kCaPemPath =
+ "test/core/tsi/test_creds/spiffe_end2end/ca.pem";
+constexpr absl::string_view kClientKeyPath =
+ "test/core/tsi/test_creds/spiffe_end2end/client.key";
+constexpr absl::string_view kClientCertPath =
+ "test/core/tsi/test_creds/spiffe_end2end/client_spiffe.pem";
+constexpr absl::string_view kServerKeyPath =
+ "test/core/tsi/test_creds/spiffe_end2end/server.key";
+constexpr absl::string_view kServerCertPath =
+ "test/core/tsi/test_creds/spiffe_end2end/server_spiffe.pem";
+constexpr absl::string_view kServerChainKeyPath =
+ "test/core/tsi/test_creds/spiffe_end2end/leaf_signed_by_intermediate.key";
+constexpr absl::string_view kServerChainCertPath =
+ "test/core/tsi/test_creds/spiffe_end2end/leaf_and_intermediate_chain.pem";
+constexpr absl::string_view kClientSpiffeBundleMapPath =
+ "test/core/tsi/test_creds/spiffe_end2end/client_spiffebundle.json";
+constexpr absl::string_view kServerSpiffeBundleMapPath =
+ "test/core/tsi/test_creds/spiffe_end2end/server_spiffebundle.json";
+
+std::string MakeConnectionFailureRegex(absl::string_view prefix) {
+ return absl::StrCat(
+ prefix,
+ "(UNKNOWN|UNAVAILABLE): "
+ // IP address
+ "(ipv6:%5B::1%5D|ipv4:127.0.0.1):[0-9]+: "
+ // Prefixes added for context
+ "(Failed to connect to remote host: )?"
+ "(Timeout occurred: )?"
+ // Parenthetical wrappers
+ "( ?\\(*("
+ "Secure read failed|"
+ "Handshake (read|write) failed|"
+ "Delayed close due to in-progress write|"
+ // Syscall
+ "((connect|sendmsg|recvmsg|getsockopt\\(SO\\_ERROR\\)): ?)?"
+ // strerror() output or other message
+ "(Connection refused"
+ "|Connection reset by peer"
+ "|Socket closed"
+ "|Broken pipe"
+ "|FD shutdown"
+ "|Endpoint closing)"
+ // errno value
+ "( \\([0-9]+\\))?"
+ // close paren from wrappers above
+ ")\\)*)+");
+}
+
+std::string MakeTlsHandshakeFailureRegex(absl::string_view prefix) {
+ return absl::StrCat(
+ prefix,
+ "(UNKNOWN|UNAVAILABLE): "
+ // IP address
+ "(ipv6:%5B::1%5D|ipv4:127.0.0.1):[0-9]+: "
+ // Prefixes added for context
+ "(Failed to connect to remote host: )?"
+ // Tls handshake failure
+ "Tls handshake failed \\(TSI_PROTOCOL_FAILURE\\): SSL_ERROR_SSL: "
+ "error:1000007d:SSL routines:OPENSSL_internal:CERTIFICATE_VERIFY_FAILED"
+ // Detailed reason for certificate verify failure
+ "(: .*)?");
+}
+
+class SpiffeBundleMapTest : public ::testing::Test {
+ protected:
+ void RunServer(absl::Notification* notification, absl::string_view key_path,
+ absl::string_view cert_path, absl::string_view root_path,
+ absl::string_view spiffe_bundle_map_path) {
+ auto certificate_provider =
+ std::make_shared<experimental::FileWatcherCertificateProvider>(
+ std::string(key_path), std::string(cert_path),
+ std::string(root_path), std::string(spiffe_bundle_map_path), 1);
+ grpc::experimental::TlsServerCredentialsOptions options(
+ certificate_provider);
+ options.watch_root_certs();
+ options.set_root_cert_name("root");
+ options.watch_identity_key_cert_pairs();
+ options.set_identity_cert_name("identity");
+ options.set_cert_request_type(
+ GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY);
+ auto server_credentials = grpc::experimental::TlsServerCredentials(options);
+ CHECK_NE(server_credentials.get(), nullptr);
+
+ grpc::ServerBuilder builder;
+
+ builder.AddListeningPort(server_addr_, server_credentials);
+ builder.RegisterService("foo.test.google.fr", &service_);
+ server_ = builder.BuildAndStart();
+ notification->Notify();
+ server_->Wait();
+ }
+
+ void TearDown() override {
+ if (server_ != nullptr) {
+ server_->Shutdown();
+ server_thread_->join();
+ delete server_thread_;
+ }
+ }
+
+ TestServiceImpl service_;
+ std::unique_ptr<Server> server_ = nullptr;
+ std::thread* server_thread_ = nullptr;
+ std::string server_addr_;
+};
+
+void DoRpc(const std::string& server_addr,
+ const experimental::TlsChannelCredentialsOptions& tls_options,
+ bool expect_success, absl::string_view failure_message_regex = "",
+ StatusCode failure_code = StatusCode::OK) {
+ ChannelArguments channel_args;
+ channel_args.SetSslTargetNameOverride("foo.test.google.fr");
+ std::shared_ptr<Channel> channel = grpc::CreateCustomChannel(
+ server_addr, grpc::experimental::TlsCredentials(tls_options),
+ channel_args);
+
+ auto stub = grpc::testing::EchoTestService::NewStub(channel);
+ grpc::testing::EchoRequest request;
+ grpc::testing::EchoResponse response;
+ request.set_message(kMessage);
+ ClientContext context;
+ context.set_deadline(grpc_timeout_seconds_to_deadline(/*time_s=*/15));
+ grpc::Status result = stub->Echo(&context, request, &response);
+ if (expect_success) {
+ EXPECT_TRUE(result.ok()) << result.error_message().c_str() << ", "
+ << result.error_details().c_str();
+ EXPECT_EQ(response.message(), kMessage);
+ } else {
+ EXPECT_FALSE(result.ok());
+ EXPECT_EQ(result.error_code(), failure_code);
+#if GTEST_USES_POSIX_RE
+ EXPECT_THAT(result.error_message(),
+ ::testing::MatchesRegex(failure_message_regex));
+#endif
+ }
+}
+
+TEST_F(SpiffeBundleMapTest, ServerSideSpiffeTLS) {
+ server_addr_ = absl::StrCat("localhost:",
+ std::to_string(grpc_pick_unused_port_or_die()));
+ absl::Notification notification;
+ server_thread_ = new std::thread([&]() {
+ RunServer(¬ification, kServerKeyPath, kServerCertPath, "",
+ kServerSpiffeBundleMapPath);
+ });
+ notification.WaitForNotification();
+
+ std::string root_cert =
+ grpc_core::testing::GetFileContents(std::string(kCaPemPath));
+ std::string client_key =
+ grpc_core::testing::GetFileContents(std::string(kClientKeyPath));
+ std::string client_cert =
+ grpc_core::testing::GetFileContents(std::string(kClientCertPath));
+ experimental::IdentityKeyCertPair key_cert_pair;
+ key_cert_pair.private_key = client_key;
+ key_cert_pair.certificate_chain = client_cert;
+ std::vector<experimental::IdentityKeyCertPair> identity_key_cert_pairs;
+ identity_key_cert_pairs.emplace_back(key_cert_pair);
+ auto certificate_provider =
+ std::make_shared<experimental::StaticDataCertificateProvider>(
+ root_cert, identity_key_cert_pairs);
+ grpc::experimental::TlsChannelCredentialsOptions options;
+ options.set_certificate_provider(certificate_provider);
+ options.watch_root_certs();
+ options.set_root_cert_name("root");
+ options.watch_identity_key_cert_pairs();
+ options.set_identity_cert_name("identity");
+
+ options.set_check_call_host(false);
+
+ DoRpc(server_addr_, options, true);
+}
+
+TEST_F(SpiffeBundleMapTest, ClientSideSpiffeTLS) {
+ server_addr_ = absl::StrCat("localhost:",
+ std::to_string(grpc_pick_unused_port_or_die()));
+ absl::Notification notification;
+ server_thread_ = new std::thread([&]() {
+ RunServer(¬ification, kServerKeyPath, kServerCertPath, kCaPemPath, "");
+ });
+ notification.WaitForNotification();
+
+ auto certificate_provider =
+ std::make_shared<experimental::FileWatcherCertificateProvider>(
+ std::string(kClientKeyPath), std::string(kClientCertPath),
+ /*root_cert_path=*/"", std::string(kClientSpiffeBundleMapPath), 1);
+ grpc::experimental::TlsChannelCredentialsOptions options;
+ options.set_certificate_provider(certificate_provider);
+ options.watch_root_certs();
+ options.set_root_cert_name("root");
+ options.watch_identity_key_cert_pairs();
+ options.set_identity_cert_name("identity");
+
+ options.set_check_call_host(false);
+ auto verifier = std::make_shared<experimental::NoOpCertificateVerifier>();
+ options.set_certificate_verifier(verifier);
+
+ DoRpc(server_addr_, options, true);
+}
+
+TEST_F(SpiffeBundleMapTest, SpiffeMTLS) {
+ server_addr_ = absl::StrCat("localhost:",
+ std::to_string(grpc_pick_unused_port_or_die()));
+ absl::Notification notification;
+ server_thread_ = new std::thread([&]() {
+ RunServer(¬ification, kServerKeyPath, kServerCertPath, "",
+ kServerSpiffeBundleMapPath);
+ });
+ notification.WaitForNotification();
+
+ auto certificate_provider =
+ std::make_shared<experimental::FileWatcherCertificateProvider>(
+ std::string(kClientKeyPath), std::string(kClientCertPath),
+ /*root_cert_path=*/"", std::string(kClientSpiffeBundleMapPath), 1);
+ grpc::experimental::TlsChannelCredentialsOptions options;
+ options.set_certificate_provider(certificate_provider);
+ options.watch_root_certs();
+ options.set_root_cert_name("root");
+ options.watch_identity_key_cert_pairs();
+ options.set_identity_cert_name("identity");
+
+ options.set_check_call_host(false);
+ auto verifier = std::make_shared<experimental::NoOpCertificateVerifier>();
+ options.set_certificate_verifier(verifier);
+
+ DoRpc(server_addr_, options, true);
+}
+
+TEST_F(SpiffeBundleMapTest, SpiffeWithCertChain) {
+ server_addr_ = absl::StrCat("localhost:",
+ std::to_string(grpc_pick_unused_port_or_die()));
+ absl::Notification notification;
+ server_thread_ = new std::thread([&]() {
+ RunServer(¬ification, kServerChainKeyPath, kServerChainCertPath, "",
+ kServerSpiffeBundleMapPath);
+ });
+ notification.WaitForNotification();
+
+ auto certificate_provider =
+ std::make_shared<experimental::FileWatcherCertificateProvider>(
+ std::string(kClientKeyPath), std::string(kClientCertPath),
+ /*root_cert_path=*/"", std::string(kClientSpiffeBundleMapPath), 1);
+ grpc::experimental::TlsChannelCredentialsOptions options;
+ options.set_certificate_provider(certificate_provider);
+ options.watch_root_certs();
+ options.set_root_cert_name("root");
+ options.watch_identity_key_cert_pairs();
+ options.set_identity_cert_name("identity");
+
+ options.set_check_call_host(false);
+ auto verifier = std::make_shared<experimental::NoOpCertificateVerifier>();
+ options.set_certificate_verifier(verifier);
+
+ DoRpc(server_addr_, options, true);
+}
+
+TEST_F(SpiffeBundleMapTest, ServerSpiffeReload) {
+ auto server_bundle_map = grpc_core::testing::GetFileContents(
+ std::string(kServerSpiffeBundleMapPath));
+ auto client_bundle_map = grpc_core::testing::GetFileContents(
+ std::string(kClientSpiffeBundleMapPath));
+ grpc_core::testing::TmpFile tmp_bundle_map(server_bundle_map);
+ server_addr_ = absl::StrCat("localhost:",
+ std::to_string(grpc_pick_unused_port_or_die()));
+ absl::Notification notification;
+ server_thread_ = new std::thread([&]() {
+ RunServer(¬ification, kServerChainKeyPath, kServerChainCertPath, "",
+ tmp_bundle_map.name());
+ });
+ notification.WaitForNotification();
+
+ auto certificate_provider =
+ std::make_shared<experimental::FileWatcherCertificateProvider>(
+ std::string(kClientKeyPath), std::string(kClientCertPath),
+ /*root_cert_path=*/"", std::string(kClientSpiffeBundleMapPath), 1);
+ grpc::experimental::TlsChannelCredentialsOptions options;
+ options.set_certificate_provider(certificate_provider);
+ options.watch_root_certs();
+ options.set_root_cert_name("root");
+ options.watch_identity_key_cert_pairs();
+ options.set_identity_cert_name("identity");
+
+ options.set_check_call_host(false);
+ auto verifier = std::make_shared<experimental::NoOpCertificateVerifier>();
+ options.set_certificate_verifier(verifier);
+ DoRpc(server_addr_, options, true);
+
+ // Update the spiffe bundle map to something that will fail
+ tmp_bundle_map.RewriteFile(client_bundle_map);
+ // Wait 2 seconds to ensure a refresh happens
+ gpr_sleep_until(gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
+ gpr_time_from_seconds(2, GPR_TIMESPAN)));
+ constexpr absl::string_view expected_message_start =
+ "failed to connect to all addresses; last error: ";
+ const StatusCode expected_status = StatusCode::UNAVAILABLE;
+ DoRpc(server_addr_, options, false,
+ MakeConnectionFailureRegex(expected_message_start), expected_status);
+}
+
+TEST_F(SpiffeBundleMapTest, ClientSpiffeReload) {
+ auto server_bundle_map = grpc_core::testing::GetFileContents(
+ std::string(kServerSpiffeBundleMapPath));
+ auto client_bundle_map = grpc_core::testing::GetFileContents(
+ std::string(kClientSpiffeBundleMapPath));
+ grpc_core::testing::TmpFile tmp_bundle_map(client_bundle_map);
+ server_addr_ = absl::StrCat("localhost:",
+ std::to_string(grpc_pick_unused_port_or_die()));
+ absl::Notification notification;
+ server_thread_ = new std::thread([&]() {
+ RunServer(¬ification, kServerChainKeyPath, kServerChainCertPath, "",
+ kServerSpiffeBundleMapPath);
+ });
+ notification.WaitForNotification();
+
+ auto certificate_provider =
+ std::make_shared<experimental::FileWatcherCertificateProvider>(
+ std::string(kClientKeyPath), std::string(kClientCertPath),
+ /*root_cert_path=*/"", tmp_bundle_map.name(), 1);
+ grpc::experimental::TlsChannelCredentialsOptions options;
+ options.set_certificate_provider(certificate_provider);
+ options.watch_root_certs();
+ options.set_root_cert_name("root");
+ options.watch_identity_key_cert_pairs();
+ options.set_identity_cert_name("identity");
+
+ options.set_check_call_host(false);
+ auto verifier = std::make_shared<experimental::NoOpCertificateVerifier>();
+ options.set_certificate_verifier(verifier);
+ DoRpc(server_addr_, options, true);
+
+ // Update the spiffe bundle map to something that will fail
+ tmp_bundle_map.RewriteFile(server_bundle_map);
+ // Wait 2 seconds to ensure a refresh happens
+ gpr_sleep_until(gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
+ gpr_time_from_seconds(2, GPR_TIMESPAN)));
+ constexpr absl::string_view expected_message_start =
+ "failed to connect to all addresses; last error: ";
+ const StatusCode expected_status = StatusCode::UNAVAILABLE;
+ DoRpc(server_addr_, options, false,
+ MakeTlsHandshakeFailureRegex(expected_message_start), expected_status);
+}
+
+TEST_F(SpiffeBundleMapTest, ServerSideSpiffeVerificationFailure) {
+ server_addr_ = absl::StrCat("localhost:",
+ std::to_string(grpc_pick_unused_port_or_die()));
+ absl::Notification notification;
+ // Use the client-side spiffe bundle map on the server side to force a failure
+ server_thread_ = new std::thread([&]() {
+ RunServer(¬ification, kServerKeyPath, kServerCertPath, "",
+ kClientSpiffeBundleMapPath);
+ });
+ notification.WaitForNotification();
+
+ auto certificate_provider =
+ std::make_shared<experimental::FileWatcherCertificateProvider>(
+ std::string(kClientKeyPath), std::string(kClientCertPath),
+ /*root_cert_path=*/"", std::string(kClientSpiffeBundleMapPath), 1);
+ grpc::experimental::TlsChannelCredentialsOptions options;
+ options.set_certificate_provider(certificate_provider);
+ options.watch_root_certs();
+ options.set_root_cert_name("root");
+ options.watch_identity_key_cert_pairs();
+ options.set_identity_cert_name("identity");
+
+ options.set_check_call_host(false);
+ auto verifier = std::make_shared<experimental::NoOpCertificateVerifier>();
+ options.set_certificate_verifier(verifier);
+
+ constexpr absl::string_view expected_message_start =
+ "failed to connect to all addresses; last error: ";
+ const StatusCode expected_status = StatusCode::UNAVAILABLE;
+ DoRpc(server_addr_, options, false,
+ MakeConnectionFailureRegex(expected_message_start), expected_status);
+}
+
+TEST_F(SpiffeBundleMapTest, ClientSideSpiffeVerificationFailure) {
+ server_addr_ = absl::StrCat("localhost:",
+ std::to_string(grpc_pick_unused_port_or_die()));
+ absl::Notification notification;
+ server_thread_ = new std::thread([&]() {
+ RunServer(¬ification, kServerKeyPath, kServerCertPath, "",
+ kServerSpiffeBundleMapPath);
+ });
+ notification.WaitForNotification();
+
+ // Use the server-side spiffe bundle map on the client side to force a failure
+ auto certificate_provider =
+ std::make_shared<experimental::FileWatcherCertificateProvider>(
+ std::string(kClientKeyPath), std::string(kClientCertPath),
+ /*root_cert_path=*/"", std::string(kServerSpiffeBundleMapPath), 1);
+ grpc::experimental::TlsChannelCredentialsOptions options;
+ options.set_certificate_provider(certificate_provider);
+ options.watch_root_certs();
+ options.set_root_cert_name("root");
+ options.watch_identity_key_cert_pairs();
+ options.set_identity_cert_name("identity");
+
+ options.set_check_call_host(false);
+ auto verifier = std::make_shared<experimental::NoOpCertificateVerifier>();
+ options.set_certificate_verifier(verifier);
+
+ constexpr absl::string_view expected_message_start =
+ "failed to connect to all addresses; last error: ";
+ const StatusCode expected_status = StatusCode::UNAVAILABLE;
+ DoRpc(server_addr_, options, false,
+ MakeTlsHandshakeFailureRegex(expected_message_start), expected_status);
+}
+
+} // namespace
+} // namespace testing
+} // namespace grpc
+
+int main(int argc, char** argv) {
+ grpc::testing::TestEnvironment env(&argc, argv);
+ ::testing::InitGoogleTest(&argc, argv);
+ int ret = RUN_ALL_TESTS();
+ return ret;
+}
diff --git a/test/cpp/end2end/xds/BUILD b/test/cpp/end2end/xds/BUILD
index b09ebe7..2f0ff8b 100644
--- a/test/cpp/end2end/xds/BUILD
+++ b/test/cpp/end2end/xds/BUILD
@@ -154,6 +154,72 @@
)
grpc_cc_test(
+ name = "xds_security_spiffe_end2end_test",
+ size = "large",
+ srcs = ["xds_security_spiffe_end2end_test.cc"],
+ data = [
+ "//src/core/tsi/test_creds:badclient.key",
+ "//src/core/tsi/test_creds:badclient.pem",
+ "//src/core/tsi/test_creds:ca.pem",
+ "//src/core/tsi/test_creds:client.key",
+ "//src/core/tsi/test_creds:client.pem",
+ "//src/core/tsi/test_creds:server1.key",
+ "//src/core/tsi/test_creds:server1.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:ca.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:client.key",
+ "//test/core/tsi/test_creds/spiffe_end2end:client_spiffe.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:client_spiffebundle.json",
+ "//test/core/tsi/test_creds/spiffe_end2end:intermediate.cnf",
+ "//test/core/tsi/test_creds/spiffe_end2end:intermediate_ca.key",
+ "//test/core/tsi/test_creds/spiffe_end2end:intermediate_ca.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:intermediate_gen.sh",
+ "//test/core/tsi/test_creds/spiffe_end2end:leaf_and_intermediate_chain.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:leaf_signed_by_intermediate.key",
+ "//test/core/tsi/test_creds/spiffe_end2end:leaf_signed_by_intermediate.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:server.key",
+ "//test/core/tsi/test_creds/spiffe_end2end:server_spiffe.pem",
+ "//test/core/tsi/test_creds/spiffe_end2end:server_spiffebundle.json",
+ ],
+ external_deps = [
+ "absl/log:check",
+ "gtest",
+ ],
+ # flaky = True, # TODO(b/144705388)
+ linkstatic = True, # Fixes dyld error on MacOS
+ # shard_count = 25,
+ tags = [
+ "no_test_ios",
+ "no_windows",
+ "xds_end2end_test",
+ ], # TODO(jtattermusch): fix test on windows
+ deps = [
+ ":xds_end2end_test_lib",
+ "//:gpr",
+ "//:grpc",
+ "//:grpc++",
+ "//src/core:grpc_audit_logging",
+ "//src/proto/grpc/testing:echo_cc_grpc",
+ "//src/proto/grpc/testing:echo_messages_cc_proto",
+ "//test/core/test_util:audit_logging_utils",
+ "//test/core/test_util:grpc_test_util",
+ "//test/core/test_util:scoped_env_var",
+ "//test/cpp/util:test_config",
+ "//test/cpp/util:test_util",
+ "//test/cpp/util:tls_test_utils",
+ "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto",
+ "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto",
+ "@envoy_api//envoy/config/listener/v3:pkg_cc_proto",
+ "@envoy_api//envoy/config/route/v3:pkg_cc_proto",
+ "@envoy_api//envoy/extensions/clusters/aggregate/v3:pkg_cc_proto",
+ "@envoy_api//envoy/extensions/filters/http/rbac/v3:pkg_cc_proto",
+ "@envoy_api//envoy/extensions/filters/http/router/v3:pkg_cc_proto",
+ "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto",
+ "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto",
+ "@envoy_api//envoy/type/v3:pkg_cc_proto",
+ ],
+)
+
+grpc_cc_test(
name = "xds_cluster_end2end_test",
size = "large",
srcs = ["xds_cluster_end2end_test.cc"],
diff --git a/test/cpp/end2end/xds/xds_end2end_test_lib.cc b/test/cpp/end2end/xds/xds_end2end_test_lib.cc
index dcade49..8a01681 100644
--- a/test/cpp/end2end/xds/xds_end2end_test_lib.cc
+++ b/test/cpp/end2end/xds/xds_end2end_test_lib.cc
@@ -377,6 +377,13 @@
const char XdsEnd2endTest::kServerKeyPath[] =
"src/core/tsi/test_creds/server1.key";
+const char XdsEnd2endTest::kSpiffeCaCertPath[] =
+ "test/core/tsi/test_creds/spiffe_end2end/ca.pem";
+const char XdsEnd2endTest::kSpiffeServerCertPath[] =
+ "test/core/tsi/test_creds/spiffe_end2end/server_spiffe.pem";
+const char XdsEnd2endTest::kSpiffeServerKeyPath[] =
+ "test/core/tsi/test_creds/spiffe_end2end/server.key";
+
const char XdsEnd2endTest::kRequestMessage[] = "Live long and prosper.";
XdsEnd2endTest::XdsEnd2endTest(
@@ -918,12 +925,26 @@
return {{std::move(private_key), std::move(identity_cert)}};
}
+std::vector<experimental::IdentityKeyCertPair>
+XdsEnd2endTest::MakeIdentityKeyCertPairForSpiffeMtlsCreds() {
+ std::string identity_cert =
+ grpc_core::testing::GetFileContents(kSpiffeServerCertPath);
+ std::string private_key =
+ grpc_core::testing::GetFileContents(kSpiffeServerKeyPath);
+ return {{std::move(private_key), std::move(identity_cert)}};
+}
+
std::shared_ptr<ChannelCredentials>
XdsEnd2endTest::CreateXdsChannelCredentials() {
return XdsCredentials(CreateTlsChannelCredentials());
}
std::shared_ptr<ChannelCredentials>
+XdsEnd2endTest::CreateSpiffeXdsChannelCredentials() {
+ return XdsCredentials(CreateSpiffeTlsChannelCredentials());
+}
+
+std::shared_ptr<ChannelCredentials>
XdsEnd2endTest::CreateTlsChannelCredentials() {
auto certificate_provider = std::make_shared<StaticDataCertificateProvider>(
grpc_core::testing::GetFileContents(kCaCertPath),
@@ -940,6 +961,23 @@
return grpc::experimental::TlsCredentials(options);
}
+std::shared_ptr<ChannelCredentials>
+XdsEnd2endTest::CreateSpiffeTlsChannelCredentials() {
+ auto certificate_provider = std::make_shared<StaticDataCertificateProvider>(
+ grpc_core::testing::GetFileContents(kSpiffeCaCertPath),
+ MakeIdentityKeyCertPairForSpiffeMtlsCreds());
+ grpc::experimental::TlsChannelCredentialsOptions options;
+ options.set_certificate_provider(std::move(certificate_provider));
+ options.watch_root_certs();
+ options.watch_identity_key_cert_pairs();
+ auto verifier =
+ ExternalCertificateVerifier::Create<SyncCertificateVerifier>(true);
+ options.set_certificate_verifier(std::move(verifier));
+ options.set_verify_server_certs(true);
+ options.set_check_call_host(false);
+ return grpc::experimental::TlsCredentials(options);
+}
+
std::shared_ptr<ServerCredentials>
XdsEnd2endTest::CreateFakeServerCredentials() {
return std::make_shared<SecureServerCredentials>(
@@ -971,5 +1009,21 @@
return grpc::experimental::TlsServerCredentials(options);
}
+std::shared_ptr<ServerCredentials>
+XdsEnd2endTest::CreateMtlsSpiffeServerCredentials() {
+ std::string root_cert =
+ grpc_core::testing::GetFileContents(kSpiffeCaCertPath);
+ auto certificate_provider =
+ std::make_shared<grpc::experimental::StaticDataCertificateProvider>(
+ std::move(root_cert), MakeIdentityKeyCertPairForSpiffeMtlsCreds());
+ grpc::experimental::TlsServerCredentialsOptions options(
+ std::move(certificate_provider));
+ options.watch_root_certs();
+ options.watch_identity_key_cert_pairs();
+ options.set_cert_request_type(
+ GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY);
+ return grpc::experimental::TlsServerCredentials(options);
+}
+
} // namespace testing
} // namespace grpc
diff --git a/test/cpp/end2end/xds/xds_end2end_test_lib.h b/test/cpp/end2end/xds/xds_end2end_test_lib.h
index b345178..7763a33 100644
--- a/test/cpp/end2end/xds/xds_end2end_test_lib.h
+++ b/test/cpp/end2end/xds/xds_end2end_test_lib.h
@@ -203,6 +203,9 @@
static const char kCaCertPath[];
static const char kServerCertPath[];
static const char kServerKeyPath[];
+ static const char kSpiffeCaCertPath[];
+ static const char kSpiffeServerCertPath[];
+ static const char kSpiffeServerKeyPath[];
// Message used in EchoRequest to the backend.
static const char kRequestMessage[];
@@ -1029,14 +1032,24 @@
static std::vector<experimental::IdentityKeyCertPair>
MakeIdentityKeyCertPairForTlsCreds();
+ // Internal helper function for creating TLS and mTLS creds.
+ // Not intended to be used by tests.
+ static std::vector<experimental::IdentityKeyCertPair>
+ MakeIdentityKeyCertPairForSpiffeMtlsCreds();
+
// Returns XdsCredentials with mTLS fallback creds.
static std::shared_ptr<ChannelCredentials> CreateXdsChannelCredentials();
static std::shared_ptr<ChannelCredentials> CreateTlsChannelCredentials();
+ static std::shared_ptr<ChannelCredentials>
+ CreateSpiffeXdsChannelCredentials();
+ static std::shared_ptr<ChannelCredentials>
+ CreateSpiffeTlsChannelCredentials();
// Creates various types of server credentials.
static std::shared_ptr<ServerCredentials> CreateFakeServerCredentials();
static std::shared_ptr<ServerCredentials> CreateMtlsServerCredentials();
static std::shared_ptr<ServerCredentials> CreateTlsServerCredentials();
+ static std::shared_ptr<ServerCredentials> CreateMtlsSpiffeServerCredentials();
// event_engine_scope_ always has to be at the top of the list to make sure
// that all other objects are destroyed before this and other event engine
diff --git a/test/cpp/end2end/xds/xds_security_spiffe_end2end_test.cc b/test/cpp/end2end/xds/xds_security_spiffe_end2end_test.cc
new file mode 100644
index 0000000..039ccf1
--- /dev/null
+++ b/test/cpp/end2end/xds/xds_security_spiffe_end2end_test.cc
@@ -0,0 +1,477 @@
+//
+// Copyright 2025 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 <grpc/grpc.h>
+#include <grpc/grpc_security.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/time.h>
+#include <grpcpp/channel.h>
+#include <grpcpp/client_context.h>
+#include <grpcpp/create_channel.h>
+#include <grpcpp/security/audit_logging.h>
+#include <grpcpp/security/tls_certificate_provider.h>
+#include <grpcpp/server.h>
+#include <grpcpp/server_builder.h>
+#include <grpcpp/xds_server_builder.h>
+
+#include <map>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/functional/function_ref.h"
+#include "absl/log/check.h"
+#include "absl/log/log.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/str_join.h"
+#include "absl/time/time.h"
+#include "envoy/config/cluster/v3/cluster.pb.h"
+#include "envoy/config/endpoint/v3/endpoint.pb.h"
+#include "envoy/config/listener/v3/listener.pb.h"
+#include "envoy/config/route/v3/route.pb.h"
+#include "envoy/extensions/clusters/aggregate/v3/cluster.pb.h"
+#include "envoy/extensions/filters/http/rbac/v3/rbac.pb.h"
+#include "envoy/extensions/filters/http/router/v3/router.pb.h"
+#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h"
+#include "envoy/extensions/transport_sockets/tls/v3/tls.pb.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "src/core/config/config_vars.h"
+#include "src/core/config/core_configuration.h"
+#include "src/core/credentials/transport/fake/fake_credentials.h"
+#include "src/core/credentials/transport/tls/certificate_provider_registry.h"
+#include "src/core/credentials/transport/tls/grpc_tls_certificate_provider.h"
+#include "src/core/ext/filters/http/client/http_client_filter.h"
+#include "src/core/lib/security/authorization/audit_logging.h"
+#include "src/core/util/env.h"
+#include "src/core/util/ref_counted_ptr.h"
+#include "src/core/util/string.h"
+#include "src/core/util/sync.h"
+#include "src/cpp/client/secure_credentials.h"
+#include "src/cpp/server/secure_server_credentials.h"
+#include "src/proto/grpc/testing/echo.grpc.pb.h"
+#include "src/proto/grpc/testing/echo_messages.pb.h"
+#include "test/core/test_util/audit_logging_utils.h"
+#include "test/core/test_util/port.h"
+#include "test/core/test_util/resolve_localhost_ip46.h"
+#include "test/core/test_util/scoped_env_var.h"
+#include "test/core/test_util/test_config.h"
+#include "test/core/test_util/tls_utils.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"
+#include "xds/type/v3/typed_struct.pb.h"
+
+namespace grpc {
+namespace testing {
+namespace {
+
+using ::envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext;
+using ::envoy::type::matcher::v3::StringMatcher;
+
+constexpr char kClientKeyPath[] =
+ "test/core/tsi/test_creds/spiffe_end2end/client.key";
+constexpr char kClientCertPath[] =
+ "test/core/tsi/test_creds/spiffe_end2end/client_spiffe.pem";
+constexpr char kClientSpiffeBundleMapPath[] =
+ "test/core/tsi/test_creds/spiffe_end2end/client_spiffebundle.json";
+constexpr char kServerSpiffeBundleMapPath[] =
+ "test/core/tsi/test_creds/spiffe_end2end/server_spiffebundle.json";
+constexpr char kBadClientCertPath[] = "src/core/tsi/test_creds/badclient.pem";
+constexpr char kBadClientKeyPath[] = "src/core/tsi/test_creds/badclient.key";
+
+// Based on StaticDataCertificateProvider, but provides alternate certificates
+// if the certificate name is not empty.
+class FakeCertificateProvider final : public grpc_tls_certificate_provider {
+ public:
+ struct CertData {
+ std::string root_certificate;
+ grpc_core::PemKeyCertPairList identity_key_cert_pairs;
+ grpc_core::SpiffeBundleMap spiffe_bundle_map;
+ };
+
+ using CertDataMap = std::map<std::string /*cert_name */, CertData>;
+ class CertDataMapWrapper {
+ public:
+ CertDataMap Get() {
+ grpc_core::MutexLock lock(&mu_);
+ return cert_data_map_;
+ }
+
+ void Set(CertDataMap data) {
+ grpc_core::MutexLock lock(&mu_);
+ cert_data_map_ = std::move(data);
+ }
+
+ private:
+ grpc_core::Mutex mu_;
+ CertDataMap cert_data_map_ ABSL_GUARDED_BY(mu_);
+ };
+
+ explicit FakeCertificateProvider(CertDataMap cert_data_map)
+ : distributor_(
+ grpc_core::MakeRefCounted<grpc_tls_certificate_distributor>()),
+ cert_data_map_(std::move(cert_data_map)) {
+ distributor_->SetWatchStatusCallback([this](std::string cert_name,
+ bool root_being_watched,
+ bool identity_being_watched) {
+ if (!root_being_watched && !identity_being_watched) return;
+ auto it = cert_data_map_.find(cert_name);
+ if (it == cert_data_map_.end()) {
+ grpc_error_handle error = GRPC_ERROR_CREATE(absl::StrCat(
+ "No certificates available for cert_name \"", cert_name, "\""));
+ distributor_->SetErrorForCert(cert_name, error, error);
+ } else {
+ std::shared_ptr<RootCertInfo> root_cert_info;
+ std::optional<grpc_core::PemKeyCertPairList> pem_key_cert_pairs;
+ if (root_being_watched) {
+ if (it->second.spiffe_bundle_map.size() != 0) {
+ root_cert_info =
+ std::make_shared<RootCertInfo>(it->second.spiffe_bundle_map);
+ } else {
+ root_cert_info =
+ std::make_shared<RootCertInfo>(it->second.root_certificate);
+ }
+ }
+ if (identity_being_watched) {
+ pem_key_cert_pairs = it->second.identity_key_cert_pairs;
+ }
+ distributor_->SetKeyMaterials(cert_name, std::move(root_cert_info),
+ std::move(pem_key_cert_pairs));
+ }
+ });
+ }
+
+ ~FakeCertificateProvider() override {
+ distributor_->SetWatchStatusCallback(nullptr);
+ }
+
+ grpc_core::RefCountedPtr<grpc_tls_certificate_distributor> distributor()
+ const override {
+ return distributor_;
+ }
+
+ grpc_core::UniqueTypeName type() const override {
+ static grpc_core::UniqueTypeName::Factory kFactory("fake");
+ return kFactory.Create();
+ }
+
+ private:
+ int CompareImpl(const grpc_tls_certificate_provider* other) const override {
+ // TODO(yashykt): Maybe do something better here.
+ return grpc_core::QsortCompare(
+ static_cast<const grpc_tls_certificate_provider*>(this), other);
+ }
+
+ grpc_core::RefCountedPtr<grpc_tls_certificate_distributor> distributor_;
+ CertDataMap cert_data_map_;
+};
+
+class FakeCertificateProviderFactory
+ : public grpc_core::CertificateProviderFactory {
+ public:
+ class Config : public grpc_core::CertificateProviderFactory::Config {
+ public:
+ explicit Config(absl::string_view name) : name_(name) {}
+
+ absl::string_view name() const override { return name_; }
+
+ std::string ToString() const override { return "{}"; }
+
+ private:
+ absl::string_view name_;
+ };
+
+ FakeCertificateProviderFactory(
+ absl::string_view name,
+ FakeCertificateProvider::CertDataMapWrapper* cert_data_map)
+ : name_(name), cert_data_map_(cert_data_map) {
+ CHECK_NE(cert_data_map, nullptr);
+ }
+
+ absl::string_view name() const override { return name_; }
+
+ grpc_core::RefCountedPtr<grpc_core::CertificateProviderFactory::Config>
+ CreateCertificateProviderConfig(
+ const grpc_core::Json& /*config_json*/,
+ const grpc_core::JsonArgs& /*args*/,
+ grpc_core::ValidationErrors* /*errors*/) override {
+ return grpc_core::MakeRefCounted<Config>(name_);
+ }
+
+ grpc_core::RefCountedPtr<grpc_tls_certificate_provider>
+ CreateCertificateProvider(
+ grpc_core::RefCountedPtr<grpc_core::CertificateProviderFactory::Config>
+ /*config*/) override {
+ CHECK_NE(cert_data_map_, nullptr);
+ return grpc_core::MakeRefCounted<FakeCertificateProvider>(
+ cert_data_map_->Get());
+ }
+
+ private:
+ absl::string_view name_;
+ FakeCertificateProvider::CertDataMapWrapper* cert_data_map_;
+};
+
+// Global variables for each provider.
+FakeCertificateProvider::CertDataMapWrapper* g_fake1_cert_data_map = nullptr;
+FakeCertificateProvider::CertDataMapWrapper* g_fake2_cert_data_map = nullptr;
+
+//
+// Client-side mTLS tests
+//
+
+class XdsSecurityTest : public XdsEnd2endTest {
+ protected:
+ void SetUp() override {
+ XdsBootstrapBuilder builder = MakeBootstrapBuilder();
+ builder.AddCertificateProviderPlugin("fake_plugin1", "fake1");
+ builder.AddCertificateProviderPlugin("fake_plugin2", "fake2");
+ std::vector<std::string> fields;
+ fields.push_back(absl::StrFormat(" \"certificate_file\": \"%s\"",
+ kClientCertPath));
+ fields.push_back(absl::StrFormat(" \"private_key_file\": \"%s\"",
+ kClientKeyPath));
+ fields.push_back(
+ absl::StrFormat(" \"spiffe_bundle_map_file\": \"%s\"",
+ kClientSpiffeBundleMapPath));
+ builder.AddCertificateProviderPlugin("file_plugin", "file_watcher",
+ absl::StrJoin(fields, ",\n"));
+
+ InitClient(builder, /*lb_expected_authority=*/"",
+ /*xds_resource_does_not_exist_timeout_ms=*/0,
+ /*balancer_authority_override=*/"", /*args=*/nullptr,
+ CreateSpiffeXdsChannelCredentials());
+ CreateAndStartBackends(2, /*xds_enabled=*/false,
+ CreateMtlsSpiffeServerCredentials());
+ root_cert_ = grpc_core::testing::GetFileContents(kSpiffeCaCertPath);
+ bad_root_cert_ = grpc_core::testing::GetFileContents(kBadClientCertPath);
+ identity_pair_ = ReadTlsIdentityPair(kClientKeyPath, kClientCertPath);
+
+ // TODO(yashykt): Use different client certs here instead of reusing
+ // server certs after https://github.com/grpc/grpc/pull/24876 is merged
+ fallback_identity_pair_ =
+ ReadTlsIdentityPair(kServerKeyPath, kServerCertPath);
+ bad_identity_pair_ =
+ ReadTlsIdentityPair(kBadClientKeyPath, kBadClientCertPath);
+ server_san_exact_.set_exact("*.test.google.fr");
+ server_san_prefix_.set_prefix("waterzooi.test.google");
+ server_san_suffix_.set_suffix("google.fr");
+ server_san_contains_.set_contains("google");
+ server_san_regex_.mutable_safe_regex()->mutable_google_re2();
+ server_san_regex_.mutable_safe_regex()->set_regex(
+ "(foo|waterzooi).test.google.(fr|be)");
+ bad_san_1_.set_exact("192.168.1.4");
+ bad_san_2_.set_exact("foo.test.google.in");
+ authenticated_identity_ = {
+ "spiffe://foo.bar.com/9eebccd2-12bf-40a6-b262-65fe0487d453"};
+ fallback_authenticated_identity_ = {"*.test.google.fr",
+ "waterzooi.test.google.be",
+ "*.test.youtube.com", "192.168.1.3"};
+ EdsResourceArgs args({
+ {"locality0", CreateEndpointsForBackends(0, 1)},
+ });
+ balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
+ }
+
+ void MaybeSetUpstreamTlsContextOnCluster(
+ absl::string_view root_instance_name,
+ absl::string_view root_certificate_name,
+ absl::string_view identity_instance_name,
+ absl::string_view identity_certificate_name,
+ const std::vector<StringMatcher>& san_matchers, Cluster* cluster) {
+ if (!identity_instance_name.empty() || !root_instance_name.empty()) {
+ auto* transport_socket = cluster->mutable_transport_socket();
+ transport_socket->set_name("envoy.transport_sockets.tls");
+ UpstreamTlsContext upstream_tls_context;
+ if (!identity_instance_name.empty()) {
+ upstream_tls_context.mutable_common_tls_context()
+ ->mutable_tls_certificate_provider_instance()
+ ->set_instance_name(std::string(identity_instance_name));
+ upstream_tls_context.mutable_common_tls_context()
+ ->mutable_tls_certificate_provider_instance()
+ ->set_certificate_name(std::string(identity_certificate_name));
+ }
+ if (!root_instance_name.empty()) {
+ upstream_tls_context.mutable_common_tls_context()
+ ->mutable_validation_context()
+ ->mutable_ca_certificate_provider_instance()
+ ->set_instance_name(std::string(root_instance_name));
+ upstream_tls_context.mutable_common_tls_context()
+ ->mutable_validation_context()
+ ->mutable_ca_certificate_provider_instance()
+ ->set_certificate_name(std::string(root_certificate_name));
+ }
+ if (!san_matchers.empty()) {
+ auto* validation_context =
+ upstream_tls_context.mutable_common_tls_context()
+ ->mutable_validation_context();
+ for (const auto& san_matcher : san_matchers) {
+ *validation_context->add_match_subject_alt_names() = san_matcher;
+ }
+ }
+ transport_socket->mutable_typed_config()->PackFrom(upstream_tls_context);
+ }
+ }
+
+ // Sends CDS updates with the new security configuration and verifies that
+ // after propagation, this new configuration is used for connections. If \a
+ // identity_instance_name and \a root_instance_name are both empty,
+ // connections are expected to use fallback credentials.
+ // TODO(yashykt): The core of this logic should be inlined into the
+ // individual tests instead of being in this helper function.
+ void UpdateAndVerifyXdsSecurityConfiguration(
+ absl::string_view root_instance_name,
+ absl::string_view root_certificate_name,
+ absl::string_view identity_instance_name,
+ absl::string_view identity_certificate_name,
+ const std::vector<StringMatcher>& san_matchers,
+ const std::vector<std::string>& expected_authenticated_identity,
+ bool test_expects_failure = false) {
+ // Change the backend and use a unique service name to use so that we know
+ // that the CDS update was applied.
+ std::string service_name = absl::StrCat(
+ "eds_service_name",
+ absl::FormatTime("%H%M%E3S", absl::Now(), absl::LocalTimeZone()));
+ backend_index_ = (backend_index_ + 1) % 2;
+ EdsResourceArgs args({
+ {"locality0",
+ CreateEndpointsForBackends(backend_index_, backend_index_ + 1)},
+ });
+ balancer_->ads_service()->SetEdsResource(
+ BuildEdsResource(args, service_name.c_str()));
+ auto cluster = default_cluster_;
+ cluster.mutable_eds_cluster_config()->set_service_name(service_name);
+ MaybeSetUpstreamTlsContextOnCluster(
+ root_instance_name, root_certificate_name, identity_instance_name,
+ identity_certificate_name, san_matchers, &cluster);
+ balancer_->ads_service()->SetCdsResource(cluster);
+ // The updates might take time to have an effect, so use a retry loop.
+ if (test_expects_failure) {
+ SendRpcsUntilFailure(DEBUG_LOCATION, StatusCode::UNAVAILABLE,
+ // TODO(yashkt): Change individual test cases to
+ // expect the exact error message here.
+ ".*", /*timeout_ms=*/20 * 1000,
+ RpcOptions().set_timeout_ms(5000));
+ } else {
+ backends_[backend_index_]->backend_service()->ResetCounters();
+ SendRpcsUntil(
+ DEBUG_LOCATION,
+ [&](const RpcResult& result) {
+ // Make sure that we are hitting the correct backend.
+ // TODO(yashykt): Even if we haven't moved to the correct backend
+ // and are still using the previous update, we should still check
+ // for the status and make sure that it fits our expectations.
+ if (backends_[backend_index_]->backend_service()->request_count() ==
+ 0) {
+ return true;
+ }
+ EXPECT_TRUE(result.status.ok())
+ << "code=" << result.status.error_code()
+ << " message=" << result.status.error_message();
+ // Check that the identity is as expected.
+ EXPECT_EQ(backends_[backend_index_]
+ ->backend_service()
+ ->last_peer_identity(),
+ expected_authenticated_identity);
+ return false;
+ },
+ /* timeout_ms= */ 20 * 1000, RpcOptions().set_timeout_ms(5000));
+ }
+ }
+
+ std::string root_cert_;
+ std::string bad_root_cert_;
+ grpc_core::PemKeyCertPairList identity_pair_;
+ grpc_core::PemKeyCertPairList fallback_identity_pair_;
+ grpc_core::PemKeyCertPairList bad_identity_pair_;
+ grpc_core::SpiffeBundleMap spiffe_bundle_map_;
+ StringMatcher server_san_exact_;
+ StringMatcher server_san_prefix_;
+ StringMatcher server_san_suffix_;
+ StringMatcher server_san_contains_;
+ StringMatcher server_san_regex_;
+ StringMatcher bad_san_1_;
+ StringMatcher bad_san_2_;
+ std::vector<std::string> authenticated_identity_;
+ std::vector<std::string> fallback_authenticated_identity_;
+ int backend_index_ = 0;
+};
+
+INSTANTIATE_TEST_SUITE_P(XdsTest, XdsSecurityTest,
+ ::testing::Values(XdsTestType()), &XdsTestType::Name);
+
+TEST_P(XdsSecurityTest, TestMtlsConfigurationWithRootPluginUpdateSpiffe) {
+ auto map = grpc_core::SpiffeBundleMap::FromFile(kClientSpiffeBundleMapPath);
+ ASSERT_TRUE(map.ok());
+ auto bad_map =
+ grpc_core::SpiffeBundleMap::FromFile(kServerSpiffeBundleMapPath);
+ ASSERT_TRUE(bad_map.ok());
+ g_fake1_cert_data_map->Set({{"", {"", identity_pair_, *map}}});
+ g_fake2_cert_data_map->Set({{"", {"", bad_identity_pair_, *bad_map}}});
+ UpdateAndVerifyXdsSecurityConfiguration("fake_plugin1", "", "fake_plugin1",
+ "", {server_san_exact_},
+ authenticated_identity_);
+ UpdateAndVerifyXdsSecurityConfiguration("fake_plugin2" /* bad root */, "",
+ "fake_plugin1", "", {}, {},
+ true /* failure */);
+ UpdateAndVerifyXdsSecurityConfiguration("fake_plugin1", "", "fake_plugin1",
+ "", {server_san_exact_},
+ authenticated_identity_);
+}
+
+} // namespace
+} // namespace testing
+} // namespace grpc
+
+int main(int argc, char** argv) {
+ grpc::testing::TestEnvironment env(&argc, argv);
+ ::testing::InitGoogleTest(&argc, argv);
+ // Make the backup poller poll very frequently in order to pick up
+ // updates from all the subchannels's FDs.
+ grpc_core::ConfigVars::Overrides overrides;
+ overrides.client_channel_backup_poll_interval_ms = 1;
+ overrides.trace =
+ "call,channel,client_channel,client_channel_call,client_channel_lb_call,"
+ "handshaker";
+ grpc_core::ConfigVars::SetOverrides(overrides);
+#if TARGET_OS_IPHONE
+ // Workaround Apple CFStream bug
+ grpc_core::SetEnv("grpc_cfstream", "0");
+#endif
+ grpc::testing::FakeCertificateProvider::CertDataMapWrapper cert_data_map_1;
+ grpc::testing::g_fake1_cert_data_map = &cert_data_map_1;
+ grpc::testing::FakeCertificateProvider::CertDataMapWrapper cert_data_map_2;
+ grpc::testing::g_fake2_cert_data_map = &cert_data_map_2;
+ grpc_core::CoreConfiguration::RegisterEphemeralBuilder(
+ [](grpc_core::CoreConfiguration::Builder* builder) {
+ builder->certificate_provider_registry()
+ ->RegisterCertificateProviderFactory(
+ std::make_unique<grpc::testing::FakeCertificateProviderFactory>(
+ "fake1", grpc::testing::g_fake1_cert_data_map));
+ builder->certificate_provider_registry()
+ ->RegisterCertificateProviderFactory(
+ std::make_unique<grpc::testing::FakeCertificateProviderFactory>(
+ "fake2", grpc::testing::g_fake2_cert_data_map));
+ });
+ grpc_init();
+ 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 2fff97c..25537c9 100644
--- a/tools/run_tests/generated/tests.json
+++ b/tools/run_tests/generated/tests.json
@@ -8354,6 +8354,52 @@
"flaky": false,
"gtest": true,
"language": "c++",
+ "name": "spiffe_bundle_map_end2end_test",
+ "platforms": [
+ "linux",
+ "mac",
+ "posix",
+ "windows"
+ ],
+ "uses_polling": true
+ },
+ {
+ "args": [],
+ "benchmark": false,
+ "ci_platforms": [
+ "linux",
+ "mac",
+ "posix"
+ ],
+ "cpu_cost": 1.0,
+ "exclude_configs": [],
+ "exclude_iomgrs": [],
+ "flaky": false,
+ "gtest": true,
+ "language": "c++",
+ "name": "spiffe_ssl_transport_security_test",
+ "platforms": [
+ "linux",
+ "mac",
+ "posix"
+ ],
+ "uses_polling": true
+ },
+ {
+ "args": [],
+ "benchmark": false,
+ "ci_platforms": [
+ "linux",
+ "mac",
+ "posix",
+ "windows"
+ ],
+ "cpu_cost": 1.0,
+ "exclude_configs": [],
+ "exclude_iomgrs": [],
+ "flaky": false,
+ "gtest": true,
+ "language": "c++",
"name": "spiffe_utils_test",
"platforms": [
"linux",
@@ -10773,6 +10819,28 @@
"ci_platforms": [
"linux",
"mac",
+ "posix"
+ ],
+ "cpu_cost": 1.0,
+ "exclude_configs": [],
+ "exclude_iomgrs": [],
+ "flaky": false,
+ "gtest": true,
+ "language": "c++",
+ "name": "xds_security_spiffe_end2end_test",
+ "platforms": [
+ "linux",
+ "mac",
+ "posix"
+ ],
+ "uses_polling": true
+ },
+ {
+ "args": [],
+ "benchmark": false,
+ "ci_platforms": [
+ "linux",
+ "mac",
"posix",
"windows"
],