From c49fc0f78eb7d729f3178933aaaf5e0ec63c3efc Mon Sep 17 00:00:00 2001 From: Eugene Ostroukhov Date: Thu, 22 Aug 2024 14:20:26 -0700 Subject: [PATCH 1/2] [ci] Fix tools/run_tests/sanity/check_testing_docker_images.sh (#37555) --- tools/bazelify_tests/dockerimage_current_versions.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/bazelify_tests/dockerimage_current_versions.bzl b/tools/bazelify_tests/dockerimage_current_versions.bzl index a07bbf3b702a8..1f9ddc40fd79f 100644 --- a/tools/bazelify_tests/dockerimage_current_versions.bzl +++ b/tools/bazelify_tests/dockerimage_current_versions.bzl @@ -93,7 +93,7 @@ DOCKERIMAGE_CURRENT_VERSIONS = { "tools/dockerfile/test/binder_transport_apk.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/binder_transport_apk@sha256:5dddb0d70ee427c7ee20d2b4f9261579218dd6b208b74c2913e0e3c79b3166f7", "tools/dockerfile/test/csharp_debian11_arm64.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/csharp_debian11_arm64@sha256:4d4bc5f15e03f3d3d8fd889670ecde2c66a2e4d2dd9db80733c05c1d90c8a248", "tools/dockerfile/test/csharp_debian11_x64.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/csharp_debian11_x64@sha256:0763d919b17b4cfe5b65aff3bf911c04e9e4d46d11649858742033facd9f534f", - "tools/dockerfile/test/cxx_alpine_x64.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/cxx_alpine_x64@sha256:5beda19bcf186b6c9606f4265e38c99bb4308f25bc0987e0fc15164f3c205232", + "tools/dockerfile/test/cxx_alpine_x64.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/cxx_alpine_x64@sha256:10587bea5d163bf5c34c6157ebd1863d22863d9d38bbaf5135ffc6fbf2b73004", "tools/dockerfile/test/cxx_clang_17_x64.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/cxx_clang_17_x64@sha256:f4e88cdfe074ee33abbe01f97f945ded0f144693f1eeac4d541a256a7812a21a", "tools/dockerfile/test/cxx_clang_6_x64.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/cxx_clang_6_x64@sha256:eebbaf353522d523ec9a7acb34bb3ae194e22ea7493c85c01437719e30da205d", "tools/dockerfile/test/cxx_debian11_openssl102_x64.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/cxx_debian11_openssl102_x64@sha256:477ae0da7ff7faa9cf195c0d32472fec4cf8b7325505c63e00b5c794c9a4b1a7", @@ -107,7 +107,7 @@ DOCKERIMAGE_CURRENT_VERSIONS = { "tools/dockerfile/test/php73_zts_debian11_x64.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/php73_zts_debian11_x64@sha256:186a96566a9c11adfb198309431086bdb02421121c262a2bf0166e3e9b21fb37", "tools/dockerfile/test/php7_debian11_arm64.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/php7_debian11_arm64@sha256:7ee21f253a2ddd255f4f6779cd19818eec6524e78b0bf0a7765339e4aa7347c3", "tools/dockerfile/test/php7_debian11_x64.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/php7_debian11_x64@sha256:34b02fd66832ebf49601bef95632753e6710a75755401c21332d50056ccd52d2", - "tools/dockerfile/test/python_alpine_x64.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/python_alpine_x64@sha256:c541f0ce00ca542ed4ae86870b6b73c929227127c13e532d98a9afd28604b830", + "tools/dockerfile/test/python_alpine_x64.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/python_alpine_x64@sha256:0a4e3c166fb676d85ea26d9fffec3858d59a2f243a3acc1c2f9bd293a590a98c", "tools/dockerfile/test/python_debian11_default_arm64.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/python_debian11_default_arm64@sha256:fccca33a655c7aa89dd7ebd9492cbcc1f636bd2a004cd939d1982cfce3d68326", "tools/dockerfile/test/python_debian11_default_x64.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/python_debian11_default_x64@sha256:4c539fc93d343324309939c2964204c90f4054cd9eeed093af315cb0f0ef7c26", "tools/dockerfile/test/rbe_ubuntu2004.current_version": "docker://us-docker.pkg.dev/grpc-testing/testing-images-public/rbe_ubuntu2004@sha256:b3eb1a17b7b091e3c5648a803076b2c40601242ff91c04d55997af6641305f68", From 71c8629e75dca5fd0d2f3d676cc0de381eb0e54c Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Thu, 22 Aug 2024 17:10:40 -0700 Subject: [PATCH 2/2] [call creds] refactor token-fetching call creds to allow cleaner reuse (#37510) Previously, `grpc_oauth2_token_fetcher_credentials` provided functionality for on-demand token-fetching, but it was integrated into the oauth2 code, so it was not possible to use that same code for on-demand fetching of (e.g.) JWT tokens. This PR splits that class into two parts: 1. A base `TokenFetcherCredentials` class that provides a framework for on-demand fetching of any arbitrary type of auth token. 2. An `Oauth2TokenFetcherCredentials` subclass that derives from `TokenFetcherCredentials` and provides handling for oauth2 tokens. The `grpc_compute_engine_token_fetcher_credentials`, `StsTokenFetcherCredentials`, and `grpc_google_refresh_token_credentials` classes that previously derived from `grpc_oauth2_token_fetcher_credentials` now derive from `Oauth2TokenFetcherCredentials` instead, so there's not much change to those classes (other than a cleaner interface with the base class functionality). The `ExternalAccountCredentials` class and its subclasses got more extensive changes here. Previously, this class inheritted from `grpc_oauth2_token_fetcher_credentials` and fooled the base class into thinking that it directly fetched the oauth2 token, when in fact it actually performed a number of steps to gather data and then constructed a synthetic HTTP response to pass back to the base class. I have changed this to instead derive directly from `TokenFetcherCredentials` to provide a much cleaner interface with the parent class. In addition, I have changed `grpc_call_credentials` from `RefCounted<>` to `DualRefCounted<>` to provide a clean way to shut down any in-flight token fetch when the credentials are unreffed. This PR paves the way for subsequent work that will allow implementing an on-demand JWT token fetcher call credential, as part of gRFC A83 (https://github.com/grpc/proposal/pull/438). Closes #37510 COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/37510 from markdroth:token_fetcher_call_creds_refactor 3bd398a7622a7671f5560108e47da4d4095d8400 PiperOrigin-RevId: 666547985 --- CMakeLists.txt | 8 + Makefile | 1 + Package.swift | 2 + build_autogenerated.yaml | 8 + config.m4 | 2 + config.w32 | 2 + gRPC-C++.podspec | 2 + gRPC-Core.podspec | 3 + grpc.gemspec | 2 + package.xml | 2 + src/core/BUILD | 37 + .../composite/composite_credentials.h | 2 + .../lib/security/credentials/credentials.h | 2 +- .../aws_external_account_credentials.cc | 645 +++++++------ .../aws_external_account_credentials.h | 95 +- .../external/external_account_credentials.cc | 748 ++++++++-------- .../external/external_account_credentials.h | 177 ++-- .../file_external_account_credentials.cc | 128 ++- .../file_external_account_credentials.h | 34 +- .../url_external_account_credentials.cc | 206 ++--- .../url_external_account_credentials.h | 31 +- .../credentials/fake/fake_credentials.h | 2 + .../google_default_credentials.cc | 68 +- .../credentials/iam/iam_credentials.h | 2 + .../credentials/jwt/jwt_credentials.h | 2 + .../credentials/oauth2/oauth2_credentials.cc | 413 ++++----- .../credentials/oauth2/oauth2_credentials.h | 90 +- .../credentials/plugin/plugin_credentials.h | 2 + .../token_fetcher_credentials.cc | 130 +++ .../token_fetcher/token_fetcher_credentials.h | 115 +++ src/python/grpcio/grpc_core_dependencies.py | 1 + test/core/filters/client_auth_filter_test.cc | 2 + test/core/security/BUILD | 2 + test/core/security/credentials_test.cc | 845 ++++++++++-------- tools/doxygen/Doxyfile.c++.internal | 2 + tools/doxygen/Doxyfile.core.internal | 2 + 36 files changed, 2129 insertions(+), 1686 deletions(-) create mode 100644 src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.cc create mode 100644 src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 99a85a30376ac..ebf76ecc93ab5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2458,6 +2458,7 @@ add_library(grpc src/core/lib/security/credentials/tls/grpc_tls_crl_provider.cc src/core/lib/security/credentials/tls/tls_credentials.cc src/core/lib/security/credentials/tls/tls_utils.cc + src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.cc src/core/lib/security/credentials/xds/xds_credentials.cc src/core/lib/security/security_connector/alts/alts_security_connector.cc src/core/lib/security/security_connector/fake/fake_security_connector.cc @@ -30600,6 +30601,12 @@ endif() if(gRPC_BUILD_TESTS) add_executable(test_core_security_credentials_test + ${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.cc + ${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.h + ${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.grpc.pb.h + test/core/event_engine/event_engine_test_utils.cc + test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.cc test/core/security/credentials_test.cc test/core/test_util/cmdline.cc test/core/test_util/fuzzer_util.cc @@ -30643,6 +30650,7 @@ target_include_directories(test_core_security_credentials_test target_link_libraries(test_core_security_credentials_test ${_gRPC_ALLTARGETS_LIBRARIES} gtest + ${_gRPC_PROTOBUF_LIBRARIES} grpc_test_util ) diff --git a/Makefile b/Makefile index 5d194c1d104cf..a426c5ae922e7 100644 --- a/Makefile +++ b/Makefile @@ -1295,6 +1295,7 @@ LIBGRPC_SRC = \ src/core/lib/security/credentials/tls/grpc_tls_crl_provider.cc \ src/core/lib/security/credentials/tls/tls_credentials.cc \ src/core/lib/security/credentials/tls/tls_utils.cc \ + src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.cc \ src/core/lib/security/credentials/xds/xds_credentials.cc \ src/core/lib/security/security_connector/alts/alts_security_connector.cc \ src/core/lib/security/security_connector/fake/fake_security_connector.cc \ diff --git a/Package.swift b/Package.swift index 1e3548bc56723..a6e7852597f28 100644 --- a/Package.swift +++ b/Package.swift @@ -1601,6 +1601,8 @@ let package = Package( "src/core/lib/security/credentials/tls/tls_credentials.h", "src/core/lib/security/credentials/tls/tls_utils.cc", "src/core/lib/security/credentials/tls/tls_utils.h", + "src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.cc", + "src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h", "src/core/lib/security/credentials/xds/xds_credentials.cc", "src/core/lib/security/credentials/xds/xds_credentials.h", "src/core/lib/security/security_connector/alts/alts_security_connector.cc", diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index abcef83cc2a0d..33b71cfa8ae6c 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -1055,6 +1055,7 @@ libs: - src/core/lib/security/credentials/tls/grpc_tls_crl_provider.h - src/core/lib/security/credentials/tls/tls_credentials.h - src/core/lib/security/credentials/tls/tls_utils.h + - src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h - src/core/lib/security/credentials/xds/xds_credentials.h - src/core/lib/security/security_connector/alts/alts_security_connector.h - src/core/lib/security/security_connector/fake/fake_security_connector.h @@ -1870,6 +1871,7 @@ libs: - src/core/lib/security/credentials/tls/grpc_tls_crl_provider.cc - src/core/lib/security/credentials/tls/tls_credentials.cc - src/core/lib/security/credentials/tls/tls_utils.cc + - src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.cc - src/core/lib/security/credentials/xds/xds_credentials.cc - src/core/lib/security/security_connector/alts/alts_security_connector.cc - src/core/lib/security/security_connector/fake/fake_security_connector.cc @@ -19649,6 +19651,8 @@ targets: build: test language: c++ headers: + - test/core/event_engine/event_engine_test_utils.h + - test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h - test/core/test_util/cmdline.h - test/core/test_util/evaluate_args_test_util.h - test/core/test_util/fuzzer_util.h @@ -19660,6 +19664,9 @@ targets: - test/core/test_util/slice_splitter.h - test/core/test_util/tracer_util.h src: + - test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.proto + - test/core/event_engine/event_engine_test_utils.cc + - test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.cc - test/core/security/credentials_test.cc - test/core/test_util/cmdline.cc - test/core/test_util/fuzzer_util.cc @@ -19672,6 +19679,7 @@ targets: - test/core/test_util/tracer_util.cc deps: - gtest + - protobuf - grpc_test_util - name: test_core_security_ssl_credentials_test gtest: true diff --git a/config.m4 b/config.m4 index 00594e8878fb1..c7f38bced2a98 100644 --- a/config.m4 +++ b/config.m4 @@ -670,6 +670,7 @@ if test "$PHP_GRPC" != "no"; then src/core/lib/security/credentials/tls/grpc_tls_crl_provider.cc \ src/core/lib/security/credentials/tls/tls_credentials.cc \ src/core/lib/security/credentials/tls/tls_utils.cc \ + src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.cc \ src/core/lib/security/credentials/xds/xds_credentials.cc \ src/core/lib/security/security_connector/alts/alts_security_connector.cc \ src/core/lib/security/security_connector/fake/fake_security_connector.cc \ @@ -1557,6 +1558,7 @@ if test "$PHP_GRPC" != "no"; then PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/plugin) PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/ssl) PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/tls) + PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/token_fetcher) PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/xds) PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/security_connector) PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/security_connector/alts) diff --git a/config.w32 b/config.w32 index d443a78a90e20..0e722d1bd9b68 100644 --- a/config.w32 +++ b/config.w32 @@ -635,6 +635,7 @@ if (PHP_GRPC != "no") { "src\\core\\lib\\security\\credentials\\tls\\grpc_tls_crl_provider.cc " + "src\\core\\lib\\security\\credentials\\tls\\tls_credentials.cc " + "src\\core\\lib\\security\\credentials\\tls\\tls_utils.cc " + + "src\\core\\lib\\security\\credentials\\token_fetcher\\token_fetcher_credentials.cc " + "src\\core\\lib\\security\\credentials\\xds\\xds_credentials.cc " + "src\\core\\lib\\security\\security_connector\\alts\\alts_security_connector.cc " + "src\\core\\lib\\security\\security_connector\\fake\\fake_security_connector.cc " + @@ -1695,6 +1696,7 @@ if (PHP_GRPC != "no") { FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\plugin"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\ssl"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\tls"); + FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\token_fetcher"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\xds"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\security_connector"); FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\security_connector\\alts"); diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec index 122a26ff39d24..42545ee1bf598 100644 --- a/gRPC-C++.podspec +++ b/gRPC-C++.podspec @@ -1159,6 +1159,7 @@ Pod::Spec.new do |s| 'src/core/lib/security/credentials/tls/grpc_tls_crl_provider.h', 'src/core/lib/security/credentials/tls/tls_credentials.h', 'src/core/lib/security/credentials/tls/tls_utils.h', + 'src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h', 'src/core/lib/security/credentials/xds/xds_credentials.h', 'src/core/lib/security/security_connector/alts/alts_security_connector.h', 'src/core/lib/security/security_connector/fake/fake_security_connector.h', @@ -2444,6 +2445,7 @@ Pod::Spec.new do |s| 'src/core/lib/security/credentials/tls/grpc_tls_crl_provider.h', 'src/core/lib/security/credentials/tls/tls_credentials.h', 'src/core/lib/security/credentials/tls/tls_utils.h', + 'src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h', 'src/core/lib/security/credentials/xds/xds_credentials.h', 'src/core/lib/security/security_connector/alts/alts_security_connector.h', 'src/core/lib/security/security_connector/fake/fake_security_connector.h', diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec index 10002b9dfe654..9b35f946375d6 100644 --- a/gRPC-Core.podspec +++ b/gRPC-Core.podspec @@ -1717,6 +1717,8 @@ Pod::Spec.new do |s| 'src/core/lib/security/credentials/tls/tls_credentials.h', 'src/core/lib/security/credentials/tls/tls_utils.cc', 'src/core/lib/security/credentials/tls/tls_utils.h', + 'src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.cc', + 'src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h', 'src/core/lib/security/credentials/xds/xds_credentials.cc', 'src/core/lib/security/credentials/xds/xds_credentials.h', 'src/core/lib/security/security_connector/alts/alts_security_connector.cc', @@ -3222,6 +3224,7 @@ Pod::Spec.new do |s| 'src/core/lib/security/credentials/tls/grpc_tls_crl_provider.h', 'src/core/lib/security/credentials/tls/tls_credentials.h', 'src/core/lib/security/credentials/tls/tls_utils.h', + 'src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h', 'src/core/lib/security/credentials/xds/xds_credentials.h', 'src/core/lib/security/security_connector/alts/alts_security_connector.h', 'src/core/lib/security/security_connector/fake/fake_security_connector.h', diff --git a/grpc.gemspec b/grpc.gemspec index ce35bf58521cd..d90dfc5dddd9b 100644 --- a/grpc.gemspec +++ b/grpc.gemspec @@ -1603,6 +1603,8 @@ Gem::Specification.new do |s| s.files += %w( src/core/lib/security/credentials/tls/tls_credentials.h ) s.files += %w( src/core/lib/security/credentials/tls/tls_utils.cc ) s.files += %w( src/core/lib/security/credentials/tls/tls_utils.h ) + s.files += %w( src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.cc ) + s.files += %w( src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h ) s.files += %w( src/core/lib/security/credentials/xds/xds_credentials.cc ) s.files += %w( src/core/lib/security/credentials/xds/xds_credentials.h ) s.files += %w( src/core/lib/security/security_connector/alts/alts_security_connector.cc ) diff --git a/package.xml b/package.xml index 7b3a1bbdb60e2..fde0df1ef9ca2 100644 --- a/package.xml +++ b/package.xml @@ -1585,6 +1585,8 @@ + + diff --git a/src/core/BUILD b/src/core/BUILD index c6ed567b76886..50ce0f5c9ccab 100644 --- a/src/core/BUILD +++ b/src/core/BUILD @@ -4317,6 +4317,40 @@ grpc_cc_library( ], ) +grpc_cc_library( + name = "token_fetcher_credentials", + srcs = [ + "lib/security/credentials/token_fetcher/token_fetcher_credentials.cc", + ], + hdrs = [ + "lib/security/credentials/token_fetcher/token_fetcher_credentials.h", + ], + external_deps = [ + "absl/container:flat_hash_set", + "absl/functional:any_invocable", + "absl/status:statusor", + "absl/types:variant", + ], + language = "c++", + deps = [ + "arena_promise", + "context", + "metadata", + "poll", + "pollset_set", + "ref_counted", + "time", + "useful", + "//:gpr", + "//:grpc_security_base", + "//:httpcli", + "//:iomgr", + "//:orphanable", + "//:promise", + "//:ref_counted_ptr", + ], +) + grpc_cc_library( name = "grpc_oauth2_credentials", srcs = [ @@ -4354,6 +4388,7 @@ grpc_cc_library( "slice_refcount", "status_helper", "time", + "token_fetcher_credentials", "unique_type_name", "useful", "//:gpr", @@ -4401,6 +4436,7 @@ grpc_cc_library( language = "c++", deps = [ "closure", + "default_event_engine", "env", "error", "error_utils", @@ -4414,6 +4450,7 @@ grpc_cc_library( "slice_refcount", "status_helper", "time", + "token_fetcher_credentials", "//:gpr", "//:grpc_base", "//:grpc_core_credentials_header", diff --git a/src/core/lib/security/credentials/composite/composite_credentials.h b/src/core/lib/security/credentials/composite/composite_credentials.h index bcf2915abaaf1..6bd13f0dea872 100644 --- a/src/core/lib/security/credentials/composite/composite_credentials.h +++ b/src/core/lib/security/credentials/composite/composite_credentials.h @@ -102,6 +102,8 @@ class grpc_composite_call_credentials : public grpc_call_credentials { grpc_core::RefCountedPtr creds2); ~grpc_composite_call_credentials() override = default; + void Orphaned() override { inner_.clear(); } + grpc_core::ArenaPromise> GetRequestMetadata(grpc_core::ClientMetadataHandle initial_metadata, const GetRequestMetadataArgs* args) override; diff --git a/src/core/lib/security/credentials/credentials.h b/src/core/lib/security/credentials/credentials.h index 55ffb5f14cf97..3066cbfeb33b6 100644 --- a/src/core/lib/security/credentials/credentials.h +++ b/src/core/lib/security/credentials/credentials.h @@ -183,7 +183,7 @@ using CredentialsMetadataArray = std::vector>; // class. Otherwise, compiler will complain about type mismatch due to // -Wmismatched-tags. struct grpc_call_credentials - : public grpc_core::RefCounted { + : public grpc_core::DualRefCounted { public: // TODO(roth): Consider whether security connector actually needs to // be part of this interface. Currently, it is here only for the diff --git a/src/core/lib/security/credentials/external/aws_external_account_credentials.cc b/src/core/lib/security/credentials/external/aws_external_account_credentials.cc index 34c4b4521dd22..c2e0686101971 100644 --- a/src/core/lib/security/credentials/external/aws_external_account_credentials.cc +++ b/src/core/lib/security/credentials/external/aws_external_account_credentials.cc @@ -58,6 +58,13 @@ const char* kAccessKeyIdEnvVar = "AWS_ACCESS_KEY_ID"; const char* kSecretAccessKeyEnvVar = "AWS_SECRET_ACCESS_KEY"; const char* kSessionTokenEnvVar = "AWS_SESSION_TOKEN"; +bool ShouldUseMetadataServer() { + return !((GetEnv(kRegionEnvVar).has_value() || + GetEnv(kDefaultRegionEnvVar).has_value()) && + (GetEnv(kAccessKeyIdEnvVar).has_value() && + GetEnv(kSecretAccessKeyEnvVar).has_value())); +} + std::string UrlEncode(const absl::string_view& s) { const char* hex = "0123456789ABCDEF"; std::string result; @@ -78,281 +85,202 @@ std::string UrlEncode(const absl::string_view& s) { } // namespace -RefCountedPtr -AwsExternalAccountCredentials::Create(Options options, - std::vector scopes, - grpc_error_handle* error) { - auto creds = MakeRefCounted( - std::move(options), std::move(scopes), error); - if (error->ok()) { - return creds; - } else { - return nullptr; - } -} +// +// AwsExternalAccountCredentials::AwsFetchBody +// -AwsExternalAccountCredentials::AwsExternalAccountCredentials( - Options options, std::vector scopes, grpc_error_handle* error) - : ExternalAccountCredentials(options, std::move(scopes)) { - audience_ = options.audience; - auto it = options.credential_source.object().find("environment_id"); - if (it == options.credential_source.object().end()) { - *error = GRPC_ERROR_CREATE("environment_id field not present."); - return; - } - if (it->second.type() != Json::Type::kString) { - *error = GRPC_ERROR_CREATE("environment_id field must be a string."); - return; - } - if (it->second.string() != kExpectedEnvironmentId) { - *error = GRPC_ERROR_CREATE("environment_id does not match."); - return; - } - it = options.credential_source.object().find("region_url"); - if (it == options.credential_source.object().end()) { - *error = GRPC_ERROR_CREATE("region_url field not present."); - return; - } - if (it->second.type() != Json::Type::kString) { - *error = GRPC_ERROR_CREATE("region_url field must be a string."); - return; - } - region_url_ = it->second.string(); - it = options.credential_source.object().find("url"); - if (it != options.credential_source.object().end() && - it->second.type() == Json::Type::kString) { - url_ = it->second.string(); - } - it = - options.credential_source.object().find("regional_cred_verification_url"); - if (it == options.credential_source.object().end()) { - *error = - GRPC_ERROR_CREATE("regional_cred_verification_url field not present."); - return; - } - if (it->second.type() != Json::Type::kString) { - *error = GRPC_ERROR_CREATE( - "regional_cred_verification_url field must be a string."); - return; - } - regional_cred_verification_url_ = it->second.string(); - it = options.credential_source.object().find("imdsv2_session_token_url"); - if (it != options.credential_source.object().end() && - it->second.type() == Json::Type::kString) { - imdsv2_session_token_url_ = it->second.string(); - } +AwsExternalAccountCredentials::AwsFetchBody::AwsFetchBody( + absl::AnyInvocable)> on_done, + AwsExternalAccountCredentials* creds, Timestamp deadline) + : FetchBody(std::move(on_done)), creds_(creds), deadline_(deadline) { + MutexLock lock(&mu_); + // Do a quick async hop here, so that we can invoke the callback at + // any time without deadlocking. + fetch_body_ = MakeOrphanable( + creds->event_engine(), + [self = RefAsSubclass()]( + absl::StatusOr /*result*/) { self->Start(); }, + ""); } -bool AwsExternalAccountCredentials::ShouldUseMetadataServer() { - return !((GetEnv(kRegionEnvVar).has_value() || - GetEnv(kDefaultRegionEnvVar).has_value()) && - (GetEnv(kAccessKeyIdEnvVar).has_value() && - GetEnv(kSecretAccessKeyEnvVar).has_value())); +void AwsExternalAccountCredentials::AwsFetchBody::Shutdown() { + MutexLock lock(&mu_); + fetch_body_.reset(); } -void AwsExternalAccountCredentials::RetrieveSubjectToken( - HTTPRequestContext* ctx, const Options& /*options*/, - std::function cb) { - if (ctx == nullptr) { - FinishRetrieveSubjectToken( - "", - GRPC_ERROR_CREATE( - "Missing HTTPRequestContext to start subject token retrieval.")); - return; - } - ctx_ = ctx; - cb_ = cb; - if (!imdsv2_session_token_url_.empty() && ShouldUseMetadataServer()) { - RetrieveImdsV2SessionToken(); - } else if (signer_ != nullptr) { - BuildSubjectToken(); - } else { - RetrieveRegion(); - } +void AwsExternalAccountCredentials::AwsFetchBody::AsyncFinish( + absl::StatusOr result) { + creds_->event_engine().Run( + [this, self = Ref(), result = std::move(result)]() mutable { + ApplicationCallbackExecCtx application_exec_ctx; + ExecCtx exec_ctx; + Finish(std::move(result)); + self.reset(); + }); } -void AwsExternalAccountCredentials::RetrieveImdsV2SessionToken() { - absl::StatusOr uri = URI::Parse(imdsv2_session_token_url_); - if (!uri.ok()) { - return; +bool AwsExternalAccountCredentials::AwsFetchBody::MaybeFail( + absl::Status status) { + if (!status.ok()) { + AsyncFinish(std::move(status)); + return true; } - grpc_http_header* headers = - static_cast(gpr_malloc(sizeof(grpc_http_header))); - headers[0].key = gpr_strdup("x-aws-ec2-metadata-token-ttl-seconds"); - headers[0].value = gpr_strdup("300"); - grpc_http_request request; - memset(&request, 0, sizeof(grpc_http_request)); - request.hdr_count = 1; - request.hdrs = headers; - grpc_http_response_destroy(&ctx_->response); - ctx_->response = {}; - GRPC_CLOSURE_INIT(&ctx_->closure, OnRetrieveImdsV2SessionToken, this, - nullptr); - RefCountedPtr http_request_creds; - if (uri->scheme() == "http") { - http_request_creds = RefCountedPtr( - grpc_insecure_credentials_create()); - } else { - http_request_creds = CreateHttpRequestSSLCredentials(); + if (fetch_body_ == nullptr) { + AsyncFinish( + absl::CancelledError("external account credentials fetch cancelled")); + return true; } - http_request_ = - HttpRequest::Put(std::move(*uri), nullptr /* channel args */, - ctx_->pollent, &request, ctx_->deadline, &ctx_->closure, - &ctx_->response, std::move(http_request_creds)); - http_request_->Start(); - grpc_http_request_destroy(&request); -} - -void AwsExternalAccountCredentials::OnRetrieveImdsV2SessionToken( - void* arg, grpc_error_handle error) { - AwsExternalAccountCredentials* self = - static_cast(arg); - self->OnRetrieveImdsV2SessionTokenInternal(error); + return false; } -void AwsExternalAccountCredentials::OnRetrieveImdsV2SessionTokenInternal( - grpc_error_handle error) { - if (!error.ok()) { - FinishRetrieveSubjectToken("", error); - return; - } - imdsv2_session_token_ = - std::string(ctx_->response.body, ctx_->response.body_length); - if (signer_ != nullptr) { +void AwsExternalAccountCredentials::AwsFetchBody::Start() { + MutexLock lock(&mu_); + if (MaybeFail(absl::OkStatus())) return; + if (!creds_->imdsv2_session_token_url_.empty() && ShouldUseMetadataServer()) { + RetrieveImdsV2SessionToken(); + } else if (creds_->signer_ != nullptr) { BuildSubjectToken(); } else { RetrieveRegion(); } } -void AwsExternalAccountCredentials::AddMetadataRequestHeaders( - grpc_http_request* request) { - if (!imdsv2_session_token_.empty()) { - CHECK_EQ(request->hdr_count, 0u); - CHECK_EQ(request->hdrs, nullptr); - grpc_http_header* headers = - static_cast(gpr_malloc(sizeof(grpc_http_header))); - headers[0].key = gpr_strdup("x-aws-ec2-metadata-token"); - headers[0].value = gpr_strdup(imdsv2_session_token_.c_str()); - request->hdr_count = 1; - request->hdrs = headers; +void AwsExternalAccountCredentials::AwsFetchBody::RetrieveImdsV2SessionToken() { + absl::StatusOr uri = URI::Parse(creds_->imdsv2_session_token_url_); + if (!uri.ok()) { + AsyncFinish(uri.status()); + return; } + fetch_body_ = MakeOrphanable( + [&](grpc_http_response* response, grpc_closure* on_http_response) { + grpc_http_header* headers = static_cast( + gpr_malloc(sizeof(grpc_http_header))); + headers[0].key = gpr_strdup("x-aws-ec2-metadata-token-ttl-seconds"); + headers[0].value = gpr_strdup("300"); + grpc_http_request request; + memset(&request, 0, sizeof(grpc_http_request)); + request.hdr_count = 1; + request.hdrs = headers; + RefCountedPtr http_request_creds; + if (uri->scheme() == "http") { + http_request_creds = RefCountedPtr( + grpc_insecure_credentials_create()); + } else { + http_request_creds = CreateHttpRequestSSLCredentials(); + } + auto http_request = HttpRequest::Put( + std::move(*uri), /*args=*/nullptr, creds_->pollent(), &request, + deadline_, on_http_response, response, + std::move(http_request_creds)); + http_request->Start(); + grpc_http_request_destroy(&request); + return http_request; + }, + [self = + RefAsSubclass()](absl::StatusOr result) { + MutexLock lock(&self->mu_); + if (self->MaybeFail(result.status())) return; + self->imdsv2_session_token_ = std::move(*result); + if (self->creds_->signer_ != nullptr) { + self->BuildSubjectToken(); + } else { + self->RetrieveRegion(); + } + }); } -void AwsExternalAccountCredentials::RetrieveRegion() { +void AwsExternalAccountCredentials::AwsFetchBody::RetrieveRegion() { auto region_from_env = GetEnv(kRegionEnvVar); if (!region_from_env.has_value()) { region_from_env = GetEnv(kDefaultRegionEnvVar); } if (region_from_env.has_value()) { region_ = std::move(*region_from_env); - if (url_.empty()) { + if (creds_->url_.empty()) { RetrieveSigningKeys(); } else { RetrieveRoleName(); } return; } - absl::StatusOr uri = URI::Parse(region_url_); + absl::StatusOr uri = URI::Parse(creds_->region_url_); if (!uri.ok()) { - FinishRetrieveSubjectToken( - "", GRPC_ERROR_CREATE(absl::StrFormat("Invalid region url. %s", - uri.status().ToString()))); - return; - } - grpc_http_request request; - memset(&request, 0, sizeof(grpc_http_request)); - grpc_http_response_destroy(&ctx_->response); - ctx_->response = {}; - AddMetadataRequestHeaders(&request); - GRPC_CLOSURE_INIT(&ctx_->closure, OnRetrieveRegion, this, nullptr); - RefCountedPtr http_request_creds; - if (uri->scheme() == "http") { - http_request_creds = RefCountedPtr( - grpc_insecure_credentials_create()); - } else { - http_request_creds = CreateHttpRequestSSLCredentials(); - } - http_request_ = - HttpRequest::Get(std::move(*uri), nullptr /* channel args */, - ctx_->pollent, &request, ctx_->deadline, &ctx_->closure, - &ctx_->response, std::move(http_request_creds)); - http_request_->Start(); - grpc_http_request_destroy(&request); -} - -void AwsExternalAccountCredentials::OnRetrieveRegion(void* arg, - grpc_error_handle error) { - AwsExternalAccountCredentials* self = - static_cast(arg); - self->OnRetrieveRegionInternal(error); -} - -void AwsExternalAccountCredentials::OnRetrieveRegionInternal( - grpc_error_handle error) { - if (!error.ok()) { - FinishRetrieveSubjectToken("", error); + AsyncFinish(GRPC_ERROR_CREATE( + absl::StrFormat("Invalid region url. %s", uri.status().ToString()))); return; } - // Remove the last letter of availability zone to get pure region - absl::string_view response_body(ctx_->response.body, - ctx_->response.body_length); - region_ = std::string(response_body.substr(0, response_body.size() - 1)); - if (url_.empty()) { - RetrieveSigningKeys(); - } else { - RetrieveRoleName(); - } + fetch_body_ = MakeOrphanable( + [&](grpc_http_response* response, grpc_closure* on_http_response) { + grpc_http_request request; + memset(&request, 0, sizeof(grpc_http_request)); + AddMetadataRequestHeaders(&request); + RefCountedPtr http_request_creds; + if (uri->scheme() == "http") { + http_request_creds = RefCountedPtr( + grpc_insecure_credentials_create()); + } else { + http_request_creds = CreateHttpRequestSSLCredentials(); + } + auto http_request = HttpRequest::Get( + std::move(*uri), /*args=*/nullptr, creds_->pollent(), &request, + deadline_, on_http_response, response, + std::move(http_request_creds)); + http_request->Start(); + grpc_http_request_destroy(&request); + return http_request; + }, + [self = + RefAsSubclass()](absl::StatusOr result) { + MutexLock lock(&self->mu_); + if (self->MaybeFail(result.status())) return; + // Remove the last letter of availability zone to get pure region + self->region_ = result->substr(0, result->size() - 1); + if (self->creds_->url_.empty()) { + self->RetrieveSigningKeys(); + } else { + self->RetrieveRoleName(); + } + }); } -void AwsExternalAccountCredentials::RetrieveRoleName() { - absl::StatusOr uri = URI::Parse(url_); +void AwsExternalAccountCredentials::AwsFetchBody::RetrieveRoleName() { + absl::StatusOr uri = URI::Parse(creds_->url_); if (!uri.ok()) { - FinishRetrieveSubjectToken( - "", GRPC_ERROR_CREATE( - absl::StrFormat("Invalid url: %s.", uri.status().ToString()))); + AsyncFinish(GRPC_ERROR_CREATE( + absl::StrFormat("Invalid url: %s.", uri.status().ToString()))); return; } - grpc_http_request request; - memset(&request, 0, sizeof(grpc_http_request)); - grpc_http_response_destroy(&ctx_->response); - ctx_->response = {}; - AddMetadataRequestHeaders(&request); - GRPC_CLOSURE_INIT(&ctx_->closure, OnRetrieveRoleName, this, nullptr); - // TODO(ctiller): use the caller's resource quota. - RefCountedPtr http_request_creds; - if (uri->scheme() == "http") { - http_request_creds = RefCountedPtr( - grpc_insecure_credentials_create()); - } else { - http_request_creds = CreateHttpRequestSSLCredentials(); - } - http_request_ = - HttpRequest::Get(std::move(*uri), nullptr /* channel args */, - ctx_->pollent, &request, ctx_->deadline, &ctx_->closure, - &ctx_->response, std::move(http_request_creds)); - http_request_->Start(); - grpc_http_request_destroy(&request); -} - -void AwsExternalAccountCredentials::OnRetrieveRoleName( - void* arg, grpc_error_handle error) { - AwsExternalAccountCredentials* self = - static_cast(arg); - self->OnRetrieveRoleNameInternal(error); + fetch_body_ = MakeOrphanable( + [&](grpc_http_response* response, grpc_closure* on_http_response) { + grpc_http_request request; + memset(&request, 0, sizeof(grpc_http_request)); + AddMetadataRequestHeaders(&request); + // TODO(ctiller): use the caller's resource quota. + RefCountedPtr http_request_creds; + if (uri->scheme() == "http") { + http_request_creds = RefCountedPtr( + grpc_insecure_credentials_create()); + } else { + http_request_creds = CreateHttpRequestSSLCredentials(); + } + auto http_request = HttpRequest::Get( + std::move(*uri), /*args=*/nullptr, creds_->pollent(), &request, + deadline_, on_http_response, response, + std::move(http_request_creds)); + http_request->Start(); + grpc_http_request_destroy(&request); + return http_request; + }, + [self = + RefAsSubclass()](absl::StatusOr result) { + MutexLock lock(&self->mu_); + if (self->MaybeFail(result.status())) return; + self->role_name_ = std::move(*result); + self->RetrieveSigningKeys(); + }); } -void AwsExternalAccountCredentials::OnRetrieveRoleNameInternal( - grpc_error_handle error) { - if (!error.ok()) { - FinishRetrieveSubjectToken("", error); - return; - } - role_name_ = std::string(ctx_->response.body, ctx_->response.body_length); - RetrieveSigningKeys(); -} - -void AwsExternalAccountCredentials::RetrieveSigningKeys() { +void AwsExternalAccountCredentials::AwsFetchBody::RetrieveSigningKeys() { auto access_key_id_from_env = GetEnv(kAccessKeyIdEnvVar); auto secret_access_key_from_env = GetEnv(kSecretAccessKeyEnvVar); auto token_from_env = GetEnv(kSessionTokenEnvVar); @@ -367,122 +295,106 @@ void AwsExternalAccountCredentials::RetrieveSigningKeys() { return; } if (role_name_.empty()) { - FinishRetrieveSubjectToken( - "", + AsyncFinish( GRPC_ERROR_CREATE("Missing role name when retrieving signing keys.")); return; } - std::string url_with_role_name = absl::StrCat(url_, "/", role_name_); + std::string url_with_role_name = absl::StrCat(creds_->url_, "/", role_name_); absl::StatusOr uri = URI::Parse(url_with_role_name); if (!uri.ok()) { - FinishRetrieveSubjectToken( - "", GRPC_ERROR_CREATE(absl::StrFormat("Invalid url with role name: %s.", - uri.status().ToString()))); + AsyncFinish(GRPC_ERROR_CREATE(absl::StrFormat( + "Invalid url with role name: %s.", uri.status().ToString()))); return; } - grpc_http_request request; - memset(&request, 0, sizeof(grpc_http_request)); - grpc_http_response_destroy(&ctx_->response); - ctx_->response = {}; - AddMetadataRequestHeaders(&request); - GRPC_CLOSURE_INIT(&ctx_->closure, OnRetrieveSigningKeys, this, nullptr); - // TODO(ctiller): use the caller's resource quota. - RefCountedPtr http_request_creds; - if (uri->scheme() == "http") { - http_request_creds = RefCountedPtr( - grpc_insecure_credentials_create()); - } else { - http_request_creds = CreateHttpRequestSSLCredentials(); - } - http_request_ = - HttpRequest::Get(std::move(*uri), nullptr /* channel args */, - ctx_->pollent, &request, ctx_->deadline, &ctx_->closure, - &ctx_->response, std::move(http_request_creds)); - http_request_->Start(); - grpc_http_request_destroy(&request); -} - -void AwsExternalAccountCredentials::OnRetrieveSigningKeys( - void* arg, grpc_error_handle error) { - AwsExternalAccountCredentials* self = - static_cast(arg); - self->OnRetrieveSigningKeysInternal(error); + fetch_body_ = MakeOrphanable( + [&](grpc_http_response* response, grpc_closure* on_http_response) { + grpc_http_request request; + memset(&request, 0, sizeof(grpc_http_request)); + AddMetadataRequestHeaders(&request); + // TODO(ctiller): use the caller's resource quota. + RefCountedPtr http_request_creds; + if (uri->scheme() == "http") { + http_request_creds = RefCountedPtr( + grpc_insecure_credentials_create()); + } else { + http_request_creds = CreateHttpRequestSSLCredentials(); + } + auto http_request = HttpRequest::Get( + std::move(*uri), /*args=*/nullptr, creds_->pollent(), &request, + deadline_, on_http_response, response, + std::move(http_request_creds)); + http_request->Start(); + grpc_http_request_destroy(&request); + return http_request; + }, + [self = + RefAsSubclass()](absl::StatusOr result) { + MutexLock lock(&self->mu_); + if (self->MaybeFail(result.status())) return; + self->OnRetrieveSigningKeys(std::move(*result)); + }); } -void AwsExternalAccountCredentials::OnRetrieveSigningKeysInternal( - grpc_error_handle error) { - if (!error.ok()) { - FinishRetrieveSubjectToken("", error); - return; - } - absl::string_view response_body(ctx_->response.body, - ctx_->response.body_length); - auto json = JsonParse(response_body); +void AwsExternalAccountCredentials::AwsFetchBody::OnRetrieveSigningKeys( + std::string result) { + auto json = JsonParse(result); if (!json.ok()) { - FinishRetrieveSubjectToken( - "", GRPC_ERROR_CREATE( - absl::StrCat("Invalid retrieve signing keys response: ", - json.status().ToString()))); + AsyncFinish(GRPC_ERROR_CREATE(absl::StrCat( + "Invalid retrieve signing keys response: ", json.status().ToString()))); return; } if (json->type() != Json::Type::kObject) { - FinishRetrieveSubjectToken( - "", GRPC_ERROR_CREATE("Invalid retrieve signing keys response: " - "JSON type is not object")); + AsyncFinish( + GRPC_ERROR_CREATE("Invalid retrieve signing keys response: " + "JSON type is not object")); return; } auto it = json->object().find("AccessKeyId"); if (it != json->object().end() && it->second.type() == Json::Type::kString) { access_key_id_ = it->second.string(); } else { - FinishRetrieveSubjectToken( - "", GRPC_ERROR_CREATE(absl::StrFormat( - "Missing or invalid AccessKeyId in %s.", response_body))); + AsyncFinish(GRPC_ERROR_CREATE( + absl::StrFormat("Missing or invalid AccessKeyId in %s.", result))); return; } it = json->object().find("SecretAccessKey"); if (it != json->object().end() && it->second.type() == Json::Type::kString) { secret_access_key_ = it->second.string(); } else { - FinishRetrieveSubjectToken( - "", GRPC_ERROR_CREATE(absl::StrFormat( - "Missing or invalid SecretAccessKey in %s.", response_body))); + AsyncFinish(GRPC_ERROR_CREATE( + absl::StrFormat("Missing or invalid SecretAccessKey in %s.", result))); return; } it = json->object().find("Token"); if (it != json->object().end() && it->second.type() == Json::Type::kString) { token_ = it->second.string(); } else { - FinishRetrieveSubjectToken( - "", GRPC_ERROR_CREATE(absl::StrFormat("Missing or invalid Token in %s.", - response_body))); + AsyncFinish(GRPC_ERROR_CREATE( + absl::StrFormat("Missing or invalid Token in %s.", result))); return; } BuildSubjectToken(); } -void AwsExternalAccountCredentials::BuildSubjectToken() { +void AwsExternalAccountCredentials::AwsFetchBody::BuildSubjectToken() { grpc_error_handle error; - if (signer_ == nullptr) { - cred_verification_url_ = absl::StrReplaceAll( - regional_cred_verification_url_, {{"{region}", region_}}); - signer_ = std::make_unique( + if (creds_->signer_ == nullptr) { + creds_->cred_verification_url_ = absl::StrReplaceAll( + creds_->regional_cred_verification_url_, {{"{region}", region_}}); + creds_->signer_ = std::make_unique( access_key_id_, secret_access_key_, token_, "POST", - cred_verification_url_, region_, "", + creds_->cred_verification_url_, region_, "", std::map(), &error); if (!error.ok()) { - FinishRetrieveSubjectToken( - "", GRPC_ERROR_CREATE_REFERENCING( - "Creating aws request signer failed.", &error, 1)); + AsyncFinish(GRPC_ERROR_CREATE_REFERENCING( + "Creating aws request signer failed.", &error, 1)); return; } } - auto signed_headers = signer_->GetSignedRequestHeaders(); + auto signed_headers = creds_->signer_->GetSignedRequestHeaders(); if (!error.ok()) { - FinishRetrieveSubjectToken( - "", GRPC_ERROR_CREATE_REFERENCING("Invalid getting signed request" - "headers.", - &error, 1)); + AsyncFinish(GRPC_ERROR_CREATE_REFERENCING( + "Invalid getting signed request headers.", &error, 1)); return; } // Construct subject token @@ -501,30 +413,117 @@ void AwsExternalAccountCredentials::BuildSubjectToken() { {"value", Json::FromString(signed_headers["x-amz-security-token"])}})); headers.push_back(Json::FromObject( {{"key", Json::FromString("x-goog-cloud-target-resource")}, - {"value", Json::FromString(audience_)}})); - Json subject_token_json = - Json::FromObject({{"url", Json::FromString(cred_verification_url_)}, - {"method", Json::FromString("POST")}, - {"headers", Json::FromArray(headers)}}); + {"value", Json::FromString(creds_->audience_)}})); + Json subject_token_json = Json::FromObject( + {{"url", Json::FromString(creds_->cred_verification_url_)}, + {"method", Json::FromString("POST")}, + {"headers", Json::FromArray(headers)}}); std::string subject_token = UrlEncode(JsonDump(subject_token_json)); - FinishRetrieveSubjectToken(subject_token, absl::OkStatus()); + AsyncFinish(std::move(subject_token)); } -void AwsExternalAccountCredentials::FinishRetrieveSubjectToken( - std::string subject_token, grpc_error_handle error) { - // Reset context - ctx_ = nullptr; - // Move object state into local variables. - auto cb = cb_; - cb_ = nullptr; - // Invoke the callback. - if (!error.ok()) { - cb("", error); - } else { - cb(subject_token, absl::OkStatus()); +void AwsExternalAccountCredentials::AwsFetchBody::AddMetadataRequestHeaders( + grpc_http_request* request) { + if (!imdsv2_session_token_.empty()) { + CHECK_EQ(request->hdr_count, 0u); + CHECK_EQ(request->hdrs, nullptr); + grpc_http_header* headers = + static_cast(gpr_malloc(sizeof(grpc_http_header))); + headers[0].key = gpr_strdup("x-aws-ec2-metadata-token"); + headers[0].value = gpr_strdup(imdsv2_session_token_.c_str()); + request->hdr_count = 1; + request->hdrs = headers; } } +// +// AwsExternalAccountCredentials +// + +absl::StatusOr> +AwsExternalAccountCredentials::Create( + Options options, std::vector scopes, + std::shared_ptr + event_engine) { + grpc_error_handle error; + auto creds = MakeRefCounted( + std::move(options), std::move(scopes), std::move(event_engine), &error); + if (!error.ok()) return error; + return creds; +} + +AwsExternalAccountCredentials::AwsExternalAccountCredentials( + Options options, std::vector scopes, + std::shared_ptr event_engine, + grpc_error_handle* error) + : ExternalAccountCredentials(options, std::move(scopes), + std::move(event_engine)) { + audience_ = options.audience; + auto it = options.credential_source.object().find("environment_id"); + if (it == options.credential_source.object().end()) { + *error = GRPC_ERROR_CREATE("environment_id field not present."); + return; + } + if (it->second.type() != Json::Type::kString) { + *error = GRPC_ERROR_CREATE("environment_id field must be a string."); + return; + } + if (it->second.string() != kExpectedEnvironmentId) { + *error = GRPC_ERROR_CREATE("environment_id does not match."); + return; + } + it = options.credential_source.object().find("region_url"); + if (it == options.credential_source.object().end()) { + *error = GRPC_ERROR_CREATE("region_url field not present."); + return; + } + if (it->second.type() != Json::Type::kString) { + *error = GRPC_ERROR_CREATE("region_url field must be a string."); + return; + } + region_url_ = it->second.string(); + it = options.credential_source.object().find("url"); + if (it != options.credential_source.object().end() && + it->second.type() == Json::Type::kString) { + url_ = it->second.string(); + } + it = + options.credential_source.object().find("regional_cred_verification_url"); + if (it == options.credential_source.object().end()) { + *error = + GRPC_ERROR_CREATE("regional_cred_verification_url field not present."); + return; + } + if (it->second.type() != Json::Type::kString) { + *error = GRPC_ERROR_CREATE( + "regional_cred_verification_url field must be a string."); + return; + } + regional_cred_verification_url_ = it->second.string(); + it = options.credential_source.object().find("imdsv2_session_token_url"); + if (it != options.credential_source.object().end() && + it->second.type() == Json::Type::kString) { + imdsv2_session_token_url_ = it->second.string(); + } +} + +std::string AwsExternalAccountCredentials::debug_string() { + return absl::StrCat("AwsExternalAccountCredentials{Audience:", audience(), + ")"); +} + +UniqueTypeName AwsExternalAccountCredentials::type() const { + static UniqueTypeName::Factory kFactory("AwsExternalAccountCredentials"); + return kFactory.Create(); +} + +OrphanablePtr +AwsExternalAccountCredentials::RetrieveSubjectToken( + Timestamp deadline, + absl::AnyInvocable)> on_done) { + return MakeOrphanable(std::move(on_done), this, deadline); +} + absl::string_view AwsExternalAccountCredentials::CredentialSourceType() { return "aws"; } diff --git a/src/core/lib/security/credentials/external/aws_external_account_credentials.h b/src/core/lib/security/credentials/external/aws_external_account_credentials.h index c7dc1f166c33a..588001e480b72 100644 --- a/src/core/lib/security/credentials/external/aws_external_account_credentials.h +++ b/src/core/lib/security/credentials/external/aws_external_account_credentials.h @@ -38,46 +38,67 @@ namespace grpc_core { class AwsExternalAccountCredentials final : public ExternalAccountCredentials { public: - static RefCountedPtr Create( + static absl::StatusOr> Create( Options options, std::vector scopes, - grpc_error_handle* error); - - AwsExternalAccountCredentials(Options options, - std::vector scopes, - grpc_error_handle* error); - - private: - bool ShouldUseMetadataServer(); - void RetrieveSubjectToken( - HTTPRequestContext* ctx, const Options& options, - std::function cb) override; - - void RetrieveRegion(); - static void OnRetrieveRegion(void* arg, grpc_error_handle error); - void OnRetrieveRegionInternal(grpc_error_handle error); - - void RetrieveImdsV2SessionToken(); - static void OnRetrieveImdsV2SessionToken(void* arg, grpc_error_handle error); - void OnRetrieveImdsV2SessionTokenInternal(grpc_error_handle error); + std::shared_ptr + event_engine = nullptr); - void RetrieveRoleName(); - static void OnRetrieveRoleName(void* arg, grpc_error_handle error); - void OnRetrieveRoleNameInternal(grpc_error_handle error); + AwsExternalAccountCredentials( + Options options, std::vector scopes, + std::shared_ptr + event_engine, + grpc_error_handle* error); - void RetrieveSigningKeys(); - static void OnRetrieveSigningKeys(void* arg, grpc_error_handle error); - void OnRetrieveSigningKeysInternal(grpc_error_handle error); + std::string debug_string() override; - void BuildSubjectToken(); - void FinishRetrieveSubjectToken(std::string subject_token, - grpc_error_handle error); + UniqueTypeName type() const override; - void AddMetadataRequestHeaders(grpc_http_request* request); + private: + // A FetchBody impl that itself performs a sequence of FetchBody operations. + class AwsFetchBody : public FetchBody { + public: + AwsFetchBody(absl::AnyInvocable)> on_done, + AwsExternalAccountCredentials* creds, Timestamp deadline); + + private: + void Shutdown() override; + + void AsyncFinish(absl::StatusOr result); + bool MaybeFail(absl::Status status) ABSL_EXCLUSIVE_LOCKS_REQUIRED(&mu_); + + void Start(); + void RetrieveImdsV2SessionToken() ABSL_EXCLUSIVE_LOCKS_REQUIRED(&mu_); + void RetrieveRegion() ABSL_EXCLUSIVE_LOCKS_REQUIRED(&mu_); + void RetrieveRoleName() ABSL_EXCLUSIVE_LOCKS_REQUIRED(&mu_); + void RetrieveSigningKeys() ABSL_EXCLUSIVE_LOCKS_REQUIRED(&mu_); + void OnRetrieveSigningKeys(std::string result) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(&mu_); + void BuildSubjectToken() ABSL_EXCLUSIVE_LOCKS_REQUIRED(&mu_); + + void AddMetadataRequestHeaders(grpc_http_request* request); + + AwsExternalAccountCredentials* creds_; + Timestamp deadline_; + + Mutex mu_; + OrphanablePtr fetch_body_ ABSL_GUARDED_BY(&mu_); + + // Information required by request signer + std::string region_; + std::string role_name_; + std::string access_key_id_; + std::string secret_access_key_; + std::string token_; + std::string imdsv2_session_token_; + }; + + OrphanablePtr RetrieveSubjectToken( + Timestamp deadline, + absl::AnyInvocable)> on_done) override; absl::string_view CredentialSourceType() override; std::string audience_; - OrphanablePtr http_request_; // Fields of credential source std::string region_url_; @@ -85,19 +106,9 @@ class AwsExternalAccountCredentials final : public ExternalAccountCredentials { std::string regional_cred_verification_url_; std::string imdsv2_session_token_url_; - // Information required by request signer - std::string region_; - std::string role_name_; - std::string access_key_id_; - std::string secret_access_key_; - std::string token_; - std::string imdsv2_session_token_; - + // These fields are set on the first fetch attempt and cached after that. std::unique_ptr signer_; std::string cred_verification_url_; - - HTTPRequestContext* ctx_ = nullptr; - std::function cb_ = nullptr; }; } // namespace grpc_core diff --git a/src/core/lib/security/credentials/external/external_account_credentials.cc b/src/core/lib/security/credentials/external/external_account_credentials.cc index 7455230613157..100b4ddd8e4d6 100644 --- a/src/core/lib/security/credentials/external/external_account_credentials.cc +++ b/src/core/lib/security/credentials/external/external_account_credentials.cc @@ -45,6 +45,7 @@ #include #include +#include "src/core/lib/event_engine/default_event_engine.h" #include "src/core/lib/gprpp/status_helper.h" #include "src/core/lib/security/credentials/credentials.h" #include "src/core/lib/security/credentials/external/aws_external_account_credentials.h" @@ -69,9 +70,94 @@ namespace grpc_core { +// +// ExternalAccountCredentials::NoOpFetchBody +// + +ExternalAccountCredentials::NoOpFetchBody::NoOpFetchBody( + grpc_event_engine::experimental::EventEngine& event_engine, + absl::AnyInvocable)> on_done, + absl::StatusOr result) + : FetchBody(std::move(on_done)) { + event_engine.Run([self = RefAsSubclass(), + result = std::move(result)]() mutable { + ApplicationCallbackExecCtx application_exec_ctx; + ExecCtx exec_ctx; + self->Finish(std::move(result)); + }); +} + +// +// ExternalAccountCredentials::HttpFetchBody +// + +ExternalAccountCredentials::HttpFetchBody::HttpFetchBody( + absl::FunctionRef(grpc_http_response*, + grpc_closure*)> + start_http_request, + absl::AnyInvocable)> on_done) + : FetchBody(std::move(on_done)) { + GRPC_CLOSURE_INIT(&on_http_response_, OnHttpResponse, this, nullptr); + Ref().release(); // Ref held by HTTP request callback. + http_request_ = start_http_request(&response_, &on_http_response_); +} + +void ExternalAccountCredentials::HttpFetchBody::OnHttpResponse( + void* arg, grpc_error_handle error) { + RefCountedPtr self(static_cast(arg)); + if (!error.ok()) { + self->Finish(std::move(error)); + return; + } + absl::string_view response_body(self->response_.body, + self->response_.body_length); + if (self->response_.status != 200) { + self->Finish(absl::UnavailableError( + absl::StrCat("Call to HTTP server ended with status ", + self->response_.status, " [", response_body, "]"))); + return; + } + self->Finish(std::string(response_body)); +} + +// +// ExternalAccountCredentials::ExternalFetchRequest +// + +// The token fetching flow: +// 1. Retrieve subject token - Subclass's RetrieveSubjectToken() gets called +// and the subject token is received in ExchangeToken(). +// 2. Exchange token - ExchangeToken() gets called with the +// subject token from #1. +// 3. (Optional) Impersonate service account - ImpersonateServiceAccount() gets +// called with the access token of the response from #2. Get an impersonated +// access token in OnImpersonateServiceAccountInternal(). +// 4. Finish token fetch - Return back the response that contains an access +// token in FinishTokenFetch(). +ExternalAccountCredentials::ExternalFetchRequest::ExternalFetchRequest( + ExternalAccountCredentials* creds, Timestamp deadline, + absl::AnyInvocable< + void(absl::StatusOr>)> + on_done) + : creds_(creds), deadline_(deadline), on_done_(std::move(on_done)) { + fetch_body_ = creds_->RetrieveSubjectToken( + deadline, [self = RefAsSubclass()]( + absl::StatusOr result) { + self->ExchangeToken(std::move(result)); + }); +} + +void ExternalAccountCredentials::ExternalFetchRequest::Orphan() { + { + MutexLock lock(&mu_); + fetch_body_.reset(); + } + Unref(); +} + namespace { -std::string UrlEncode(const absl::string_view& s) { +std::string UrlEncode(const absl::string_view s) { const char* hex = "0123456789ABCDEF"; std::string result; result.reserve(s.length()); @@ -89,6 +175,270 @@ std::string UrlEncode(const absl::string_view& s) { return result; } +} // namespace + +void ExternalAccountCredentials::ExternalFetchRequest::ExchangeToken( + absl::StatusOr subject_token) { + MutexLock lock(&mu_); + if (MaybeFailLocked(subject_token.status())) return; + // Parse URI. + absl::StatusOr uri = URI::Parse(options().token_url); + if (!uri.ok()) { + return FinishTokenFetch(GRPC_ERROR_CREATE( + absl::StrFormat("Invalid token url: %s. Error: %s", options().token_url, + uri.status().ToString()))); + } + // Start HTTP request. + fetch_body_ = MakeOrphanable( + [&](grpc_http_response* response, grpc_closure* on_http_response) { + grpc_http_request request; + memset(&request, 0, sizeof(grpc_http_request)); + const bool add_authorization_header = + !options().client_id.empty() && !options().client_secret.empty(); + request.hdr_count = add_authorization_header ? 3 : 2; + auto* headers = static_cast( + gpr_malloc(sizeof(grpc_http_header) * request.hdr_count)); + headers[0].key = gpr_strdup("Content-Type"); + headers[0].value = gpr_strdup("application/x-www-form-urlencoded"); + headers[1].key = gpr_strdup("x-goog-api-client"); + headers[1].value = gpr_strdup(creds_->MetricsHeaderValue().c_str()); + if (add_authorization_header) { + std::string raw_cred = absl::StrFormat("%s:%s", options().client_id, + options().client_secret); + std::string str = + absl::StrFormat("Basic %s", absl::Base64Escape(raw_cred)); + headers[2].key = gpr_strdup("Authorization"); + headers[2].value = gpr_strdup(str.c_str()); + } + request.hdrs = headers; + std::vector body_parts; + body_parts.push_back(absl::StrFormat( + "audience=%s", UrlEncode(options().audience).c_str())); + body_parts.push_back(absl::StrFormat( + "grant_type=%s", + UrlEncode(EXTERNAL_ACCOUNT_CREDENTIALS_GRANT_TYPE).c_str())); + body_parts.push_back(absl::StrFormat( + "requested_token_type=%s", + UrlEncode(EXTERNAL_ACCOUNT_CREDENTIALS_REQUESTED_TOKEN_TYPE) + .c_str())); + body_parts.push_back( + absl::StrFormat("subject_token_type=%s", + UrlEncode(options().subject_token_type).c_str())); + body_parts.push_back(absl::StrFormat( + "subject_token=%s", UrlEncode(*subject_token).c_str())); + std::string scope = GOOGLE_CLOUD_PLATFORM_DEFAULT_SCOPE; + if (options().service_account_impersonation_url.empty()) { + scope = absl::StrJoin(creds_->scopes_, " "); + } + body_parts.push_back( + absl::StrFormat("scope=%s", UrlEncode(scope).c_str())); + Json::Object addtional_options_json_object; + if (options().client_id.empty() && options().client_secret.empty()) { + addtional_options_json_object["userProject"] = + Json::FromString(options().workforce_pool_user_project); + } + Json addtional_options_json = + Json::FromObject(std::move(addtional_options_json_object)); + body_parts.push_back(absl::StrFormat( + "options=%s", UrlEncode(JsonDump(addtional_options_json)).c_str())); + std::string body = absl::StrJoin(body_parts, "&"); + request.body = const_cast(body.c_str()); + request.body_length = body.size(); + RefCountedPtr http_request_creds; + if (uri->scheme() == "http") { + http_request_creds = RefCountedPtr( + grpc_insecure_credentials_create()); + } else { + http_request_creds = CreateHttpRequestSSLCredentials(); + } + auto http_request = HttpRequest::Post( + std::move(*uri), /*args=*/nullptr, pollent(), &request, deadline(), + on_http_response, response, std::move(http_request_creds)); + http_request->Start(); + request.body = nullptr; + grpc_http_request_destroy(&request); + return http_request; + }, + [self = RefAsSubclass()]( + absl::StatusOr result) { + self->MaybeImpersonateServiceAccount(std::move(result)); + }); +} + +void ExternalAccountCredentials::ExternalFetchRequest:: + MaybeImpersonateServiceAccount(absl::StatusOr response_body) { + MutexLock lock(&mu_); + if (MaybeFailLocked(response_body.status())) return; + // If not doing impersonation, response_body contains oauth token. + if (options().service_account_impersonation_url.empty()) { + return FinishTokenFetch(std::move(response_body)); + } + // Do impersonation. + auto json = JsonParse(*response_body); + if (!json.ok()) { + FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrCat( + "Invalid token exchange response: ", json.status().ToString()))); + return; + } + if (json->type() != Json::Type::kObject) { + FinishTokenFetch(GRPC_ERROR_CREATE( + "Invalid token exchange response: JSON type is not object")); + return; + } + auto it = json->object().find("access_token"); + if (it == json->object().end() || it->second.type() != Json::Type::kString) { + FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat( + "Missing or invalid access_token in %s.", *response_body))); + return; + } + absl::string_view access_token = it->second.string(); + absl::StatusOr uri = + URI::Parse(options().service_account_impersonation_url); + if (!uri.ok()) { + FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat( + "Invalid service account impersonation url: %s. Error: %s", + options().service_account_impersonation_url, uri.status().ToString()))); + return; + } + // Start HTTP request. + fetch_body_ = MakeOrphanable( + [&](grpc_http_response* response, grpc_closure* on_http_response) { + grpc_http_request request; + memset(&request, 0, sizeof(grpc_http_request)); + request.hdr_count = 2; + grpc_http_header* headers = static_cast( + gpr_malloc(sizeof(grpc_http_header) * request.hdr_count)); + headers[0].key = gpr_strdup("Content-Type"); + headers[0].value = gpr_strdup("application/x-www-form-urlencoded"); + std::string str = absl::StrFormat("Bearer %s", access_token); + headers[1].key = gpr_strdup("Authorization"); + headers[1].value = gpr_strdup(str.c_str()); + request.hdrs = headers; + std::vector body_members; + std::string scope = absl::StrJoin(creds_->scopes_, " "); + body_members.push_back( + absl::StrFormat("scope=%s", UrlEncode(scope).c_str())); + body_members.push_back(absl::StrFormat( + "lifetime=%ds", + options().service_account_impersonation.token_lifetime_seconds)); + std::string body = absl::StrJoin(body_members, "&"); + request.body = const_cast(body.c_str()); + request.body_length = body.size(); + // TODO(ctiller): Use the callers resource quota. + RefCountedPtr http_request_creds; + if (uri->scheme() == "http") { + http_request_creds = RefCountedPtr( + grpc_insecure_credentials_create()); + } else { + http_request_creds = CreateHttpRequestSSLCredentials(); + } + auto http_request = HttpRequest::Post( + std::move(*uri), nullptr, pollent(), &request, deadline(), + on_http_response, response, std::move(http_request_creds)); + http_request->Start(); + request.body = nullptr; + grpc_http_request_destroy(&request); + return http_request; + }, + [self = RefAsSubclass()]( + absl::StatusOr result) { + self->OnImpersonateServiceAccount(std::move(result)); + }); +} + +void ExternalAccountCredentials::ExternalFetchRequest:: + OnImpersonateServiceAccount(absl::StatusOr response_body) { + MutexLock lock(&mu_); + if (MaybeFailLocked(response_body.status())) return; + auto json = JsonParse(*response_body); + if (!json.ok()) { + FinishTokenFetch(GRPC_ERROR_CREATE( + absl::StrCat("Invalid service account impersonation response: ", + json.status().ToString()))); + return; + } + if (json->type() != Json::Type::kObject) { + FinishTokenFetch( + GRPC_ERROR_CREATE("Invalid service account impersonation response: " + "JSON type is not object")); + return; + } + auto it = json->object().find("accessToken"); + if (it == json->object().end() || it->second.type() != Json::Type::kString) { + FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat( + "Missing or invalid accessToken in %s.", *response_body))); + return; + } + absl::string_view access_token = it->second.string(); + it = json->object().find("expireTime"); + if (it == json->object().end() || it->second.type() != Json::Type::kString) { + FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat( + "Missing or invalid expireTime in %s.", *response_body))); + return; + } + absl::string_view expire_time = it->second.string(); + absl::Time t; + if (!absl::ParseTime(absl::RFC3339_full, expire_time, &t, nullptr)) { + FinishTokenFetch(GRPC_ERROR_CREATE( + "Invalid expire time of service account impersonation response.")); + return; + } + int64_t expire_in = (t - absl::Now()) / absl::Seconds(1); + std::string body = absl::StrFormat( + "{\"access_token\":\"%s\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", + access_token, expire_in); + FinishTokenFetch(std::move(body)); +} + +void ExternalAccountCredentials::ExternalFetchRequest::FinishTokenFetch( + absl::StatusOr response_body) { + absl::StatusOr> result; + if (!response_body.ok()) { + LOG(ERROR) << "Fetch external account credentials access token: " + << response_body.status(); + result = absl::Status(response_body.status().code(), + absl::StrCat("error fetching oauth2 token: ", + response_body.status().message())); + } else { + absl::optional token_value; + Duration token_lifetime; + if (grpc_oauth2_token_fetcher_credentials_parse_server_response_body( + *response_body, &token_value, &token_lifetime) != + GRPC_CREDENTIALS_OK) { + result = GRPC_ERROR_CREATE("Could not parse oauth token"); + } else { + result = MakeRefCounted(std::move(*token_value), + Timestamp::Now() + token_lifetime); + } + } + creds_->event_engine().Run([on_done = std::exchange(on_done_, nullptr), + result = std::move(result)]() mutable { + ApplicationCallbackExecCtx application_exec_ctx; + ExecCtx exec_ctx; + std::exchange(on_done, nullptr)(std::move(result)); + }); +} + +bool ExternalAccountCredentials::ExternalFetchRequest::MaybeFailLocked( + absl::Status status) { + if (!status.ok()) { + FinishTokenFetch(std::move(status)); + return true; + } + if (fetch_body_ == nullptr) { // Will be set by Orphan() on cancellation. + FinishTokenFetch( + absl::CancelledError("external account credentials fetch cancelled")); + return true; + } + return false; +} + +// +// ExternalAccountCredentials +// + +namespace { + // Expression to match: // //iam.googleapis.com/locations/[^/]+/workforcePools/[^/]+/providers/.+ bool MatchWorkforcePoolAudience(absl::string_view audience) { @@ -108,49 +458,41 @@ bool MatchWorkforcePoolAudience(absl::string_view audience) { } // namespace -RefCountedPtr ExternalAccountCredentials::Create( +absl::StatusOr> +ExternalAccountCredentials::Create( const Json& json, std::vector scopes, - grpc_error_handle* error) { - CHECK(error->ok()); + std::shared_ptr + event_engine) { Options options; options.type = GRPC_AUTH_JSON_TYPE_INVALID; if (json.type() != Json::Type::kObject) { - *error = - GRPC_ERROR_CREATE("Invalid json to construct credentials options."); - return nullptr; + return GRPC_ERROR_CREATE("Invalid json to construct credentials options."); } auto it = json.object().find("type"); if (it == json.object().end()) { - *error = GRPC_ERROR_CREATE("type field not present."); - return nullptr; + return GRPC_ERROR_CREATE("type field not present."); } if (it->second.type() != Json::Type::kString) { - *error = GRPC_ERROR_CREATE("type field must be a string."); - return nullptr; + return GRPC_ERROR_CREATE("type field must be a string."); } if (it->second.string() != GRPC_AUTH_JSON_TYPE_EXTERNAL_ACCOUNT) { - *error = GRPC_ERROR_CREATE("Invalid credentials json type."); - return nullptr; + return GRPC_ERROR_CREATE("Invalid credentials json type."); } options.type = GRPC_AUTH_JSON_TYPE_EXTERNAL_ACCOUNT; it = json.object().find("audience"); if (it == json.object().end()) { - *error = GRPC_ERROR_CREATE("audience field not present."); - return nullptr; + return GRPC_ERROR_CREATE("audience field not present."); } if (it->second.type() != Json::Type::kString) { - *error = GRPC_ERROR_CREATE("audience field must be a string."); - return nullptr; + return GRPC_ERROR_CREATE("audience field must be a string."); } options.audience = it->second.string(); it = json.object().find("subject_token_type"); if (it == json.object().end()) { - *error = GRPC_ERROR_CREATE("subject_token_type field not present."); - return nullptr; + return GRPC_ERROR_CREATE("subject_token_type field not present."); } if (it->second.type() != Json::Type::kString) { - *error = GRPC_ERROR_CREATE("subject_token_type field must be a string."); - return nullptr; + return GRPC_ERROR_CREATE("subject_token_type field must be a string."); } options.subject_token_type = it->second.string(); it = json.object().find("service_account_impersonation_url"); @@ -159,12 +501,10 @@ RefCountedPtr ExternalAccountCredentials::Create( } it = json.object().find("token_url"); if (it == json.object().end()) { - *error = GRPC_ERROR_CREATE("token_url field not present."); - return nullptr; + return GRPC_ERROR_CREATE("token_url field not present."); } if (it->second.type() != Json::Type::kString) { - *error = GRPC_ERROR_CREATE("token_url field must be a string."); - return nullptr; + return GRPC_ERROR_CREATE("token_url field must be a string."); } options.token_url = it->second.string(); it = json.object().find("token_info_url"); @@ -173,8 +513,7 @@ RefCountedPtr ExternalAccountCredentials::Create( } it = json.object().find("credential_source"); if (it == json.object().end()) { - *error = GRPC_ERROR_CREATE("credential_source field not present."); - return nullptr; + return GRPC_ERROR_CREATE("credential_source field not present."); } options.credential_source = it->second; it = json.object().find("quota_project_id"); @@ -194,10 +533,9 @@ RefCountedPtr ExternalAccountCredentials::Create( if (MatchWorkforcePoolAudience(options.audience)) { options.workforce_pool_user_project = it->second.string(); } else { - *error = GRPC_ERROR_CREATE( + return GRPC_ERROR_CREATE( "workforce_pool_user_project should not be set for non-workforce " "pool credentials"); - return nullptr; } } it = json.object().find("service_account_impersonation"); @@ -211,53 +549,53 @@ RefCountedPtr ExternalAccountCredentials::Create( if (!absl::SimpleAtoi( service_acc_imp_obj_it->second.string(), &options.service_account_impersonation.token_lifetime_seconds)) { - *error = GRPC_ERROR_CREATE("token_lifetime_seconds must be a number"); - return nullptr; + return GRPC_ERROR_CREATE("token_lifetime_seconds must be a number"); } if (options.service_account_impersonation.token_lifetime_seconds > IMPERSONATED_CRED_MAX_LIFETIME_IN_SECONDS) { - *error = GRPC_ERROR_CREATE( + return GRPC_ERROR_CREATE( absl::StrFormat("token_lifetime_seconds must be less than %ds", IMPERSONATED_CRED_MAX_LIFETIME_IN_SECONDS)); - return nullptr; } if (options.service_account_impersonation.token_lifetime_seconds < IMPERSONATED_CRED_MIN_LIFETIME_IN_SECONDS) { - *error = GRPC_ERROR_CREATE( + return GRPC_ERROR_CREATE( absl::StrFormat("token_lifetime_seconds must be more than %ds", IMPERSONATED_CRED_MIN_LIFETIME_IN_SECONDS)); - return nullptr; } } } RefCountedPtr creds; + grpc_error_handle error; if (options.credential_source.object().find("environment_id") != options.credential_source.object().end()) { creds = MakeRefCounted( - std::move(options), std::move(scopes), error); + std::move(options), std::move(scopes), std::move(event_engine), &error); } else if (options.credential_source.object().find("file") != options.credential_source.object().end()) { creds = MakeRefCounted( - std::move(options), std::move(scopes), error); + std::move(options), std::move(scopes), std::move(event_engine), &error); } else if (options.credential_source.object().find("url") != options.credential_source.object().end()) { creds = MakeRefCounted( - std::move(options), std::move(scopes), error); + std::move(options), std::move(scopes), std::move(event_engine), &error); } else { - *error = GRPC_ERROR_CREATE( + return GRPC_ERROR_CREATE( "Invalid options credential source to create " "ExternalAccountCredentials."); } - if (error->ok()) { - return creds; - } else { - return nullptr; - } + if (!error.ok()) return error; + return creds; } ExternalAccountCredentials::ExternalAccountCredentials( - Options options, std::vector scopes) - : options_(std::move(options)) { + Options options, std::vector scopes, + std::shared_ptr event_engine) + : event_engine_( + event_engine == nullptr + ? grpc_event_engine::experimental::GetDefaultEventEngine() + : std::move(event_engine)), + options_(std::move(options)) { if (scopes.empty()) { scopes.push_back(GOOGLE_CLOUD_PLATFORM_DEFAULT_SCOPE); } @@ -266,12 +604,6 @@ ExternalAccountCredentials::ExternalAccountCredentials( ExternalAccountCredentials::~ExternalAccountCredentials() {} -std::string ExternalAccountCredentials::debug_string() { - return absl::StrFormat("ExternalAccountCredentials{Audience:%s,%s}", - options_.audience, - grpc_oauth2_token_fetcher_credentials::debug_string()); -} - std::string ExternalAccountCredentials::MetricsHeaderValue() { return absl::StrFormat( "gl-cpp/unknown auth/%s google-byoid-sdk source/%s sa-impersonation/%v " @@ -286,300 +618,12 @@ absl::string_view ExternalAccountCredentials::CredentialSourceType() { return "unknown"; } -// The token fetching flow: -// 1. Retrieve subject token - Subclass's RetrieveSubjectToken() gets called -// and the subject token is received in OnRetrieveSubjectTokenInternal(). -// 2. Exchange token - ExchangeToken() gets called with the -// subject token from #1. Receive the response in OnExchangeTokenInternal(). -// 3. (Optional) Impersonate service account - ImpersenateServiceAccount() gets -// called with the access token of the response from #2. Get an impersonated -// access token in OnImpersenateServiceAccountInternal(). -// 4. Finish token fetch - Return back the response that contains an access -// token in FinishTokenFetch(). -// TODO(chuanr): Avoid starting the remaining requests if the channel gets shut -// down. -void ExternalAccountCredentials::fetch_oauth2( - grpc_credentials_metadata_request* metadata_req, - grpc_polling_entity* pollent, grpc_iomgr_cb_func response_cb, - Timestamp deadline) { - CHECK_EQ(ctx_, nullptr); - ctx_ = new HTTPRequestContext(pollent, deadline); - metadata_req_ = metadata_req; - response_cb_ = response_cb; - auto cb = [this](std::string token, grpc_error_handle error) { - OnRetrieveSubjectTokenInternal(token, error); - }; - RetrieveSubjectToken(ctx_, options_, cb); -} - -void ExternalAccountCredentials::OnRetrieveSubjectTokenInternal( - absl::string_view subject_token, grpc_error_handle error) { - if (!error.ok()) { - FinishTokenFetch(error); - } else { - ExchangeToken(subject_token); - } -} - -void ExternalAccountCredentials::ExchangeToken( - absl::string_view subject_token) { - absl::StatusOr uri = URI::Parse(options_.token_url); - if (!uri.ok()) { - FinishTokenFetch(GRPC_ERROR_CREATE( - absl::StrFormat("Invalid token url: %s. Error: %s", options_.token_url, - uri.status().ToString()))); - return; - } - grpc_http_request request; - memset(&request, 0, sizeof(grpc_http_request)); - const bool add_authorization_header = - !options_.client_id.empty() && !options_.client_secret.empty(); - request.hdr_count = add_authorization_header ? 3 : 2; - auto* headers = static_cast( - gpr_malloc(sizeof(grpc_http_header) * request.hdr_count)); - headers[0].key = gpr_strdup("Content-Type"); - headers[0].value = gpr_strdup("application/x-www-form-urlencoded"); - headers[1].key = gpr_strdup("x-goog-api-client"); - headers[1].value = gpr_strdup(MetricsHeaderValue().c_str()); - if (add_authorization_header) { - std::string raw_cred = - absl::StrFormat("%s:%s", options_.client_id, options_.client_secret); - std::string str = absl::StrFormat("Basic %s", absl::Base64Escape(raw_cred)); - headers[2].key = gpr_strdup("Authorization"); - headers[2].value = gpr_strdup(str.c_str()); - } - request.hdrs = headers; - std::vector body_parts; - body_parts.push_back( - absl::StrFormat("audience=%s", UrlEncode(options_.audience).c_str())); - body_parts.push_back(absl::StrFormat( - "grant_type=%s", - UrlEncode(EXTERNAL_ACCOUNT_CREDENTIALS_GRANT_TYPE).c_str())); - body_parts.push_back(absl::StrFormat( - "requested_token_type=%s", - UrlEncode(EXTERNAL_ACCOUNT_CREDENTIALS_REQUESTED_TOKEN_TYPE).c_str())); - body_parts.push_back(absl::StrFormat( - "subject_token_type=%s", UrlEncode(options_.subject_token_type).c_str())); - body_parts.push_back( - absl::StrFormat("subject_token=%s", UrlEncode(subject_token).c_str())); - std::string scope = GOOGLE_CLOUD_PLATFORM_DEFAULT_SCOPE; - if (options_.service_account_impersonation_url.empty()) { - scope = absl::StrJoin(scopes_, " "); - } - body_parts.push_back(absl::StrFormat("scope=%s", UrlEncode(scope).c_str())); - Json::Object addtional_options_json_object; - if (options_.client_id.empty() && options_.client_secret.empty()) { - addtional_options_json_object["userProject"] = - Json::FromString(options_.workforce_pool_user_project); - } - Json addtional_options_json = - Json::FromObject(std::move(addtional_options_json_object)); - body_parts.push_back(absl::StrFormat( - "options=%s", UrlEncode(JsonDump(addtional_options_json)).c_str())); - std::string body = absl::StrJoin(body_parts, "&"); - request.body = const_cast(body.c_str()); - request.body_length = body.size(); - grpc_http_response_destroy(&ctx_->response); - ctx_->response = {}; - GRPC_CLOSURE_INIT(&ctx_->closure, OnExchangeToken, this, nullptr); - CHECK(http_request_ == nullptr); - RefCountedPtr http_request_creds; - if (uri->scheme() == "http") { - http_request_creds = RefCountedPtr( - grpc_insecure_credentials_create()); - } else { - http_request_creds = CreateHttpRequestSSLCredentials(); - } - http_request_ = - HttpRequest::Post(std::move(*uri), nullptr /* channel args */, - ctx_->pollent, &request, ctx_->deadline, &ctx_->closure, - &ctx_->response, std::move(http_request_creds)); - http_request_->Start(); - request.body = nullptr; - grpc_http_request_destroy(&request); -} - -void ExternalAccountCredentials::OnExchangeToken(void* arg, - grpc_error_handle error) { - ExternalAccountCredentials* self = - static_cast(arg); - self->OnExchangeTokenInternal(error); -} - -void ExternalAccountCredentials::OnExchangeTokenInternal( - grpc_error_handle error) { - http_request_.reset(); - if (!error.ok()) { - FinishTokenFetch(error); - } else { - if (options_.service_account_impersonation_url.empty()) { - metadata_req_->response = ctx_->response; - metadata_req_->response.body = gpr_strdup( - std::string(ctx_->response.body, ctx_->response.body_length).c_str()); - metadata_req_->response.hdrs = static_cast( - gpr_malloc(sizeof(grpc_http_header) * ctx_->response.hdr_count)); - for (size_t i = 0; i < ctx_->response.hdr_count; i++) { - metadata_req_->response.hdrs[i].key = - gpr_strdup(ctx_->response.hdrs[i].key); - metadata_req_->response.hdrs[i].value = - gpr_strdup(ctx_->response.hdrs[i].value); - } - FinishTokenFetch(absl::OkStatus()); - } else { - ImpersenateServiceAccount(); - } - } -} - -void ExternalAccountCredentials::ImpersenateServiceAccount() { - absl::string_view response_body(ctx_->response.body, - ctx_->response.body_length); - auto json = JsonParse(response_body); - if (!json.ok()) { - FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrCat( - "Invalid token exchange response: ", json.status().ToString()))); - return; - } - if (json->type() != Json::Type::kObject) { - FinishTokenFetch(GRPC_ERROR_CREATE( - "Invalid token exchange response: JSON type is not object")); - return; - } - auto it = json->object().find("access_token"); - if (it == json->object().end() || it->second.type() != Json::Type::kString) { - FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat( - "Missing or invalid access_token in %s.", response_body))); - return; - } - std::string access_token = it->second.string(); - absl::StatusOr uri = - URI::Parse(options_.service_account_impersonation_url); - if (!uri.ok()) { - FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat( - "Invalid service account impersonation url: %s. Error: %s", - options_.service_account_impersonation_url, uri.status().ToString()))); - return; - } - grpc_http_request request; - memset(&request, 0, sizeof(grpc_http_request)); - request.hdr_count = 2; - grpc_http_header* headers = static_cast( - gpr_malloc(sizeof(grpc_http_header) * request.hdr_count)); - headers[0].key = gpr_strdup("Content-Type"); - headers[0].value = gpr_strdup("application/x-www-form-urlencoded"); - std::string str = absl::StrFormat("Bearer %s", access_token); - headers[1].key = gpr_strdup("Authorization"); - headers[1].value = gpr_strdup(str.c_str()); - request.hdrs = headers; - std::vector body_members; - std::string scope = absl::StrJoin(scopes_, " "); - body_members.push_back(absl::StrFormat("scope=%s", UrlEncode(scope).c_str())); - body_members.push_back(absl::StrFormat( - "lifetime=%ds", - options_.service_account_impersonation.token_lifetime_seconds)); - std::string body = absl::StrJoin(body_members, "&"); - request.body = const_cast(body.c_str()); - request.body_length = body.size(); - grpc_http_response_destroy(&ctx_->response); - ctx_->response = {}; - GRPC_CLOSURE_INIT(&ctx_->closure, OnImpersenateServiceAccount, this, nullptr); - // TODO(ctiller): Use the callers resource quota. - CHECK(http_request_ == nullptr); - RefCountedPtr http_request_creds; - if (uri->scheme() == "http") { - http_request_creds = RefCountedPtr( - grpc_insecure_credentials_create()); - } else { - http_request_creds = CreateHttpRequestSSLCredentials(); - } - http_request_ = HttpRequest::Post( - std::move(*uri), nullptr, ctx_->pollent, &request, ctx_->deadline, - &ctx_->closure, &ctx_->response, std::move(http_request_creds)); - http_request_->Start(); - request.body = nullptr; - grpc_http_request_destroy(&request); -} - -void ExternalAccountCredentials::OnImpersenateServiceAccount( - void* arg, grpc_error_handle error) { - ExternalAccountCredentials* self = - static_cast(arg); - self->OnImpersenateServiceAccountInternal(error); -} - -void ExternalAccountCredentials::OnImpersenateServiceAccountInternal( - grpc_error_handle error) { - http_request_.reset(); - if (!error.ok()) { - FinishTokenFetch(error); - return; - } - absl::string_view response_body(ctx_->response.body, - ctx_->response.body_length); - auto json = JsonParse(response_body); - if (!json.ok()) { - FinishTokenFetch(GRPC_ERROR_CREATE( - absl::StrCat("Invalid service account impersonation response: ", - json.status().ToString()))); - return; - } - if (json->type() != Json::Type::kObject) { - FinishTokenFetch( - GRPC_ERROR_CREATE("Invalid service account impersonation response: " - "JSON type is not object")); - return; - } - auto it = json->object().find("accessToken"); - if (it == json->object().end() || it->second.type() != Json::Type::kString) { - FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat( - "Missing or invalid accessToken in %s.", response_body))); - return; - } - std::string access_token = it->second.string(); - it = json->object().find("expireTime"); - if (it == json->object().end() || it->second.type() != Json::Type::kString) { - FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat( - "Missing or invalid expireTime in %s.", response_body))); - return; - } - std::string expire_time = it->second.string(); - absl::Time t; - if (!absl::ParseTime(absl::RFC3339_full, expire_time, &t, nullptr)) { - FinishTokenFetch(GRPC_ERROR_CREATE( - "Invalid expire time of service account impersonation response.")); - return; - } - int64_t expire_in = (t - absl::Now()) / absl::Seconds(1); - std::string body = absl::StrFormat( - "{\"access_token\":\"%s\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", - access_token, expire_in); - metadata_req_->response = ctx_->response; - metadata_req_->response.body = gpr_strdup(body.c_str()); - metadata_req_->response.body_length = body.length(); - metadata_req_->response.hdrs = static_cast( - gpr_malloc(sizeof(grpc_http_header) * ctx_->response.hdr_count)); - for (size_t i = 0; i < ctx_->response.hdr_count; i++) { - metadata_req_->response.hdrs[i].key = - gpr_strdup(ctx_->response.hdrs[i].key); - metadata_req_->response.hdrs[i].value = - gpr_strdup(ctx_->response.hdrs[i].value); - } - FinishTokenFetch(absl::OkStatus()); -} - -void ExternalAccountCredentials::FinishTokenFetch(grpc_error_handle error) { - GRPC_LOG_IF_ERROR("Fetch external account credentials access token", error); - // Move object state into local variables. - auto* cb = response_cb_; - response_cb_ = nullptr; - auto* metadata_req = metadata_req_; - metadata_req_ = nullptr; - auto* ctx = ctx_; - ctx_ = nullptr; - // Invoke the callback. - cb(metadata_req, error); - // Delete context. - delete ctx; +OrphanablePtr +ExternalAccountCredentials::FetchToken( + Timestamp deadline, + absl::AnyInvocable>)> on_done) { + return MakeOrphanable(this, deadline, + std::move(on_done)); } } // namespace grpc_core @@ -593,14 +637,12 @@ grpc_call_credentials* grpc_external_account_credentials_create( return nullptr; } std::vector scopes = absl::StrSplit(scopes_string, ','); - grpc_error_handle error; - auto creds = grpc_core::ExternalAccountCredentials::Create( - *json, std::move(scopes), &error) - .release(); - if (!error.ok()) { + auto creds = + grpc_core::ExternalAccountCredentials::Create(*json, std::move(scopes)); + if (!creds.ok()) { LOG(ERROR) << "External account credentials creation failed. Error: " - << grpc_core::StatusToString(error); + << grpc_core::StatusToString(creds.status()); return nullptr; } - return creds; + return creds->release(); } diff --git a/src/core/lib/security/credentials/external/external_account_credentials.h b/src/core/lib/security/credentials/external/external_account_credentials.h index f0023ab878ec9..0617c3e6f0717 100644 --- a/src/core/lib/security/credentials/external/external_account_credentials.h +++ b/src/core/lib/security/credentials/external/external_account_credentials.h @@ -20,11 +20,13 @@ #include #include +#include #include #include #include "absl/strings/string_view.h" +#include #include #include "src/core/lib/gprpp/orphanable.h" @@ -34,6 +36,7 @@ #include "src/core/lib/iomgr/error.h" #include "src/core/lib/iomgr/polling_entity.h" #include "src/core/lib/security/credentials/oauth2/oauth2_credentials.h" +#include "src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h" #include "src/core/util/http_client/httpcli.h" #include "src/core/util/http_client/parser.h" #include "src/core/util/json/json.h" @@ -44,8 +47,7 @@ namespace grpc_core { // exchanging external account credentials for GCP access token to authorize // requests to GCP APIs. The specific logic of retrieving subject token is // implemented in subclasses. -class ExternalAccountCredentials - : public grpc_oauth2_token_fetcher_credentials { +class ExternalAccountCredentials : public TokenFetcherCredentials { public: struct ServiceAccountImpersonation { int32_t token_lifetime_seconds; @@ -66,72 +68,145 @@ class ExternalAccountCredentials std::string workforce_pool_user_project; }; - static RefCountedPtr Create( + static absl::StatusOr> Create( const Json& json, std::vector scopes, - grpc_error_handle* error); + std::shared_ptr + event_engine = nullptr); - ExternalAccountCredentials(Options options, std::vector scopes); + ExternalAccountCredentials( + Options options, std::vector scopes, + std::shared_ptr + event_engine = nullptr); ~ExternalAccountCredentials() override; - std::string debug_string() override; protected: - // This is a helper struct to pass information between multiple callback based - // asynchronous calls. - struct HTTPRequestContext { - HTTPRequestContext(grpc_polling_entity* pollent, Timestamp deadline) - : pollent(pollent), deadline(deadline) {} - ~HTTPRequestContext() { grpc_http_response_destroy(&response); } - - // Contextual parameters passed from - // grpc_oauth2_token_fetcher_credentials::fetch_oauth2(). - grpc_polling_entity* pollent; - Timestamp deadline; - - // Reusable token fetch http response and closure. - grpc_closure closure; - grpc_http_response response; + // A base class for a cancellable fetch operation. + class FetchBody : public InternallyRefCounted { + public: + explicit FetchBody( + absl::AnyInvocable)> on_done) + : on_done_(std::move(on_done)) {} + + void Orphan() override { + Shutdown(); + Unref(); + } + + protected: + // The subclass must call this when the fetch is complete, even if + // cancelled. + void Finish(absl::StatusOr result) { + std::exchange(on_done_, nullptr)(std::move(result)); + } + + private: + virtual void Shutdown() = 0; + + absl::AnyInvocable)> on_done_; }; - // Subclasses of base external account credentials need to override this - // method to implement the specific subject token retrieval logic. - // Once the subject token is ready, subclasses need to invoke - // the callback function (cb) to pass the subject token (or error) - // back. - virtual void RetrieveSubjectToken( - HTTPRequestContext* ctx, const Options& options, - std::function cb) = 0; + // A simple no-op implementation, used for async execution of the + // on_done callback. + class NoOpFetchBody final : public FetchBody { + public: + NoOpFetchBody(grpc_event_engine::experimental::EventEngine& event_engine, + absl::AnyInvocable)> on_done, + absl::StatusOr result); - virtual absl::string_view CredentialSourceType(); + private: + void Shutdown() override {} + }; - std::string MetricsHeaderValue(); + // An implementation for HTTP requests. + class HttpFetchBody final : public FetchBody { + public: + HttpFetchBody( + absl::FunctionRef(grpc_http_response*, + grpc_closure*)> + start_http_request, + absl::AnyInvocable)> on_done); - private: - // This method implements the common token fetch logic and it will be called - // when grpc_oauth2_token_fetcher_credentials request a new access token. - void fetch_oauth2(grpc_credentials_metadata_request* req, - grpc_polling_entity* pollent, grpc_iomgr_cb_func cb, - Timestamp deadline) override; + ~HttpFetchBody() override { grpc_http_response_destroy(&response_); } + + private: + void Shutdown() override { http_request_.reset(); } + + static void OnHttpResponse(void* arg, grpc_error_handle error); + + OrphanablePtr http_request_; + grpc_http_response response_; + grpc_closure on_http_response_; + }; - void OnRetrieveSubjectTokenInternal(absl::string_view subject_token, - grpc_error_handle error); + // An implementation of TokenFetcherCredentials::FetchRequest that + // executes a series of FetchBody operations to ultimately get to a + // token result. + class ExternalFetchRequest : public FetchRequest { + public: + ExternalFetchRequest( + ExternalAccountCredentials* creds, Timestamp deadline, + absl::AnyInvocable< + void(absl::StatusOr>)> + on_done); + + void Orphan() override; + + protected: + Timestamp deadline() const { return deadline_; } + grpc_polling_entity* pollent() const { return creds_->pollent(); } + const Options& options() const { return creds_->options_; } + + private: + void ExchangeToken(absl::StatusOr subject_token); + void MaybeImpersonateServiceAccount( + absl::StatusOr response_body); + void OnImpersonateServiceAccount(absl::StatusOr response_body); + + void FinishTokenFetch(absl::StatusOr response_body); + + // If status is non-OK or we've been shut down, calls FinishTokenFetch() + // and returns true. + bool MaybeFailLocked(absl::Status status) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(&mu_); + + ExternalAccountCredentials* creds_; + Timestamp deadline_; + absl::AnyInvocable>)> + on_done_; + + Mutex mu_; + OrphanablePtr fetch_body_ ABSL_GUARDED_BY(&mu_); + }; - void ExchangeToken(absl::string_view subject_token); - static void OnExchangeToken(void* arg, grpc_error_handle error); - void OnExchangeTokenInternal(grpc_error_handle error); + virtual absl::string_view CredentialSourceType(); - void ImpersenateServiceAccount(); - static void OnImpersenateServiceAccount(void* arg, grpc_error_handle error); - void OnImpersenateServiceAccountInternal(grpc_error_handle error); + std::string MetricsHeaderValue(); - void FinishTokenFetch(grpc_error_handle error); + absl::string_view audience() const { return options_.audience; } + grpc_event_engine::experimental::EventEngine& event_engine() const { + return *event_engine_; + } + + private: + OrphanablePtr FetchToken( + Timestamp deadline, + absl::AnyInvocable>)> on_done) + final; + + // Subclasses of ExternalAccountCredentials need to override this + // method to implement the specific-subject token retrieval logic. + // The caller will save the resulting FetchBody object, which will + // be orphaned upon cancellation. The FetchBody object must + // eventually invoke on_done. + virtual OrphanablePtr RetrieveSubjectToken( + Timestamp deadline, + absl::AnyInvocable)> on_done) = 0; + + std::shared_ptr event_engine_; Options options_; std::vector scopes_; - - OrphanablePtr http_request_; - HTTPRequestContext* ctx_ = nullptr; - grpc_credentials_metadata_request* metadata_req_ = nullptr; - grpc_iomgr_cb_func response_cb_ = nullptr; }; } // namespace grpc_core diff --git a/src/core/lib/security/credentials/external/file_external_account_credentials.cc b/src/core/lib/security/credentials/external/file_external_account_credentials.cc index 0bbef7b8a07db..cad9b7f7ee43a 100644 --- a/src/core/lib/security/credentials/external/file_external_account_credentials.cc +++ b/src/core/lib/security/credentials/external/file_external_account_credentials.cc @@ -26,6 +26,7 @@ #include #include +#include "src/core/lib/event_engine/default_event_engine.h" #include "src/core/lib/gprpp/load_file.h" #include "src/core/lib/slice/slice.h" #include "src/core/lib/slice/slice_internal.h" @@ -34,22 +35,78 @@ namespace grpc_core { -RefCountedPtr -FileExternalAccountCredentials::Create(Options options, - std::vector scopes, - grpc_error_handle* error) { - auto creds = MakeRefCounted( - std::move(options), std::move(scopes), error); - if (error->ok()) { - return creds; - } else { - return nullptr; +// +// FileExternalAccountCredentials::FileFetchBody +// + +FileExternalAccountCredentials::FileFetchBody::FileFetchBody( + absl::AnyInvocable)> on_done, + FileExternalAccountCredentials* creds) + : FetchBody(std::move(on_done)), creds_(creds) { + // Start work asynchronously, since we can't invoke the callback + // synchronously without causing a deadlock. + creds->event_engine().Run([self = RefAsSubclass()]() mutable { + ApplicationCallbackExecCtx application_exec_ctx; + ExecCtx exec_ctx; + self->ReadFile(); + self.reset(); + }); +} + +void FileExternalAccountCredentials::FileFetchBody::ReadFile() { + // To retrieve the subject token, we read the file every time we make a + // request because it may have changed since the last request. + auto content_slice = LoadFile(creds_->file_, /*add_null_terminator=*/false); + if (!content_slice.ok()) { + Finish(content_slice.status()); + return; } + absl::string_view content = content_slice->as_string_view(); + if (creds_->format_type_ == "json") { + auto content_json = JsonParse(content); + if (!content_json.ok() || content_json->type() != Json::Type::kObject) { + Finish(GRPC_ERROR_CREATE( + "The content of the file is not a valid json object.")); + return; + } + auto content_it = + content_json->object().find(creds_->format_subject_token_field_name_); + if (content_it == content_json->object().end()) { + Finish(GRPC_ERROR_CREATE("Subject token field not present.")); + return; + } + if (content_it->second.type() != Json::Type::kString) { + Finish(GRPC_ERROR_CREATE("Subject token field must be a string.")); + return; + } + Finish(content_it->second.string()); + return; + } + Finish(std::string(content)); +} + +// +// FileExternalAccountCredentials +// + +absl::StatusOr> +FileExternalAccountCredentials::Create( + Options options, std::vector scopes, + std::shared_ptr + event_engine) { + grpc_error_handle error; + auto creds = MakeRefCounted( + std::move(options), std::move(scopes), std::move(event_engine), &error); + if (!error.ok()) return error; + return creds; } FileExternalAccountCredentials::FileExternalAccountCredentials( - Options options, std::vector scopes, grpc_error_handle* error) - : ExternalAccountCredentials(options, std::move(scopes)) { + Options options, std::vector scopes, + std::shared_ptr event_engine, + grpc_error_handle* error) + : ExternalAccountCredentials(options, std::move(scopes), + std::move(event_engine)) { auto it = options.credential_source.object().find("file"); if (it == options.credential_source.object().end()) { *error = GRPC_ERROR_CREATE("file field not present."); @@ -96,38 +153,21 @@ FileExternalAccountCredentials::FileExternalAccountCredentials( } } -void FileExternalAccountCredentials::RetrieveSubjectToken( - HTTPRequestContext* /*ctx*/, const Options& /*options*/, - std::function cb) { - // To retrieve the subject token, we read the file every time we make a - // request because it may have changed since the last request. - auto content_slice = LoadFile(file_, /*add_null_terminator=*/false); - if (!content_slice.ok()) { - cb("", content_slice.status()); - return; - } - absl::string_view content = content_slice->as_string_view(); - if (format_type_ == "json") { - auto content_json = JsonParse(content); - if (!content_json.ok() || content_json->type() != Json::Type::kObject) { - cb("", GRPC_ERROR_CREATE( - "The content of the file is not a valid json object.")); - return; - } - auto content_it = - content_json->object().find(format_subject_token_field_name_); - if (content_it == content_json->object().end()) { - cb("", GRPC_ERROR_CREATE("Subject token field not present.")); - return; - } - if (content_it->second.type() != Json::Type::kString) { - cb("", GRPC_ERROR_CREATE("Subject token field must be a string.")); - return; - } - cb(content_it->second.string(), absl::OkStatus()); - return; - } - cb(std::string(content), absl::OkStatus()); +std::string FileExternalAccountCredentials::debug_string() { + return absl::StrCat("FileExternalAccountCredentials{Audience:", audience(), + ")"); +} + +UniqueTypeName FileExternalAccountCredentials::type() const { + static UniqueTypeName::Factory kFactory("FileExternalAccountCredentials"); + return kFactory.Create(); +} + +OrphanablePtr +FileExternalAccountCredentials::RetrieveSubjectToken( + Timestamp /*deadline*/, + absl::AnyInvocable)> on_done) { + return MakeOrphanable(std::move(on_done), this); } absl::string_view FileExternalAccountCredentials::CredentialSourceType() { diff --git a/src/core/lib/security/credentials/external/file_external_account_credentials.h b/src/core/lib/security/credentials/external/file_external_account_credentials.h index 8436dea8e7d86..8ed7b14653713 100644 --- a/src/core/lib/security/credentials/external/file_external_account_credentials.h +++ b/src/core/lib/security/credentials/external/file_external_account_credentials.h @@ -33,18 +33,38 @@ namespace grpc_core { class FileExternalAccountCredentials final : public ExternalAccountCredentials { public: - static RefCountedPtr Create( + static absl::StatusOr> Create( Options options, std::vector scopes, + std::shared_ptr + event_engine = nullptr); + + FileExternalAccountCredentials( + Options options, std::vector scopes, + std::shared_ptr + event_engine, grpc_error_handle* error); - FileExternalAccountCredentials(Options options, - std::vector scopes, - grpc_error_handle* error); + std::string debug_string() override; + + UniqueTypeName type() const override; private: - void RetrieveSubjectToken( - HTTPRequestContext* ctx, const Options& options, - std::function cb) override; + class FileFetchBody final : public FetchBody { + public: + FileFetchBody(absl::AnyInvocable)> on_done, + FileExternalAccountCredentials* creds); + + private: + void Shutdown() override {} + + void ReadFile(); + + FileExternalAccountCredentials* creds_; + }; + + OrphanablePtr RetrieveSubjectToken( + Timestamp deadline, + absl::AnyInvocable)> on_done) override; absl::string_view CredentialSourceType() override; diff --git a/src/core/lib/security/credentials/external/url_external_account_credentials.cc b/src/core/lib/security/credentials/external/url_external_account_credentials.cc index faa6e19a108e0..bcea5eadc09e3 100644 --- a/src/core/lib/security/credentials/external/url_external_account_credentials.cc +++ b/src/core/lib/security/credentials/external/url_external_account_credentials.cc @@ -46,22 +46,24 @@ namespace grpc_core { -RefCountedPtr -UrlExternalAccountCredentials::Create(Options options, - std::vector scopes, - grpc_error_handle* error) { +absl::StatusOr> +UrlExternalAccountCredentials::Create( + Options options, std::vector scopes, + std::shared_ptr + event_engine) { + grpc_error_handle error; auto creds = MakeRefCounted( - std::move(options), std::move(scopes), error); - if (error->ok()) { - return creds; - } else { - return nullptr; - } + std::move(options), std::move(scopes), std::move(event_engine), &error); + if (!error.ok()) return error; + return creds; } UrlExternalAccountCredentials::UrlExternalAccountCredentials( - Options options, std::vector scopes, grpc_error_handle* error) - : ExternalAccountCredentials(options, std::move(scopes)) { + Options options, std::vector scopes, + std::shared_ptr event_engine, + grpc_error_handle* error) + : ExternalAccountCredentials(options, std::move(scopes), + std::move(event_engine)) { auto it = options.credential_source.object().find("url"); if (it == options.credential_source.object().end()) { *error = GRPC_ERROR_CREATE("url field not present."); @@ -130,114 +132,88 @@ UrlExternalAccountCredentials::UrlExternalAccountCredentials( } } -void UrlExternalAccountCredentials::RetrieveSubjectToken( - HTTPRequestContext* ctx, const Options& /*options*/, - std::function cb) { - if (ctx == nullptr) { - FinishRetrieveSubjectToken( - "", - GRPC_ERROR_CREATE( - "Missing HTTPRequestContext to start subject token retrieval.")); - return; - } - auto url_for_request = - URI::Create(url_.scheme(), url_.authority(), url_full_path_, - {} /* query params */, "" /* fragment */); - if (!url_for_request.ok()) { - FinishRetrieveSubjectToken( - "", absl_status_to_grpc_error(url_for_request.status())); - return; - } - ctx_ = ctx; - cb_ = cb; - grpc_http_request request; - memset(&request, 0, sizeof(grpc_http_request)); - request.path = gpr_strdup(url_full_path_.c_str()); - grpc_http_header* headers = nullptr; - request.hdr_count = headers_.size(); - headers = static_cast( - gpr_malloc(sizeof(grpc_http_header) * request.hdr_count)); - int i = 0; - for (auto const& header : headers_) { - headers[i].key = gpr_strdup(header.first.c_str()); - headers[i].value = gpr_strdup(header.second.c_str()); - ++i; - } - request.hdrs = headers; - grpc_http_response_destroy(&ctx_->response); - ctx_->response = {}; - GRPC_CLOSURE_INIT(&ctx_->closure, OnRetrieveSubjectToken, this, nullptr); - CHECK(http_request_ == nullptr); - RefCountedPtr http_request_creds; - if (url_.scheme() == "http") { - http_request_creds = RefCountedPtr( - grpc_insecure_credentials_create()); - } else { - http_request_creds = CreateHttpRequestSSLCredentials(); - } - http_request_ = - HttpRequest::Get(std::move(*url_for_request), nullptr /* channel args */, - ctx_->pollent, &request, ctx_->deadline, &ctx_->closure, - &ctx_->response, std::move(http_request_creds)); - http_request_->Start(); - grpc_http_request_destroy(&request); +std::string UrlExternalAccountCredentials::debug_string() { + return absl::StrCat("UrlExternalAccountCredentials{Audience:", audience(), + ")"); } -void UrlExternalAccountCredentials::OnRetrieveSubjectToken( - void* arg, grpc_error_handle error) { - UrlExternalAccountCredentials* self = - static_cast(arg); - self->OnRetrieveSubjectTokenInternal(error); +UniqueTypeName UrlExternalAccountCredentials::type() const { + static UniqueTypeName::Factory kFactory("UrlExternalAccountCredentials"); + return kFactory.Create(); } -void UrlExternalAccountCredentials::OnRetrieveSubjectTokenInternal( - grpc_error_handle error) { - http_request_.reset(); - if (!error.ok()) { - FinishRetrieveSubjectToken("", error); - return; - } - absl::string_view response_body(ctx_->response.body, - ctx_->response.body_length); - if (format_type_ == "json") { - auto response_json = JsonParse(response_body); - if (!response_json.ok() || response_json->type() != Json::Type::kObject) { - FinishRetrieveSubjectToken( - "", GRPC_ERROR_CREATE( - "The format of response is not a valid json object.")); - return; - } - auto response_it = - response_json->object().find(format_subject_token_field_name_); - if (response_it == response_json->object().end()) { - FinishRetrieveSubjectToken( - "", GRPC_ERROR_CREATE("Subject token field not present.")); - return; - } - if (response_it->second.type() != Json::Type::kString) { - FinishRetrieveSubjectToken( - "", GRPC_ERROR_CREATE("Subject token field must be a string.")); - return; - } - FinishRetrieveSubjectToken(response_it->second.string(), error); - return; - } - FinishRetrieveSubjectToken(std::string(response_body), absl::OkStatus()); -} - -void UrlExternalAccountCredentials::FinishRetrieveSubjectToken( - std::string subject_token, grpc_error_handle error) { - // Reset context - ctx_ = nullptr; - // Move object state into local variables. - auto cb = cb_; - cb_ = nullptr; - // Invoke the callback. - if (!error.ok()) { - cb("", error); - } else { - cb(subject_token, absl::OkStatus()); +OrphanablePtr +UrlExternalAccountCredentials::RetrieveSubjectToken( + Timestamp deadline, + absl::AnyInvocable)> on_done) { + auto url_for_request = + URI::Create(url_.scheme(), url_.authority(), url_full_path_, + {} /* query params */, "" /* fragment */); + if (!url_for_request.ok()) { + return MakeOrphanable( + event_engine(), std::move(on_done), + absl_status_to_grpc_error(url_for_request.status())); } + return MakeOrphanable( + [&](grpc_http_response* response, grpc_closure* on_http_response) { + grpc_http_request request; + memset(&request, 0, sizeof(grpc_http_request)); + request.path = gpr_strdup(url_full_path_.c_str()); + grpc_http_header* headers = nullptr; + request.hdr_count = headers_.size(); + headers = static_cast( + gpr_malloc(sizeof(grpc_http_header) * request.hdr_count)); + int i = 0; + for (auto const& header : headers_) { + headers[i].key = gpr_strdup(header.first.c_str()); + headers[i].value = gpr_strdup(header.second.c_str()); + ++i; + } + request.hdrs = headers; + RefCountedPtr http_request_creds; + if (url_.scheme() == "http") { + http_request_creds = RefCountedPtr( + grpc_insecure_credentials_create()); + } else { + http_request_creds = CreateHttpRequestSSLCredentials(); + } + auto http_request = + HttpRequest::Get(std::move(*url_for_request), /*args=*/nullptr, + pollent(), &request, deadline, on_http_response, + response, std::move(http_request_creds)); + http_request->Start(); + grpc_http_request_destroy(&request); + return http_request; + }, + [this, on_done = std::move(on_done)]( + absl::StatusOr response_body) mutable { + if (!response_body.ok()) { + on_done(std::move(response_body)); + return; + } + if (format_type_ == "json") { + auto response_json = JsonParse(*response_body); + if (!response_json.ok() || + response_json->type() != Json::Type::kObject) { + on_done(GRPC_ERROR_CREATE( + "The format of response is not a valid json object.")); + return; + } + auto response_it = + response_json->object().find(format_subject_token_field_name_); + if (response_it == response_json->object().end()) { + on_done(GRPC_ERROR_CREATE("Subject token field not present.")); + return; + } + if (response_it->second.type() != Json::Type::kString) { + on_done(GRPC_ERROR_CREATE("Subject token field must be a string.")); + return; + } + on_done(response_it->second.string()); + return; + } + on_done(std::move(response_body)); + }); } absl::string_view UrlExternalAccountCredentials::CredentialSourceType() { diff --git a/src/core/lib/security/credentials/external/url_external_account_credentials.h b/src/core/lib/security/credentials/external/url_external_account_credentials.h index 209e652a15c8e..65dcae9176e11 100644 --- a/src/core/lib/security/credentials/external/url_external_account_credentials.h +++ b/src/core/lib/security/credentials/external/url_external_account_credentials.h @@ -37,37 +37,34 @@ namespace grpc_core { class UrlExternalAccountCredentials final : public ExternalAccountCredentials { public: - static RefCountedPtr Create( + static absl::StatusOr> Create( Options options, std::vector scopes, + std::shared_ptr + event_engine = nullptr); + + UrlExternalAccountCredentials( + Options options, std::vector scopes, + std::shared_ptr + event_engine, grpc_error_handle* error); - UrlExternalAccountCredentials(Options options, - std::vector scopes, - grpc_error_handle* error); + std::string debug_string() override; + + UniqueTypeName type() const override; private: - void RetrieveSubjectToken( - HTTPRequestContext* ctx, const Options& options, - std::function cb) override; + OrphanablePtr RetrieveSubjectToken( + Timestamp deadline, + absl::AnyInvocable)> on_done) override; absl::string_view CredentialSourceType() override; - static void OnRetrieveSubjectToken(void* arg, grpc_error_handle error); - void OnRetrieveSubjectTokenInternal(grpc_error_handle error); - - void FinishRetrieveSubjectToken(std::string subject_token, - grpc_error_handle error); - // Fields of credential source URI url_; std::string url_full_path_; std::map headers_; std::string format_type_; std::string format_subject_token_field_name_; - - OrphanablePtr http_request_; - HTTPRequestContext* ctx_ = nullptr; - std::function cb_ = nullptr; }; } // namespace grpc_core diff --git a/src/core/lib/security/credentials/fake/fake_credentials.h b/src/core/lib/security/credentials/fake/fake_credentials.h index 214d9b3ac3e05..d4b00d6eb403a 100644 --- a/src/core/lib/security/credentials/fake/fake_credentials.h +++ b/src/core/lib/security/credentials/fake/fake_credentials.h @@ -99,6 +99,8 @@ class grpc_md_only_test_credentials : public grpc_call_credentials { key_(grpc_core::Slice::FromCopiedString(md_key)), value_(grpc_core::Slice::FromCopiedString(md_value)) {} + void Orphaned() override {} + grpc_core::ArenaPromise> GetRequestMetadata(grpc_core::ClientMetadataHandle initial_metadata, const GetRequestMetadataArgs* args) override; diff --git a/src/core/lib/security/credentials/google_default/google_default_credentials.cc b/src/core/lib/security/credentials/google_default/google_default_credentials.cc index 3fa306cae56bd..d649eb5512b5c 100644 --- a/src/core/lib/security/credentials/google_default/google_default_credentials.cc +++ b/src/core/lib/security/credentials/google_default/google_default_credentials.cc @@ -258,68 +258,54 @@ static int is_metadata_server_reachable() { static grpc_error_handle create_default_creds_from_path( const std::string& creds_path, grpc_core::RefCountedPtr* creds) { - grpc_auth_json_key key; - grpc_auth_refresh_token token; - grpc_core::RefCountedPtr result; - absl::StatusOr creds_data; - grpc_error_handle error; - Json json; if (creds_path.empty()) { - error = GRPC_ERROR_CREATE("creds_path unset"); - goto end; + return GRPC_ERROR_CREATE("creds_path unset"); } - creds_data = grpc_core::LoadFile(creds_path, /*add_null_terminator=*/false); + auto creds_data = + grpc_core::LoadFile(creds_path, /*add_null_terminator=*/false); if (!creds_data.ok()) { - error = absl_status_to_grpc_error(creds_data.status()); - goto end; + return absl_status_to_grpc_error(creds_data.status()); } - { - auto json_or = grpc_core::JsonParse(creds_data->as_string_view()); - if (!json_or.ok()) { - error = absl_status_to_grpc_error(json_or.status()); - goto end; - } - json = std::move(*json_or); + auto json = grpc_core::JsonParse(creds_data->as_string_view()); + if (!json.ok()) { + return absl_status_to_grpc_error(json.status()); } - if (json.type() != Json::Type::kObject) { - error = GRPC_ERROR_CREATE(absl::StrCat("Failed to parse JSON \"", - creds_data->as_string_view(), "\"")); - goto end; + if (json->type() != Json::Type::kObject) { + return GRPC_ERROR_CREATE(absl::StrCat("Failed to parse JSON \"", + creds_data->as_string_view(), "\"")); } - // First, try an auth json key. - key = grpc_auth_json_key_create_from_json(json); + grpc_auth_json_key key = grpc_auth_json_key_create_from_json(*json); if (grpc_auth_json_key_is_valid(&key)) { - result = + *creds = grpc_service_account_jwt_access_credentials_create_from_auth_json_key( key, grpc_max_auth_token_lifetime()); - if (result == nullptr) { - error = GRPC_ERROR_CREATE( + if (*creds == nullptr) { + return GRPC_ERROR_CREATE( "grpc_service_account_jwt_access_credentials_create_from_auth_json_" "key failed"); } - goto end; + return absl::OkStatus(); } - // Then try a refresh token if the auth json key was invalid. - token = grpc_auth_refresh_token_create_from_json(json); + grpc_auth_refresh_token token = + grpc_auth_refresh_token_create_from_json(*json); if (grpc_auth_refresh_token_is_valid(&token)) { - result = + *creds = grpc_refresh_token_credentials_create_from_auth_refresh_token(token); - if (result == nullptr) { - error = GRPC_ERROR_CREATE( + if (*creds == nullptr) { + return GRPC_ERROR_CREATE( "grpc_refresh_token_credentials_create_from_auth_refresh_token " "failed"); } - goto end; + return absl::OkStatus(); } - - result = grpc_core::ExternalAccountCredentials::Create(json, {}, &error); - -end: - CHECK((result == nullptr) + (error.ok()) == 1); - *creds = result; - return error; + // Use external creds. + auto external_creds = + grpc_core::ExternalAccountCredentials::Create(*json, {}); + if (!external_creds.ok()) return external_creds.status(); + *creds = std::move(*external_creds); + return absl::OkStatus(); } static void update_tenancy() { diff --git a/src/core/lib/security/credentials/iam/iam_credentials.h b/src/core/lib/security/credentials/iam/iam_credentials.h index 592dffc4ebdcd..8fa67a9048d67 100644 --- a/src/core/lib/security/credentials/iam/iam_credentials.h +++ b/src/core/lib/security/credentials/iam/iam_credentials.h @@ -40,6 +40,8 @@ class grpc_google_iam_credentials : public grpc_call_credentials { grpc_google_iam_credentials(const char* token, const char* authority_selector); + void Orphaned() override {} + grpc_core::ArenaPromise> GetRequestMetadata(grpc_core::ClientMetadataHandle initial_metadata, const GetRequestMetadataArgs* args) override; diff --git a/src/core/lib/security/credentials/jwt/jwt_credentials.h b/src/core/lib/security/credentials/jwt/jwt_credentials.h index 91332098eeb59..751ff187fc6f0 100644 --- a/src/core/lib/security/credentials/jwt/jwt_credentials.h +++ b/src/core/lib/security/credentials/jwt/jwt_credentials.h @@ -51,6 +51,8 @@ class grpc_service_account_jwt_access_credentials gpr_timespec token_lifetime); ~grpc_service_account_jwt_access_credentials() override; + void Orphaned() override {} + grpc_core::ArenaPromise> GetRequestMetadata(grpc_core::ClientMetadataHandle initial_metadata, const GetRequestMetadataArgs* args) override; diff --git a/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc b/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc index a0738bce008f1..4e366d0bb69ea 100644 --- a/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc +++ b/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc @@ -18,7 +18,6 @@ #include "src/core/lib/security/credentials/oauth2/oauth2_credentials.h" -#include #include #include @@ -30,6 +29,7 @@ #include "absl/log/check.h" #include "absl/log/log.h" #include "absl/status/status.h" +#include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" @@ -141,13 +141,49 @@ void grpc_auth_refresh_token_destruct(grpc_auth_refresh_token* refresh_token) { } // -// Oauth2 Token Fetcher credentials. +// Oauth2 Token parsing. // -grpc_oauth2_token_fetcher_credentials:: - ~grpc_oauth2_token_fetcher_credentials() { - gpr_mu_destroy(&mu_); - grpc_pollset_set_destroy(grpc_polling_entity_pollset_set(&pollent_)); +grpc_credentials_status +grpc_oauth2_token_fetcher_credentials_parse_server_response_body( + absl::string_view body, absl::optional* token_value, + grpc_core::Duration* token_lifetime) { + auto json = grpc_core::JsonParse(body); + if (!json.ok()) { + LOG(ERROR) << "Could not parse JSON from " << body << ": " << json.status(); + return GRPC_CREDENTIALS_ERROR; + } + if (json->type() != Json::Type::kObject) { + LOG(ERROR) << "Response should be a JSON object"; + return GRPC_CREDENTIALS_ERROR; + } + auto it = json->object().find("access_token"); + if (it == json->object().end() || it->second.type() != Json::Type::kString) { + LOG(ERROR) << "Missing or invalid access_token in JSON."; + return GRPC_CREDENTIALS_ERROR; + } + absl::string_view access_token = it->second.string(); + it = json->object().find("token_type"); + if (it == json->object().end() || it->second.type() != Json::Type::kString) { + LOG(ERROR) << "Missing or invalid token_type in JSON."; + return GRPC_CREDENTIALS_ERROR; + } + absl::string_view token_type = it->second.string(); + it = json->object().find("expires_in"); + if (it == json->object().end() || it->second.type() != Json::Type::kNumber) { + LOG(ERROR) << "Missing or invalid expires_in in JSON."; + return GRPC_CREDENTIALS_ERROR; + } + absl::string_view expires_in = it->second.string(); + long seconds; + if (!absl::SimpleAtoi(expires_in, &seconds)) { + LOG(ERROR) << "Invalid expires_in in JSON."; + return GRPC_CREDENTIALS_ERROR; + } + *token_lifetime = grpc_core::Duration::Seconds(seconds); + *token_value = grpc_core::Slice::FromCopiedString( + absl::StrCat(token_type, " ", access_token)); + return GRPC_CREDENTIALS_OK; } grpc_credentials_status @@ -155,217 +191,99 @@ grpc_oauth2_token_fetcher_credentials_parse_server_response( const grpc_http_response* response, absl::optional* token_value, grpc_core::Duration* token_lifetime) { - char* null_terminated_body = nullptr; - grpc_credentials_status status = GRPC_CREDENTIALS_OK; - + *token_value = absl::nullopt; if (response == nullptr) { LOG(ERROR) << "Received NULL response."; - status = GRPC_CREDENTIALS_ERROR; - goto end; + return GRPC_CREDENTIALS_ERROR; } - - if (response->body_length > 0) { - null_terminated_body = - static_cast(gpr_malloc(response->body_length + 1)); - null_terminated_body[response->body_length] = '\0'; - memcpy(null_terminated_body, response->body, response->body_length); - } - + absl::string_view body(response->body, response->body_length); if (response->status != 200) { LOG(ERROR) << "Call to http server ended with error " << response->status - << " [" - << (null_terminated_body != nullptr ? null_terminated_body : "") - << "]"; - status = GRPC_CREDENTIALS_ERROR; - goto end; - } else { - const char* access_token = nullptr; - const char* token_type = nullptr; - const char* expires_in = nullptr; - Json::Object::const_iterator it; - auto json = grpc_core::JsonParse( - null_terminated_body != nullptr ? null_terminated_body : ""); - if (!json.ok()) { - LOG(ERROR) << "Could not parse JSON from " << null_terminated_body << ": " - << json.status(); - status = GRPC_CREDENTIALS_ERROR; - goto end; - } - if (json->type() != Json::Type::kObject) { - LOG(ERROR) << "Response should be a JSON object"; - status = GRPC_CREDENTIALS_ERROR; - goto end; - } - it = json->object().find("access_token"); - if (it == json->object().end() || - it->second.type() != Json::Type::kString) { - LOG(ERROR) << "Missing or invalid access_token in JSON."; - status = GRPC_CREDENTIALS_ERROR; - goto end; - } - access_token = it->second.string().c_str(); - it = json->object().find("token_type"); - if (it == json->object().end() || - it->second.type() != Json::Type::kString) { - LOG(ERROR) << "Missing or invalid token_type in JSON."; - status = GRPC_CREDENTIALS_ERROR; - goto end; - } - token_type = it->second.string().c_str(); - it = json->object().find("expires_in"); - if (it == json->object().end() || - it->second.type() != Json::Type::kNumber) { - LOG(ERROR) << "Missing or invalid expires_in in JSON."; - status = GRPC_CREDENTIALS_ERROR; - goto end; - } - expires_in = it->second.string().c_str(); - *token_lifetime = - grpc_core::Duration::Seconds(strtol(expires_in, nullptr, 10)); - *token_value = grpc_core::Slice::FromCopiedString( - absl::StrCat(token_type, " ", access_token)); - status = GRPC_CREDENTIALS_OK; + << " [" << body << "]"; + return GRPC_CREDENTIALS_ERROR; } - -end: - if (status != GRPC_CREDENTIALS_OK) *token_value = absl::nullopt; - gpr_free(null_terminated_body); - return status; + return grpc_oauth2_token_fetcher_credentials_parse_server_response_body( + body, token_value, token_lifetime); } -static void on_oauth2_token_fetcher_http_response(void* user_data, - grpc_error_handle error) { - GRPC_LOG_IF_ERROR("oauth_fetch", error); - grpc_credentials_metadata_request* r = - static_cast(user_data); - grpc_oauth2_token_fetcher_credentials* c = - reinterpret_cast(r->creds.get()); - c->on_http_response(r, error); -} +// +// Oauth2TokenFetcherCredentials +// -void grpc_oauth2_token_fetcher_credentials::on_http_response( - grpc_credentials_metadata_request* r, grpc_error_handle error) { - absl::optional access_token_value; - grpc_core::Duration token_lifetime; - grpc_credentials_status status = - error.ok() ? grpc_oauth2_token_fetcher_credentials_parse_server_response( - &r->response, &access_token_value, &token_lifetime) - : GRPC_CREDENTIALS_ERROR; - // Update cache and grab list of pending requests. - gpr_mu_lock(&mu_); - token_fetch_pending_ = false; - if (access_token_value.has_value()) { - access_token_value_ = access_token_value->Ref(); - } else { - access_token_value_ = absl::nullopt; - } - token_expiration_ = status == GRPC_CREDENTIALS_OK - ? gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), - token_lifetime.as_timespec()) - : gpr_inf_past(GPR_CLOCK_MONOTONIC); - grpc_oauth2_pending_get_request_metadata* pending_request = pending_requests_; - pending_requests_ = nullptr; - gpr_mu_unlock(&mu_); - // Invoke callbacks for all pending requests. - while (pending_request != nullptr) { - if (status == GRPC_CREDENTIALS_OK) { - pending_request->result = access_token_value->Ref(); - } else { - auto err = GRPC_ERROR_CREATE_REFERENCING( - "Error occurred when fetching oauth2 token.", &error, 1); - pending_request->result = grpc_error_to_absl_status(err); - } - pending_request->done.store(true, std::memory_order_release); - pending_request->waker.Wakeup(); - grpc_polling_entity_del_from_pollset_set( - pending_request->pollent, grpc_polling_entity_pollset_set(&pollent_)); - grpc_oauth2_pending_get_request_metadata* prev = pending_request; - pending_request = pending_request->next; - prev->Unref(); - } - delete r; -} +namespace grpc_core { -grpc_core::ArenaPromise> -grpc_oauth2_token_fetcher_credentials::GetRequestMetadata( - grpc_core::ClientMetadataHandle initial_metadata, - const grpc_call_credentials::GetRequestMetadataArgs*) { - // Check if we can use the cached token. - absl::optional cached_access_token_value; - gpr_mu_lock(&mu_); - if (access_token_value_.has_value() && - gpr_time_cmp( - gpr_time_sub(token_expiration_, gpr_now(GPR_CLOCK_MONOTONIC)), - gpr_time_from_seconds(GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS, - GPR_TIMESPAN)) > 0) { - cached_access_token_value = access_token_value_->Ref(); - } - if (cached_access_token_value.has_value()) { - gpr_mu_unlock(&mu_); - initial_metadata->Append( - GRPC_AUTHORIZATION_METADATA_KEY, std::move(*cached_access_token_value), - [](absl::string_view, const grpc_core::Slice&) { abort(); }); - return grpc_core::Immediate(std::move(initial_metadata)); +// State held for a pending HTTP request. +class Oauth2TokenFetcherCredentials::HttpFetchRequest final + : public TokenFetcherCredentials::FetchRequest { + public: + HttpFetchRequest( + Oauth2TokenFetcherCredentials* creds, Timestamp deadline, + absl::AnyInvocable< + void(absl::StatusOr>)> + on_done) + : on_done_(std::move(on_done)) { + GRPC_CLOSURE_INIT(&on_http_response_, OnHttpResponse, this, nullptr); + Ref().release(); // Ref held by HTTP request callback. + http_request_ = creds->StartHttpRequest(creds->pollent(), deadline, + &response_, &on_http_response_); } - // Couldn't get the token from the cache. - // Add request to pending_requests_ and start a new fetch if needed. - grpc_core::Duration refresh_threshold = - grpc_core::Duration::Seconds(GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS); - auto pending_request = - grpc_core::MakeRefCounted(); - pending_request->pollent = grpc_core::GetContext(); - pending_request->waker = - grpc_core::GetContext()->MakeNonOwningWaker(); - grpc_polling_entity_add_to_pollset_set( - pending_request->pollent, grpc_polling_entity_pollset_set(&pollent_)); - pending_request->next = pending_requests_; - pending_request->md = std::move(initial_metadata); - pending_requests_ = pending_request->Ref().release(); - bool start_fetch = false; - if (!token_fetch_pending_) { - token_fetch_pending_ = true; - start_fetch = true; + + ~HttpFetchRequest() override { grpc_http_response_destroy(&response_); } + + void Orphan() override { + http_request_.reset(); + Unref(); } - gpr_mu_unlock(&mu_); - if (start_fetch) { - fetch_oauth2(new grpc_credentials_metadata_request(Ref()), &pollent_, - on_oauth2_token_fetcher_http_response, - grpc_core::Timestamp::Now() + refresh_threshold); + + private: + static void OnHttpResponse(void* arg, grpc_error_handle error) { + RefCountedPtr self(static_cast(arg)); + if (!error.ok()) { + self->on_done_(std::move(error)); + return; + } + // Parse oauth2 token. + absl::optional access_token_value; + Duration token_lifetime; + grpc_credentials_status status = + grpc_oauth2_token_fetcher_credentials_parse_server_response( + &self->response_, &access_token_value, &token_lifetime); + if (status != GRPC_CREDENTIALS_OK) { + self->on_done_(absl::UnavailableError("error parsing oauth2 token")); + return; + } + self->on_done_(MakeRefCounted(std::move(*access_token_value), + Timestamp::Now() + token_lifetime)); } - return - [pending_request]() - -> grpc_core::Poll> { - if (!pending_request->done.load(std::memory_order_acquire)) { - return grpc_core::Pending{}; - } - if (pending_request->result.ok()) { - pending_request->md->Append( - GRPC_AUTHORIZATION_METADATA_KEY, - std::move(*pending_request->result), - [](absl::string_view, const grpc_core::Slice&) { abort(); }); - return std::move(pending_request->md); - } else { - return pending_request->result.status(); - } - }; -} -grpc_oauth2_token_fetcher_credentials::grpc_oauth2_token_fetcher_credentials() - : token_expiration_(gpr_inf_past(GPR_CLOCK_MONOTONIC)), - pollent_(grpc_polling_entity_create_from_pollset_set( - grpc_pollset_set_create())) { - gpr_mu_init(&mu_); -} + OrphanablePtr http_request_; + grpc_closure on_http_response_; + grpc_http_response response_; + absl::AnyInvocable>)> + on_done_; +}; -std::string grpc_oauth2_token_fetcher_credentials::debug_string() { +std::string Oauth2TokenFetcherCredentials::debug_string() { return "OAuth2TokenFetcherCredentials"; } -grpc_core::UniqueTypeName grpc_oauth2_token_fetcher_credentials::type() const { - static grpc_core::UniqueTypeName::Factory kFactory("Oauth2"); +UniqueTypeName Oauth2TokenFetcherCredentials::type() const { + static UniqueTypeName::Factory kFactory("Oauth2"); return kFactory.Create(); } +OrphanablePtr +Oauth2TokenFetcherCredentials::FetchToken( + Timestamp deadline, + absl::AnyInvocable< + void(absl::StatusOr>)> + on_done) { + return MakeOrphanable(this, deadline, std::move(on_done)); +} + +} // namespace grpc_core + // // Google Compute Engine credentials. // @@ -373,16 +291,21 @@ grpc_core::UniqueTypeName grpc_oauth2_token_fetcher_credentials::type() const { namespace { class grpc_compute_engine_token_fetcher_credentials - : public grpc_oauth2_token_fetcher_credentials { + : public grpc_core::Oauth2TokenFetcherCredentials { public: grpc_compute_engine_token_fetcher_credentials() = default; ~grpc_compute_engine_token_fetcher_credentials() override = default; - protected: - void fetch_oauth2(grpc_credentials_metadata_request* metadata_req, - grpc_polling_entity* pollent, - grpc_iomgr_cb_func response_cb, - grpc_core::Timestamp deadline) override { + std::string debug_string() override { + return absl::StrFormat( + "GoogleComputeEngineTokenFetcherCredentials{%s}", + grpc_core::Oauth2TokenFetcherCredentials::debug_string()); + } + + private: + grpc_core::OrphanablePtr StartHttpRequest( + grpc_polling_entity* pollent, grpc_core::Timestamp deadline, + grpc_http_response* response, grpc_closure* on_complete) override { grpc_http_header header = {const_cast("Metadata-Flavor"), const_cast("Google")}; grpc_http_request request; @@ -396,26 +319,14 @@ class grpc_compute_engine_token_fetcher_credentials GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH, {} /* query params */, "" /* fragment */); CHECK(uri.ok()); // params are hardcoded - http_request_ = grpc_core::HttpRequest::Get( - std::move(*uri), nullptr /* channel args */, pollent, &request, - deadline, - GRPC_CLOSURE_INIT(&http_get_cb_closure_, response_cb, metadata_req, - grpc_schedule_on_exec_ctx), - &metadata_req->response, + auto http_request = grpc_core::HttpRequest::Get( + std::move(*uri), /*args=*/nullptr, pollent, &request, deadline, + on_complete, response, grpc_core::RefCountedPtr( grpc_insecure_credentials_create())); - http_request_->Start(); + http_request->Start(); + return http_request; } - - std::string debug_string() override { - return absl::StrFormat( - "GoogleComputeEngineTokenFetcherCredentials{%s}", - grpc_oauth2_token_fetcher_credentials::debug_string()); - } - - private: - grpc_closure http_get_cb_closure_; - grpc_core::OrphanablePtr http_request_; }; } // namespace @@ -434,22 +345,26 @@ grpc_call_credentials* grpc_google_compute_engine_credentials_create( // Google Refresh Token credentials. // +grpc_google_refresh_token_credentials::grpc_google_refresh_token_credentials( + grpc_auth_refresh_token refresh_token) + : refresh_token_(refresh_token) {} + grpc_google_refresh_token_credentials:: ~grpc_google_refresh_token_credentials() { grpc_auth_refresh_token_destruct(&refresh_token_); } -void grpc_google_refresh_token_credentials::fetch_oauth2( - grpc_credentials_metadata_request* metadata_req, - grpc_polling_entity* pollent, grpc_iomgr_cb_func response_cb, - grpc_core::Timestamp deadline) { +grpc_core::OrphanablePtr +grpc_google_refresh_token_credentials::StartHttpRequest( + grpc_polling_entity* pollent, grpc_core::Timestamp deadline, + grpc_http_response* response, grpc_closure* on_complete) { grpc_http_header header = { const_cast("Content-Type"), const_cast("application/x-www-form-urlencoded")}; - grpc_http_request request; std::string body = absl::StrFormat( GRPC_REFRESH_TOKEN_POST_BODY_FORMAT_STRING, refresh_token_.client_id, refresh_token_.client_secret, refresh_token_.refresh_token); + grpc_http_request request; memset(&request, 0, sizeof(grpc_http_request)); request.hdr_count = 1; request.hdrs = &header; @@ -462,18 +377,13 @@ void grpc_google_refresh_token_credentials::fetch_oauth2( GRPC_GOOGLE_OAUTH2_SERVICE_TOKEN_PATH, {} /* query params */, "" /* fragment */); CHECK(uri.ok()); // params are hardcoded - http_request_ = grpc_core::HttpRequest::Post( - std::move(*uri), nullptr /* channel args */, pollent, &request, deadline, - GRPC_CLOSURE_INIT(&http_post_cb_closure_, response_cb, metadata_req, - grpc_schedule_on_exec_ctx), - &metadata_req->response, grpc_core::CreateHttpRequestSSLCredentials()); - http_request_->Start(); + auto http_request = grpc_core::HttpRequest::Post( + std::move(*uri), /*args=*/nullptr, pollent, &request, deadline, + on_complete, response, grpc_core::CreateHttpRequestSSLCredentials()); + http_request->Start(); + return http_request; } -grpc_google_refresh_token_credentials::grpc_google_refresh_token_credentials( - grpc_auth_refresh_token refresh_token) - : refresh_token_(refresh_token) {} - grpc_core::RefCountedPtr grpc_refresh_token_credentials_create_from_auth_refresh_token( grpc_auth_refresh_token refresh_token) { @@ -486,9 +396,9 @@ grpc_refresh_token_credentials_create_from_auth_refresh_token( } std::string grpc_google_refresh_token_credentials::debug_string() { - return absl::StrFormat("GoogleRefreshToken{ClientID:%s,%s}", - refresh_token_.client_id, - grpc_oauth2_token_fetcher_credentials::debug_string()); + return absl::StrFormat( + "GoogleRefreshToken{ClientID:%s,%s}", refresh_token_.client_id, + grpc_core::Oauth2TokenFetcherCredentials::debug_string()); } grpc_core::UniqueTypeName grpc_google_refresh_token_credentials::type() const { @@ -545,8 +455,7 @@ grpc_error_handle LoadTokenFile(const char* path, grpc_slice* token) { return absl::OkStatus(); } -class StsTokenFetcherCredentials - : public grpc_oauth2_token_fetcher_credentials { +class StsTokenFetcherCredentials : public Oauth2TokenFetcherCredentials { public: StsTokenFetcherCredentials(URI sts_url, const grpc_sts_credentials_options* options) @@ -563,21 +472,19 @@ class StsTokenFetcherCredentials std::string debug_string() override { return absl::StrFormat( "StsTokenFetcherCredentials{Path:%s,Authority:%s,%s}", sts_url_.path(), - sts_url_.authority(), - grpc_oauth2_token_fetcher_credentials::debug_string()); + sts_url_.authority(), Oauth2TokenFetcherCredentials::debug_string()); } private: - void fetch_oauth2(grpc_credentials_metadata_request* metadata_req, - grpc_polling_entity* pollent, - grpc_iomgr_cb_func response_cb, - Timestamp deadline) override { + OrphanablePtr StartHttpRequest( + grpc_polling_entity* pollent, Timestamp deadline, + grpc_http_response* response, grpc_closure* on_complete) override { grpc_http_request request; memset(&request, 0, sizeof(grpc_http_request)); grpc_error_handle err = FillBody(&request.body, &request.body_length); if (!err.ok()) { - response_cb(metadata_req, err); - return; + ExecCtx::Run(DEBUG_LOCATION, on_complete, std::move(err)); + return nullptr; } grpc_http_header header = { const_cast("Content-Type"), @@ -594,13 +501,12 @@ class StsTokenFetcherCredentials } else { http_request_creds = CreateHttpRequestSSLCredentials(); } - http_request_ = HttpRequest::Post( - sts_url_, nullptr /* channel args */, pollent, &request, deadline, - GRPC_CLOSURE_INIT(&http_post_cb_closure_, response_cb, metadata_req, - grpc_schedule_on_exec_ctx), - &metadata_req->response, std::move(http_request_creds)); - http_request_->Start(); + auto http_request = HttpRequest::Post( + sts_url_, /*args=*/nullptr, pollent, &request, deadline, on_complete, + response, std::move(http_request_creds)); + http_request->Start(); gpr_free(request.body); + return http_request; } grpc_error_handle FillBody(char** body, size_t* body_length) { @@ -646,7 +552,6 @@ class StsTokenFetcherCredentials } URI sts_url_; - grpc_closure http_post_cb_closure_; UniquePtr resource_; UniquePtr audience_; UniquePtr scope_; diff --git a/src/core/lib/security/credentials/oauth2/oauth2_credentials.h b/src/core/lib/security/credentials/oauth2/oauth2_credentials.h index 199f35f33c8b6..ea9315ba0121d 100644 --- a/src/core/lib/security/credentials/oauth2/oauth2_credentials.h +++ b/src/core/lib/security/credentials/oauth2/oauth2_credentials.h @@ -24,6 +24,7 @@ #include #include "absl/status/statusor.h" +#include "absl/strings/string_view.h" #include "absl/types/optional.h" #include @@ -43,6 +44,7 @@ #include "src/core/lib/promise/activity.h" #include "src/core/lib/promise/arena_promise.h" #include "src/core/lib/security/credentials/credentials.h" +#include "src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h" #include "src/core/lib/slice/slice.h" #include "src/core/lib/transport/transport.h" #include "src/core/lib/uri/uri_parser.h" @@ -80,73 +82,45 @@ grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json( /// Destructs the object. void grpc_auth_refresh_token_destruct(grpc_auth_refresh_token* refresh_token); -// -- Credentials Metadata Request. -- - -struct grpc_credentials_metadata_request { - explicit grpc_credentials_metadata_request( - grpc_core::RefCountedPtr creds) - : creds(std::move(creds)) {} - ~grpc_credentials_metadata_request() { - grpc_http_response_destroy(&response); - } - - grpc_core::RefCountedPtr creds; - grpc_http_response response; -}; - -struct grpc_oauth2_pending_get_request_metadata - : public grpc_core::RefCounted { - std::atomic done{false}; - grpc_core::Waker waker; - grpc_polling_entity* pollent; - grpc_core::ClientMetadataHandle md; - struct grpc_oauth2_pending_get_request_metadata* next; - absl::StatusOr result; -}; - // -- Oauth2 Token Fetcher credentials -- // // This object is a base for credentials that need to acquire an oauth2 token // from an http service. -class grpc_oauth2_token_fetcher_credentials : public grpc_call_credentials { - public: - grpc_oauth2_token_fetcher_credentials(); - ~grpc_oauth2_token_fetcher_credentials() override; - - grpc_core::ArenaPromise> - GetRequestMetadata(grpc_core::ClientMetadataHandle initial_metadata, - const GetRequestMetadataArgs* args) override; +namespace grpc_core { - void on_http_response(grpc_credentials_metadata_request* r, - grpc_error_handle error); +// A base class for oauth2 token fetching credentials. +// Subclasses must implement StartHttpRequest(). +class Oauth2TokenFetcherCredentials : public TokenFetcherCredentials { + public: std::string debug_string() override; - grpc_core::UniqueTypeName type() const override; + UniqueTypeName type() const override; + + OrphanablePtr FetchToken( + Timestamp deadline, + absl::AnyInvocable< + void(absl::StatusOr>)> + on_done) final; - protected: - virtual void fetch_oauth2(grpc_credentials_metadata_request* req, - grpc_polling_entity* pollent, grpc_iomgr_cb_func cb, - grpc_core::Timestamp deadline) = 0; + virtual OrphanablePtr StartHttpRequest( + grpc_polling_entity* pollent, Timestamp deadline, + grpc_http_response* response, grpc_closure* on_complete) = 0; private: + class HttpFetchRequest; + int cmp_impl(const grpc_call_credentials* other) const override { // TODO(yashykt): Check if we can do something better here - return grpc_core::QsortCompare( - static_cast(this), other); + return QsortCompare(static_cast(this), other); } - - gpr_mu mu_; - absl::optional access_token_value_; - gpr_timespec token_expiration_; - bool token_fetch_pending_ = false; - grpc_oauth2_pending_get_request_metadata* pending_requests_ = nullptr; - grpc_polling_entity pollent_; }; +} // namespace grpc_core + // Google refresh token credentials. class grpc_google_refresh_token_credentials final - : public grpc_oauth2_token_fetcher_credentials { + : public grpc_core::Oauth2TokenFetcherCredentials { public: explicit grpc_google_refresh_token_credentials( grpc_auth_refresh_token refresh_token); @@ -160,15 +134,12 @@ class grpc_google_refresh_token_credentials final grpc_core::UniqueTypeName type() const override; - protected: - void fetch_oauth2(grpc_credentials_metadata_request* req, - grpc_polling_entity* pollent, grpc_iomgr_cb_func cb, - grpc_core::Timestamp deadline) override; - private: + grpc_core::OrphanablePtr StartHttpRequest( + grpc_polling_entity* pollent, grpc_core::Timestamp deadline, + grpc_http_response* response, grpc_closure* on_complete) override; + grpc_auth_refresh_token refresh_token_; - grpc_closure http_post_cb_closure_; - grpc_core::OrphanablePtr http_request_; }; // Access token credentials. @@ -176,6 +147,8 @@ class grpc_access_token_credentials final : public grpc_call_credentials { public: explicit grpc_access_token_credentials(const char* access_token); + void Orphaned() override {} + grpc_core::ArenaPromise> GetRequestMetadata(grpc_core::ClientMetadataHandle initial_metadata, const GetRequestMetadataArgs* args) override; @@ -202,6 +175,11 @@ grpc_core::RefCountedPtr grpc_refresh_token_credentials_create_from_auth_refresh_token( grpc_auth_refresh_token token); +grpc_credentials_status +grpc_oauth2_token_fetcher_credentials_parse_server_response_body( + absl::string_view body, absl::optional* token_value, + grpc_core::Duration* token_lifetime); + // Exposed for testing only. grpc_credentials_status grpc_oauth2_token_fetcher_credentials_parse_server_response( diff --git a/src/core/lib/security/credentials/plugin/plugin_credentials.h b/src/core/lib/security/credentials/plugin/plugin_credentials.h index b1f6710af136f..9cffc05b3dc0d 100644 --- a/src/core/lib/security/credentials/plugin/plugin_credentials.h +++ b/src/core/lib/security/credentials/plugin/plugin_credentials.h @@ -57,6 +57,8 @@ struct grpc_plugin_credentials final : public grpc_call_credentials { grpc_security_level min_security_level); ~grpc_plugin_credentials() override; + void Orphaned() override {} + grpc_core::ArenaPromise> GetRequestMetadata(grpc_core::ClientMetadataHandle initial_metadata, const GetRequestMetadataArgs* args) override; diff --git a/src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.cc b/src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.cc new file mode 100644 index 0000000000000..eb5bee6478a96 --- /dev/null +++ b/src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.cc @@ -0,0 +1,130 @@ +// +// +// Copyright 2015 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// + +#include "src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h" + +#include "src/core/lib/iomgr/pollset_set.h" +#include "src/core/lib/promise/context.h" +#include "src/core/lib/promise/poll.h" +#include "src/core/lib/promise/promise.h" + +namespace grpc_core { + +namespace { + +// Amount of time before the token's expiration that we consider it +// invalid and start a new fetch. Also determines the timeout for the +// fetch request. +constexpr Duration kTokenRefreshDuration = Duration::Seconds(60); + +} // namespace + +// +// TokenFetcherCredentials::Token +// + +void TokenFetcherCredentials::Token::AddTokenToClientInitialMetadata( + ClientMetadata& metadata) const { + metadata.Append(GRPC_AUTHORIZATION_METADATA_KEY, token_.Ref(), + [](absl::string_view, const Slice&) { abort(); }); +} + +// +// TokenFetcherCredentials +// + +TokenFetcherCredentials::TokenFetcherCredentials() + : pollent_(grpc_polling_entity_create_from_pollset_set( + grpc_pollset_set_create())) {} + +TokenFetcherCredentials::~TokenFetcherCredentials() { + grpc_pollset_set_destroy(grpc_polling_entity_pollset_set(&pollent_)); +} + +void TokenFetcherCredentials::Orphaned() { + MutexLock lock(&mu_); + auto* fetch_request = absl::get_if>(&token_); + if (fetch_request != nullptr) fetch_request->reset(); +} + +ArenaPromise> +TokenFetcherCredentials::GetRequestMetadata( + ClientMetadataHandle initial_metadata, const GetRequestMetadataArgs*) { + RefCountedPtr pending_call; + { + MutexLock lock(&mu_); + // Check if we can use the cached token. + auto* cached_token = absl::get_if>(&token_); + if (cached_token != nullptr && *cached_token != nullptr && + ((*cached_token)->ExpirationTime() - Timestamp::Now()) > + kTokenRefreshDuration) { + (*cached_token)->AddTokenToClientInitialMetadata(*initial_metadata); + return Immediate(std::move(initial_metadata)); + } + // Couldn't get the token from the cache. + // Add this call to the pending list. + pending_call = MakeRefCounted(); + pending_call->waker = GetContext()->MakeNonOwningWaker(); + pending_call->pollent = GetContext(); + grpc_polling_entity_add_to_pollset_set( + pending_call->pollent, grpc_polling_entity_pollset_set(&pollent_)); + pending_call->md = std::move(initial_metadata); + pending_calls_.insert(pending_call); + // Start a new fetch if needed. + if (!absl::holds_alternative>(token_)) { + token_ = FetchToken( + /*deadline=*/Timestamp::Now() + kTokenRefreshDuration, + [self = WeakRefAsSubclass()]( + absl::StatusOr> token) mutable { + self->TokenFetchComplete(std::move(token)); + }); + } + } + return [pending_call = std::move( + pending_call)]() -> Poll> { + if (!pending_call->done.load(std::memory_order_acquire)) { + return Pending{}; + } + if (!pending_call->result.ok()) { + return pending_call->result.status(); + } + (*pending_call->result)->AddTokenToClientInitialMetadata(*pending_call->md); + return std::move(pending_call->md); + }; +} + +void TokenFetcherCredentials::TokenFetchComplete( + absl::StatusOr> token) { + // Update cache and grab list of pending requests. + absl::flat_hash_set> pending_calls; + { + MutexLock lock(&mu_); + token_ = token.value_or(nullptr); + pending_calls_.swap(pending_calls); + } + // Invoke callbacks for all pending requests. + for (auto& pending_call : pending_calls) { + pending_call->result = token; + pending_call->done.store(true, std::memory_order_release); + pending_call->waker.Wakeup(); + grpc_polling_entity_del_from_pollset_set( + pending_call->pollent, grpc_polling_entity_pollset_set(&pollent_)); + } +} + +} // namespace grpc_core diff --git a/src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h b/src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h new file mode 100644 index 0000000000000..72793afcdee25 --- /dev/null +++ b/src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h @@ -0,0 +1,115 @@ +// +// Copyright 2016 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef GRPC_SRC_CORE_LIB_SECURITY_CREDENTIALS_TOKEN_FETCHER_TOKEN_FETCHER_CREDENTIALS_H +#define GRPC_SRC_CORE_LIB_SECURITY_CREDENTIALS_TOKEN_FETCHER_TOKEN_FETCHER_CREDENTIALS_H + +#include +#include +#include + +#include "absl/container/flat_hash_set.h" +#include "absl/functional/any_invocable.h" +#include "absl/status/statusor.h" +#include "absl/types/variant.h" + +#include "src/core/lib/gprpp/orphanable.h" +#include "src/core/lib/gprpp/ref_counted.h" +#include "src/core/lib/gprpp/ref_counted_ptr.h" +#include "src/core/lib/gprpp/sync.h" +#include "src/core/lib/gprpp/time.h" +#include "src/core/lib/iomgr/polling_entity.h" +#include "src/core/lib/promise/arena_promise.h" +#include "src/core/lib/security/credentials/credentials.h" +#include "src/core/lib/transport/metadata.h" +#include "src/core/util/http_client/httpcli.h" +#include "src/core/util/useful.h" + +namespace grpc_core { + +// A base class for credentials that fetch tokens via an HTTP request. +// Subclasses must implement FetchToken(). +class TokenFetcherCredentials : public grpc_call_credentials { + public: + // Represents a token. + class Token : public RefCounted { + public: + Token(Slice token, Timestamp expiration) + : token_(std::move(token)), expiration_(expiration) {} + + // Returns the token's expiration time. + Timestamp ExpirationTime() const { return expiration_; } + + // Adds the token to the call's client initial metadata. + void AddTokenToClientInitialMetadata(ClientMetadata& metadata) const; + + private: + Slice token_; + Timestamp expiration_; + }; + + ~TokenFetcherCredentials() override; + + void Orphaned() override; + + ArenaPromise> GetRequestMetadata( + ClientMetadataHandle initial_metadata, + const GetRequestMetadataArgs* args) override; + + protected: + // Base class for fetch requests. + class FetchRequest : public InternallyRefCounted {}; + + TokenFetcherCredentials(); + + // Fetches a token. The on_done callback will be invoked when complete. + virtual OrphanablePtr FetchToken( + Timestamp deadline, + absl::AnyInvocable>)> + on_done) = 0; + + grpc_polling_entity* pollent() { return &pollent_; } + + private: + // A call that is waiting for a token fetch request to complete. + struct PendingCall : public RefCounted { + std::atomic done{false}; + Waker waker; + grpc_polling_entity* pollent; + ClientMetadataHandle md; + absl::StatusOr> result; + }; + + int cmp_impl(const grpc_call_credentials* other) const override { + // TODO(yashykt): Check if we can do something better here + return QsortCompare(static_cast(this), other); + } + + void TokenFetchComplete(absl::StatusOr> token); + + Mutex mu_; + // Either the cached token or a pending request to fetch the token. + absl::variant, OrphanablePtr> token_ + ABSL_GUARDED_BY(&mu_); + // Calls that are queued up waiting for the token. + absl::flat_hash_set> pending_calls_ + ABSL_GUARDED_BY(&mu_); + grpc_polling_entity pollent_ ABSL_GUARDED_BY(&mu_); +}; + +} // namespace grpc_core + +#endif // GRPC_SRC_CORE_LIB_SECURITY_CREDENTIALS_TOKEN_FETCHER_TOKEN_FETCHER_CREDENTIALS_H diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py index 53b0c28a869e6..74eedd1126cf2 100644 --- a/src/python/grpcio/grpc_core_dependencies.py +++ b/src/python/grpcio/grpc_core_dependencies.py @@ -644,6 +644,7 @@ 'src/core/lib/security/credentials/tls/grpc_tls_crl_provider.cc', 'src/core/lib/security/credentials/tls/tls_credentials.cc', 'src/core/lib/security/credentials/tls/tls_utils.cc', + 'src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.cc', 'src/core/lib/security/credentials/xds/xds_credentials.cc', 'src/core/lib/security/security_connector/alts/alts_security_connector.cc', 'src/core/lib/security/security_connector/fake/fake_security_connector.cc', diff --git a/test/core/filters/client_auth_filter_test.cc b/test/core/filters/client_auth_filter_test.cc index 2d842875d5af2..a8659d9f3cd94 100644 --- a/test/core/filters/client_auth_filter_test.cc +++ b/test/core/filters/client_auth_filter_test.cc @@ -58,6 +58,8 @@ class ClientAuthFilterTest : public FilterTest { : grpc_call_credentials(GRPC_SECURITY_NONE), status_(std::move(status)) {} + void Orphaned() override {} + UniqueTypeName type() const override { static UniqueTypeName::Factory kFactory("FailCallCreds"); return kFactory.Create(); diff --git a/test/core/security/BUILD b/test/core/security/BUILD index 4fec75a477cf6..e11332fb22e9f 100644 --- a/test/core/security/BUILD +++ b/test/core/security/BUILD @@ -111,6 +111,8 @@ grpc_cc_test( "//:gpr", "//:grpc", "//src/core:channel_args", + "//test/core/event_engine:event_engine_test_utils", + "//test/core/event_engine/fuzzing_event_engine", "//test/core/test_util:grpc_test_util", "//test/core/test_util:grpc_test_util_base", ], diff --git a/test/core/security/credentials_test.cc b/test/core/security/credentials_test.cc index c122a8111b5a2..fb93d522a7c9f 100644 --- a/test/core/security/credentials_test.cc +++ b/test/core/security/credentials_test.cc @@ -48,6 +48,7 @@ #include "src/core/lib/gprpp/time.h" #include "src/core/lib/gprpp/unique_type_name.h" #include "src/core/lib/iomgr/error.h" +#include "src/core/lib/iomgr/timer_manager.h" #include "src/core/lib/promise/exec_ctx_wakeup_scheduler.h" #include "src/core/lib/promise/promise.h" #include "src/core/lib/promise/seq.h" @@ -72,6 +73,8 @@ #include "src/core/util/json/json_reader.h" #include "src/core/util/string.h" #include "src/core/util/tmpfile.h" +#include "test/core/event_engine/event_engine_test_utils.h" +#include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h" #include "test/core/test_util/test_config.h" namespace grpc_core { @@ -274,7 +277,7 @@ const char invalid_aws_external_account_creds_options_credential_source_invalid_regional_cred_verification_url [] = "{\"environment_id\":\"aws1\"," "\"region_url\":\"https://169.254.169.254:5555/region_url\"," - "\"url\":\"https://169.254.169.254:5555/url_no_role_name\"," + "\"url\":\"https://169.254.169.254:5555/url\"," "\"regional_cred_verification_url\":\"invalid_regional_cred_" "verification_url\"}"; @@ -319,7 +322,14 @@ grpc_http_response http_response(int status, const char* body) { // -- Tests. -- -TEST(CredentialsTest, TestOauth2TokenFetcherCredsParsingOk) { +class CredentialsTest : public ::testing::Test { + protected: + void SetUp() override { grpc_init(); } + + void TearDown() override { grpc_shutdown_blocking(); } +}; + +TEST_F(CredentialsTest, TestOauth2TokenFetcherCredsParsingOk) { ExecCtx exec_ctx; absl::optional token_value; Duration token_lifetime; @@ -332,7 +342,7 @@ TEST(CredentialsTest, TestOauth2TokenFetcherCredsParsingOk) { grpc_http_response_destroy(&response); } -TEST(CredentialsTest, TestOauth2TokenFetcherCredsParsingBadHttpStatus) { +TEST_F(CredentialsTest, TestOauth2TokenFetcherCredsParsingBadHttpStatus) { ExecCtx exec_ctx; absl::optional token_value; Duration token_lifetime; @@ -343,7 +353,7 @@ TEST(CredentialsTest, TestOauth2TokenFetcherCredsParsingBadHttpStatus) { grpc_http_response_destroy(&response); } -TEST(CredentialsTest, TestOauth2TokenFetcherCredsParsingEmptyHttpBody) { +TEST_F(CredentialsTest, TestOauth2TokenFetcherCredsParsingEmptyHttpBody) { ExecCtx exec_ctx; absl::optional token_value; Duration token_lifetime; @@ -354,7 +364,7 @@ TEST(CredentialsTest, TestOauth2TokenFetcherCredsParsingEmptyHttpBody) { grpc_http_response_destroy(&response); } -TEST(CredentialsTest, TestOauth2TokenFetcherCredsParsingInvalidJson) { +TEST_F(CredentialsTest, TestOauth2TokenFetcherCredsParsingInvalidJson) { ExecCtx exec_ctx; absl::optional token_value; Duration token_lifetime; @@ -369,7 +379,7 @@ TEST(CredentialsTest, TestOauth2TokenFetcherCredsParsingInvalidJson) { grpc_http_response_destroy(&response); } -TEST(CredentialsTest, TestOauth2TokenFetcherCredsParsingMissingToken) { +TEST_F(CredentialsTest, TestOauth2TokenFetcherCredsParsingMissingToken) { ExecCtx exec_ctx; absl::optional token_value; Duration token_lifetime; @@ -383,7 +393,7 @@ TEST(CredentialsTest, TestOauth2TokenFetcherCredsParsingMissingToken) { grpc_http_response_destroy(&response); } -TEST(CredentialsTest, TestOauth2TokenFetcherCredsParsingMissingTokenType) { +TEST_F(CredentialsTest, TestOauth2TokenFetcherCredsParsingMissingTokenType) { ExecCtx exec_ctx; absl::optional token_value; Duration token_lifetime; @@ -398,7 +408,8 @@ TEST(CredentialsTest, TestOauth2TokenFetcherCredsParsingMissingTokenType) { grpc_http_response_destroy(&response); } -TEST(CredentialsTest, TestOauth2TokenFetcherCredsParsingMissingTokenLifetime) { +TEST_F(CredentialsTest, + TestOauth2TokenFetcherCredsParsingMissingTokenLifetime) { ExecCtx exec_ctx; absl::optional token_value; Duration token_lifetime; @@ -494,18 +505,15 @@ class RequestMetadataState : public RefCounted { }; void CheckRequestMetadata(grpc_error_handle error) { - LOG(INFO) << "expected_error: " << StatusToString(expected_error_); - LOG(INFO) << "actual_error: " << StatusToString(error); if (expected_error_.ok()) { - CHECK_OK(error); + ASSERT_TRUE(error.ok()) << error; } else { std::string expected_error; - CHECK(grpc_error_get_str(expected_error_, StatusStrProperty::kDescription, - &expected_error)); + grpc_error_get_str(expected_error_, StatusStrProperty::kDescription, + &expected_error); std::string actual_error; - CHECK(grpc_error_get_str(error, StatusStrProperty::kDescription, - &actual_error)); - CHECK(expected_error == actual_error); + grpc_error_get_str(error, StatusStrProperty::kDescription, &actual_error); + EXPECT_EQ(expected_error, actual_error); } md_.Remove(HttpAuthorityMetadata()); md_.Remove(HttpPathMetadata()); @@ -522,7 +530,7 @@ class RequestMetadataState : public RefCounted { ActivityPtr activity_; }; -TEST(CredentialsTest, TestGoogleIamCreds) { +TEST_F(CredentialsTest, TestGoogleIamCreds) { ExecCtx exec_ctx; auto state = RequestMetadataState::NewInstance( absl::OkStatus(), @@ -540,7 +548,7 @@ TEST(CredentialsTest, TestGoogleIamCreds) { creds->Unref(); } -TEST(CredentialsTest, TestAccessTokenCreds) { +TEST_F(CredentialsTest, TestAccessTokenCreds) { ExecCtx exec_ctx; auto state = RequestMetadataState::NewInstance(absl::OkStatus(), "authorization: Bearer blah"); @@ -580,7 +588,7 @@ class check_channel_oauth2 final : public grpc_channel_credentials { } }; -TEST(CredentialsTest, TestChannelOauth2CompositeCreds) { +TEST_F(CredentialsTest, TestChannelOauth2CompositeCreds) { ExecCtx exec_ctx; ChannelArgs new_args; grpc_channel_credentials* channel_creds = new check_channel_oauth2(); @@ -595,7 +603,7 @@ TEST(CredentialsTest, TestChannelOauth2CompositeCreds) { grpc_channel_credentials_release(channel_oauth2_creds); } -TEST(CredentialsTest, TestOauth2GoogleIamCompositeCreds) { +TEST_F(CredentialsTest, TestOauth2GoogleIamCompositeCreds) { ExecCtx exec_ctx; auto state = RequestMetadataState::NewInstance( absl::OkStatus(), @@ -665,7 +673,7 @@ class check_channel_oauth2_google_iam final : public grpc_channel_credentials { } }; -TEST(CredentialsTest, TestChannelOauth2GoogleIamCompositeCreds) { +TEST_F(CredentialsTest, TestChannelOauth2GoogleIamCompositeCreds) { ExecCtx exec_ctx; ChannelArgs new_args; grpc_channel_credentials* channel_creds = @@ -749,7 +757,7 @@ int httpcli_put_should_not_be_called(const grpc_http_request* /*request*/, return 1; } -TEST(CredentialsTest, TestComputeEngineCredsSuccess) { +TEST_F(CredentialsTest, TestComputeEngineCredsSuccess) { ExecCtx exec_ctx; std::string emd = "authorization: Bearer ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_"; const char expected_creds_debug_string[] = @@ -784,13 +792,13 @@ TEST(CredentialsTest, TestComputeEngineCredsSuccess) { HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, TestComputeEngineCredsFailure) { +TEST_F(CredentialsTest, TestComputeEngineCredsFailure) { ExecCtx exec_ctx; const char expected_creds_debug_string[] = "GoogleComputeEngineTokenFetcherCredentials{" "OAuth2TokenFetcherCredentials}"; auto state = RequestMetadataState::NewInstance( - GRPC_ERROR_CREATE("Error occurred when fetching oauth2 token."), {}); + GRPC_ERROR_CREATE("error parsing oauth2 token"), {}); grpc_call_credentials* creds = grpc_google_compute_engine_credentials_create(nullptr); HttpRequest::SetOverride(compute_engine_httpcli_get_failure_override, @@ -840,7 +848,7 @@ int token_httpcli_post_failure(const grpc_http_request* /*request*/, return 1; } -TEST(CredentialsTest, TestRefreshTokenCredsSuccess) { +TEST_F(CredentialsTest, TestRefreshTokenCredsSuccess) { ExecCtx exec_ctx; std::string emd = "authorization: Bearer ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_"; const char expected_creds_debug_string[] = @@ -876,13 +884,13 @@ TEST(CredentialsTest, TestRefreshTokenCredsSuccess) { HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, TestRefreshTokenCredsFailure) { +TEST_F(CredentialsTest, TestRefreshTokenCredsFailure) { ExecCtx exec_ctx; const char expected_creds_debug_string[] = "GoogleRefreshToken{ClientID:32555999999.apps.googleusercontent.com," "OAuth2TokenFetcherCredentials}"; auto state = RequestMetadataState::NewInstance( - GRPC_ERROR_CREATE("Error occurred when fetching oauth2 token."), {}); + GRPC_ERROR_CREATE("error parsing oauth2 token"), {}); grpc_call_credentials* creds = grpc_google_refresh_token_credentials_create( test_refresh_token_str, nullptr); HttpRequest::SetOverride(httpcli_get_should_not_be_called, @@ -897,7 +905,7 @@ TEST(CredentialsTest, TestRefreshTokenCredsFailure) { HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, TestValidStsCredsOptions) { +TEST_F(CredentialsTest, TestValidStsCredsOptions) { grpc_sts_credentials_options valid_options = { test_sts_endpoint_url, // sts_endpoint_url nullptr, // resource @@ -918,7 +926,7 @@ TEST(CredentialsTest, TestValidStsCredsOptions) { CHECK(port == "5555"); } -TEST(CredentialsTest, TestInvalidStsCredsOptions) { +TEST_F(CredentialsTest, TestInvalidStsCredsOptions) { grpc_sts_credentials_options invalid_options = { test_sts_endpoint_url, // sts_endpoint_url nullptr, // resource @@ -1071,7 +1079,7 @@ char* write_tmp_jwt_file(const char* jwt_contents) { return path; } -TEST(CredentialsTest, TestStsCredsSuccess) { +TEST_F(CredentialsTest, TestStsCredsSuccess) { ExecCtx exec_ctx; std::string emd = "authorization: Bearer ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_"; const char expected_creds_debug_string[] = @@ -1122,7 +1130,7 @@ TEST(CredentialsTest, TestStsCredsSuccess) { gpr_free(actor_token_path); } -TEST(CredentialsTest, TestStsCredsTokenFileNotFound) { +TEST_F(CredentialsTest, TestStsCredsTokenFileNotFound) { ExecCtx exec_ctx; grpc_sts_credentials_options valid_options = { test_sts_endpoint_url, // sts_endpoint_url @@ -1142,7 +1150,10 @@ TEST(CredentialsTest, TestStsCredsTokenFileNotFound) { CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( - GRPC_ERROR_CREATE("Error occurred when fetching oauth2 token."), {}); + GRPC_ERROR_CREATE( + "Failed to load file: /some/completely/random/path due to " + "error(fdopen): No such file or directory"), + {}); HttpRequest::SetOverride(httpcli_get_should_not_be_called, httpcli_post_should_not_be_called, httpcli_put_should_not_be_called); @@ -1155,7 +1166,7 @@ TEST(CredentialsTest, TestStsCredsTokenFileNotFound) { HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, TestStsCredsNoActorTokenSuccess) { +TEST_F(CredentialsTest, TestStsCredsNoActorTokenSuccess) { ExecCtx exec_ctx; std::string emd = "authorization: Bearer ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_"; const char expected_creds_debug_string[] = @@ -1204,13 +1215,15 @@ TEST(CredentialsTest, TestStsCredsNoActorTokenSuccess) { gpr_free(subject_token_path); } -TEST(CredentialsTest, TestStsCredsLoadTokenFailure) { +TEST_F(CredentialsTest, TestStsCredsLoadTokenFailure) { const char expected_creds_debug_string[] = "StsTokenFetcherCredentials{Path:/v1/" "token-exchange,Authority:foo.com:5555,OAuth2TokenFetcherCredentials}"; ExecCtx exec_ctx; auto state = RequestMetadataState::NewInstance( - GRPC_ERROR_CREATE("Error occurred when fetching oauth2 token."), {}); + GRPC_ERROR_CREATE("Failed to load file: invalid_path due to " + "error(fdopen): No such file or directory"), + {}); char* test_signed_jwt_path = write_tmp_jwt_file(test_signed_jwt); grpc_sts_credentials_options options = { test_sts_endpoint_url, // sts_endpoint_url @@ -1237,13 +1250,13 @@ TEST(CredentialsTest, TestStsCredsLoadTokenFailure) { gpr_free(test_signed_jwt_path); } -TEST(CredentialsTest, TestStsCredsHttpFailure) { +TEST_F(CredentialsTest, TestStsCredsHttpFailure) { const char expected_creds_debug_string[] = "StsTokenFetcherCredentials{Path:/v1/" "token-exchange,Authority:foo.com:5555,OAuth2TokenFetcherCredentials}"; ExecCtx exec_ctx; auto state = RequestMetadataState::NewInstance( - GRPC_ERROR_CREATE("Error occurred when fetching oauth2 token."), {}); + GRPC_ERROR_CREATE("error parsing oauth2 token"), {}); char* test_signed_jwt_path = write_tmp_jwt_file(test_signed_jwt); grpc_sts_credentials_options valid_options = { test_sts_endpoint_url, // sts_endpoint_url @@ -1333,7 +1346,7 @@ grpc_service_account_jwt_access_credentials* creds_as_jwt( return reinterpret_cast(creds); } -TEST(CredentialsTest, TestJwtCredsLifetime) { +TEST_F(CredentialsTest, TestJwtCredsLifetime) { char* json_key_string = test_json_key_str(); const char expected_creds_debug_string_prefix[] = "JWTAccessCredentials{ExpirationTime:"; @@ -1380,7 +1393,7 @@ TEST(CredentialsTest, TestJwtCredsLifetime) { gpr_free(json_key_string); } -TEST(CredentialsTest, TestRemoveServiceFromJwtUri) { +TEST_F(CredentialsTest, TestRemoveServiceFromJwtUri) { const char wrong_uri[] = "hello world"; CHECK(!RemoveServiceNameFromJwtUri(wrong_uri).ok()); const char valid_uri[] = "https://foo.com/get/"; @@ -1390,7 +1403,7 @@ TEST(CredentialsTest, TestRemoveServiceFromJwtUri) { CHECK_EQ(strcmp(output->c_str(), expected_uri), 0); } -TEST(CredentialsTest, TestJwtCredsSuccess) { +TEST_F(CredentialsTest, TestJwtCredsSuccess) { const char expected_creds_debug_string_prefix[] = "JWTAccessCredentials{ExpirationTime:"; @@ -1434,7 +1447,7 @@ TEST(CredentialsTest, TestJwtCredsSuccess) { grpc_jwt_encode_and_sign_set_override(nullptr); } -TEST(CredentialsTest, TestJwtCredsSigningFailure) { +TEST_F(CredentialsTest, TestJwtCredsSigningFailure) { const char expected_creds_debug_string_prefix[] = "JWTAccessCredentials{ExpirationTime:"; char* json_key_string = test_json_key_str(); @@ -1479,7 +1492,7 @@ bool test_gce_tenancy_checker(void) { std::string null_well_known_creds_path_getter(void) { return ""; } -TEST(CredentialsTest, TestGoogleDefaultCredsAuthKey) { +TEST_F(CredentialsTest, TestGoogleDefaultCredsAuthKey) { ExecCtx exec_ctx; grpc_composite_channel_credentials* creds; char* json_key = test_json_key_str(); @@ -1511,7 +1524,7 @@ TEST(CredentialsTest, TestGoogleDefaultCredsAuthKey) { grpc_override_well_known_credentials_path_getter(nullptr); } -TEST(CredentialsTest, TestGoogleDefaultCredsRefreshToken) { +TEST_F(CredentialsTest, TestGoogleDefaultCredsRefreshToken) { ExecCtx exec_ctx; grpc_composite_channel_credentials* creds; grpc_flush_cached_google_default_credentials(); @@ -1536,7 +1549,8 @@ TEST(CredentialsTest, TestGoogleDefaultCredsRefreshToken) { grpc_override_well_known_credentials_path_getter(nullptr); } -TEST(CredentialsTest, TestGoogleDefaultCredsExternalAccountCredentialsPscSts) { +TEST_F(CredentialsTest, + TestGoogleDefaultCredsExternalAccountCredentialsPscSts) { ExecCtx exec_ctx; grpc_composite_channel_credentials* creds; grpc_flush_cached_google_default_credentials(); @@ -1559,7 +1573,8 @@ TEST(CredentialsTest, TestGoogleDefaultCredsExternalAccountCredentialsPscSts) { grpc_override_well_known_credentials_path_getter(nullptr); } -TEST(CredentialsTest, TestGoogleDefaultCredsExternalAccountCredentialsPscIam) { +TEST_F(CredentialsTest, + TestGoogleDefaultCredsExternalAccountCredentialsPscIam) { ExecCtx exec_ctx; grpc_composite_channel_credentials* creds; grpc_flush_cached_google_default_credentials(); @@ -1599,7 +1614,7 @@ int default_creds_metadata_server_detection_httpcli_get_success_override( return 1; } -TEST(CredentialsTest, TestGoogleDefaultCredsGce) { +TEST_F(CredentialsTest, TestGoogleDefaultCredsGce) { ExecCtx exec_ctx; auto state = RequestMetadataState::NewInstance( absl::OkStatus(), @@ -1635,7 +1650,7 @@ TEST(CredentialsTest, TestGoogleDefaultCredsGce) { grpc_override_well_known_credentials_path_getter(nullptr); } -TEST(CredentialsTest, TestGoogleDefaultCredsNonGce) { +TEST_F(CredentialsTest, TestGoogleDefaultCredsNonGce) { ExecCtx exec_ctx; auto state = RequestMetadataState::NewInstance( absl::OkStatus(), @@ -1682,7 +1697,7 @@ int default_creds_gce_detection_httpcli_get_failure_override( return 1; } -TEST(CredentialsTest, TestNoGoogleDefaultCreds) { +TEST_F(CredentialsTest, TestNoGoogleDefaultCreds) { grpc_flush_cached_google_default_credentials(); SetEnv(GRPC_GOOGLE_CREDENTIALS_ENV_VAR, ""); // Reset. grpc_override_well_known_credentials_path_getter( @@ -1704,7 +1719,7 @@ TEST(CredentialsTest, TestNoGoogleDefaultCreds) { HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, TestGoogleDefaultCredsCallCredsSpecified) { +TEST_F(CredentialsTest, TestGoogleDefaultCredsCallCredsSpecified) { auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_"); @@ -1736,6 +1751,8 @@ TEST(CredentialsTest, TestGoogleDefaultCredsCallCredsSpecified) { struct fake_call_creds : public grpc_call_credentials { public: + void Orphaned() override {} + ArenaPromise> GetRequestMetadata( ClientMetadataHandle initial_metadata, const grpc_call_credentials::GetRequestMetadataArgs*) override { @@ -1756,7 +1773,7 @@ struct fake_call_creds : public grpc_call_credentials { } }; -TEST(CredentialsTest, TestGoogleDefaultCredsNotDefault) { +TEST_F(CredentialsTest, TestGoogleDefaultCredsNotDefault) { auto state = RequestMetadataState::NewInstance(absl::OkStatus(), "foo: oof"); ExecCtx exec_ctx; grpc_flush_cached_google_default_credentials(); @@ -1858,7 +1875,7 @@ char* plugin_debug_string(void* state) { return ret; } -TEST(CredentialsTest, TestMetadataPluginSuccess) { +TEST_F(CredentialsTest, TestMetadataPluginSuccess) { const char expected_creds_debug_string[] = "TestPluginCredentials{state:GET_METADATA_CALLED}"; plugin_state state = PLUGIN_INITIAL_STATE; @@ -1887,7 +1904,7 @@ TEST(CredentialsTest, TestMetadataPluginSuccess) { CHECK_EQ(state, PLUGIN_DESTROY_CALLED_STATE); } -TEST(CredentialsTest, TestMetadataPluginFailure) { +TEST_F(CredentialsTest, TestMetadataPluginFailure) { const char expected_creds_debug_string[] = "TestPluginCredentials{state:GET_METADATA_CALLED}"; @@ -1918,7 +1935,7 @@ TEST(CredentialsTest, TestMetadataPluginFailure) { CHECK_EQ(state, PLUGIN_DESTROY_CALLED_STATE); } -TEST(CredentialsTest, TestGetWellKnownGoogleCredentialsFilePath) { +TEST_F(CredentialsTest, TestGetWellKnownGoogleCredentialsFilePath) { auto home = GetEnv("HOME"); bool restore_home_env = false; #if defined(GRPC_BAZEL_BUILD) && \ @@ -1942,7 +1959,7 @@ TEST(CredentialsTest, TestGetWellKnownGoogleCredentialsFilePath) { } } -TEST(CredentialsTest, TestChannelCredsDuplicateWithoutCallCreds) { +TEST_F(CredentialsTest, TestChannelCredsDuplicateWithoutCallCreds) { const char expected_creds_debug_string[] = "AccessTokenCredentials{Token:present}"; ExecCtx exec_ctx; @@ -2022,7 +2039,7 @@ void auth_metadata_context_build(const char* url_scheme, gpr_free(host_and_port); } -TEST(CredentialsTest, TestAuthMetadataContext) { +TEST_F(CredentialsTest, TestAuthMetadataContext) { auth_metadata_context_test_case test_cases[] = { // No service nor method. {"https", "www.foo.com", "", "https://www.foo.com", ""}, @@ -2357,21 +2374,34 @@ int aws_external_account_creds_httpcli_post_success( // against it. class TestExternalAccountCredentials final : public ExternalAccountCredentials { public: - TestExternalAccountCredentials(Options options, - std::vector scopes) - : ExternalAccountCredentials(std::move(options), std::move(scopes)) {} + TestExternalAccountCredentials( + Options options, std::vector scopes, + std::shared_ptr + event_engine = nullptr) + : ExternalAccountCredentials(std::move(options), std::move(scopes), + std::move(event_engine)) {} std::string GetMetricsValue() { return MetricsHeaderValue(); } - protected: - void RetrieveSubjectToken( - HTTPRequestContext* /*ctx*/, const Options& /*options*/, - std::function cb) override { - cb("test_subject_token", absl::OkStatus()); + std::string debug_string() override { + return "TestExternalAccountCredentials"; + } + + UniqueTypeName type() const override { + static UniqueTypeName::Factory kFactory("TestExternalAccountCredentials"); + return kFactory.Create(); + } + + private: + OrphanablePtr RetrieveSubjectToken( + Timestamp /*deadline*/, + absl::AnyInvocable)> on_done) override { + return MakeOrphanable(event_engine(), std::move(on_done), + "test_subject_token"); } }; -TEST(CredentialsTest, TestExternalAccountCredsMetricsHeader) { +TEST_F(CredentialsTest, TestExternalAccountCredsMetricsHeader) { Json credential_source = Json::FromString(""); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; @@ -2391,7 +2421,6 @@ TEST(CredentialsTest, TestExternalAccountCredsMetricsHeader) { "", // workforce_pool_user_project; }; TestExternalAccountCredentials creds(options, {}); - EXPECT_EQ( creds.GetMetricsValue(), absl::StrFormat("gl-cpp/unknown auth/%s google-byoid-sdk source/unknown " @@ -2399,8 +2428,8 @@ TEST(CredentialsTest, TestExternalAccountCredsMetricsHeader) { grpc_version_string())); } -TEST(CredentialsTest, - TestExternalAccountCredsMetricsHeaderWithServiceAccountImpersonation) { +TEST_F(CredentialsTest, + TestExternalAccountCredsMetricsHeaderWithServiceAccountImpersonation) { Json credential_source = Json::FromString(""); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; @@ -2420,7 +2449,6 @@ TEST(CredentialsTest, "", // workforce_pool_user_project; }; TestExternalAccountCredentials creds(options, {}); - EXPECT_EQ( creds.GetMetricsValue(), absl::StrFormat("gl-cpp/unknown auth/%s google-byoid-sdk source/unknown " @@ -2428,7 +2456,8 @@ TEST(CredentialsTest, grpc_version_string())); } -TEST(CredentialsTest, TestExternalAccountCredsMetricsHeaderWithConfigLifetime) { +TEST_F(CredentialsTest, + TestExternalAccountCredsMetricsHeaderWithConfigLifetime) { Json credential_source = Json::FromString(""); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; @@ -2448,7 +2477,6 @@ TEST(CredentialsTest, TestExternalAccountCredsMetricsHeaderWithConfigLifetime) { "", // workforce_pool_user_project; }; TestExternalAccountCredentials creds(options, {}); - EXPECT_EQ( creds.GetMetricsValue(), absl::StrFormat("gl-cpp/unknown auth/%s google-byoid-sdk source/unknown " @@ -2456,7 +2484,30 @@ TEST(CredentialsTest, TestExternalAccountCredsMetricsHeaderWithConfigLifetime) { grpc_version_string())); } -TEST(CredentialsTest, TestExternalAccountCredsSuccess) { +using grpc_event_engine::experimental::FuzzingEventEngine; + +class ExternalAccountCredentialsTest : public ::testing::Test { + protected: + void SetUp() override { + grpc_timer_manager_set_start_threaded(false); + grpc_init(); + } + + void TearDown() override { + event_engine_->FuzzingDone(); + event_engine_->TickUntilIdle(); + event_engine_->UnsetGlobalHooks(); + grpc_event_engine::experimental::WaitForSingleOwner( + std::move(event_engine_)); + grpc_shutdown_blocking(); + } + + std::shared_ptr event_engine_ = + std::make_shared(FuzzingEventEngine::Options(), + fuzzing_event_engine::Actions()); +}; + +TEST_F(ExternalAccountCredentialsTest, Success) { ExecCtx exec_ctx; Json credential_source = Json::FromString(""); TestExternalAccountCredentials::ServiceAccountImpersonation @@ -2476,31 +2527,34 @@ TEST(CredentialsTest, TestExternalAccountCredsSuccess) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - TestExternalAccountCredentials creds(options, {}); + auto creds = MakeRefCounted( + options, std::vector(), event_engine_); // Check security level. - CHECK_EQ(creds.min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); - // First request: http put should be called. + EXPECT_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + // First request: http post should be called. auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(httpcli_get_should_not_be_called, external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(&creds, kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); // Second request: the cached token should be served directly. state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(httpcli_get_should_not_be_called, httpcli_post_should_not_be_called, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(&creds, kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, TestExternalAccountCredsSuccessWithUrlEncode) { +TEST_F(ExternalAccountCredentialsTest, SuccessWithUrlEncode) { std::map emd = { {"authorization", "Bearer token_exchange_access_token"}}; ExecCtx exec_ctx; @@ -2522,20 +2576,21 @@ TEST(CredentialsTest, TestExternalAccountCredsSuccessWithUrlEncode) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - TestExternalAccountCredentials creds(options, {}); + auto creds = MakeRefCounted( + options, std::vector(), event_engine_); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(httpcli_get_should_not_be_called, external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(&creds, kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, - TestExternalAccountCredsSuccessWithServiceAccountImpersonation) { +TEST_F(ExternalAccountCredentialsTest, SuccessWithServiceAccountImpersonation) { ExecCtx exec_ctx; Json credential_source = Json::FromString(""); TestExternalAccountCredentials::ServiceAccountImpersonation @@ -2555,9 +2610,10 @@ TEST(CredentialsTest, "client_secret", // client_secret; "", // workforce_pool_user_project; }; - TestExternalAccountCredentials creds(options, {"scope_1", "scope_2"}); + auto creds = MakeRefCounted( + options, std::vector{"scope_1", "scope_2"}, event_engine_); // Check security level. - CHECK_EQ(creds.min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + EXPECT_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); // First request: http put should be called. auto state = RequestMetadataState::NewInstance( absl::OkStatus(), @@ -2565,15 +2621,15 @@ TEST(CredentialsTest, HttpRequest::SetOverride(httpcli_get_should_not_be_called, external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(&creds, kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST( - CredentialsTest, - TestExternalAccountCredsSuccessWithServiceAccountImpersonationAndCustomTokenLifetime) { +TEST_F(ExternalAccountCredentialsTest, + SuccessWithServiceAccountImpersonationAndCustomTokenLifetime) { ExecCtx exec_ctx; Json credential_source = Json::FromString(""); TestExternalAccountCredentials::ServiceAccountImpersonation @@ -2593,9 +2649,10 @@ TEST( "client_secret", // client_secret; "", // workforce_pool_user_project; }; - TestExternalAccountCredentials creds(options, {"scope_1", "scope_2"}); + auto creds = MakeRefCounted( + options, std::vector{"scope_1", "scope_2"}, event_engine_); // Check security level. - CHECK_EQ(creds.min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + EXPECT_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); // First request: http put should be called. auto state = RequestMetadataState::NewInstance( absl::OkStatus(), @@ -2604,15 +2661,15 @@ TEST( httpcli_get_should_not_be_called, external_acc_creds_serv_acc_imp_custom_lifetime_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(&creds, kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST( - CredentialsTest, - TestExternalAccountCredsFailureWithServiceAccountImpersonationAndInvalidCustomTokenLifetime) { +TEST_F(ExternalAccountCredentialsTest, + FailureWithServiceAccountImpersonationAndInvalidCustomTokenLifetime) { const char* options_string1 = "{\"type\":\"external_account\",\"audience\":\"audience\"," "\"subject_token_type\":\"subject_token_type\"," @@ -2627,14 +2684,13 @@ TEST( "\"format\":{\"type\":\"json\",\"subject_token_field_name\":\"access_" "token\"}},\"quota_project_id\":\"quota_project_id\"," "\"client_id\":\"client_id\",\"client_secret\":\"client_secret\"}"; - grpc_error_handle error1, error2; - auto json1 = JsonParse(options_string1); - CHECK_OK(json1); - ExternalAccountCredentials::Create(*json1, {"scope1", "scope2"}, &error1); - std::string actual_error1, - expected_error1 = "token_lifetime_seconds must be more than 600s"; - grpc_error_get_str(error1, StatusStrProperty::kDescription, &actual_error1); - CHECK_EQ(strcmp(actual_error1.c_str(), expected_error1.c_str()), 0); + auto json = JsonParse(options_string1); + ASSERT_TRUE(json.ok()) << json.status(); + auto creds = ExternalAccountCredentials::Create(*json, {"scope1", "scope2"}); + std::string actual_error; + grpc_error_get_str(creds.status(), StatusStrProperty::kDescription, + &actual_error); + EXPECT_EQ("token_lifetime_seconds must be more than 600s", actual_error); const char* options_string2 = "{\"type\":\"external_account\",\"audience\":\"audience\"," @@ -2650,16 +2706,15 @@ TEST( "\"format\":{\"type\":\"json\",\"subject_token_field_name\":\"access_" "token\"}},\"quota_project_id\":\"quota_project_id\"," "\"client_id\":\"client_id\",\"client_secret\":\"client_secret\"}"; - auto json2 = JsonParse(options_string2); - CHECK_OK(json2); - ExternalAccountCredentials::Create(*json2, {"scope1", "scope2"}, &error2); - std::string actual_error2, - expected_error2 = "token_lifetime_seconds must be less than 43200s"; - grpc_error_get_str(error2, StatusStrProperty::kDescription, &actual_error2); - CHECK_EQ(strcmp(actual_error2.c_str(), expected_error2.c_str()), 0); + json = JsonParse(options_string2); + ASSERT_TRUE(json.ok()) << json.status(); + creds = ExternalAccountCredentials::Create(*json, {"scope1", "scope2"}); + grpc_error_get_str(creds.status(), StatusStrProperty::kDescription, + &actual_error); + EXPECT_EQ("token_lifetime_seconds must be less than 43200s", actual_error); } -TEST(CredentialsTest, TestExternalAccountCredsFailureInvalidTokenUrl) { +TEST_F(ExternalAccountCredentialsTest, FailureInvalidTokenUrl) { ExecCtx exec_ctx; Json credential_source = Json::FromString(""); TestExternalAccountCredentials::ServiceAccountImpersonation @@ -2679,23 +2734,25 @@ TEST(CredentialsTest, TestExternalAccountCredsFailureInvalidTokenUrl) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - TestExternalAccountCredentials creds(options, {}); + auto creds = MakeRefCounted( + options, std::vector(), event_engine_); HttpRequest::SetOverride(httpcli_get_should_not_be_called, httpcli_post_should_not_be_called, httpcli_put_should_not_be_called); - grpc_error_handle error = - GRPC_ERROR_CREATE("Invalid token url: invalid_token_url."); - grpc_error_handle expected_error = GRPC_ERROR_CREATE_REFERENCING( - "Error occurred when fetching oauth2 token.", &error, 1); + grpc_error_handle expected_error = GRPC_ERROR_CREATE( + "error fetching oauth2 token: Invalid token url: " + "invalid_token_url. Error: INVALID_ARGUMENT: Could not parse " + "'scheme' from uri 'invalid_token_url'. Scheme not found."); auto state = RequestMetadataState::NewInstance(expected_error, {}); - state->RunRequestMetadataTest(&creds, kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, - TestExternalAccountCredsFailureInvalidServiceAccountImpersonationUrl) { +TEST_F(ExternalAccountCredentialsTest, + FailureInvalidServiceAccountImpersonationUrl) { ExecCtx exec_ctx; Json credential_source = Json::FromString(""); TestExternalAccountCredentials::ServiceAccountImpersonation @@ -2715,24 +2772,26 @@ TEST(CredentialsTest, "client_secret", // client_secret; "", // workforce_pool_user_project; }; - TestExternalAccountCredentials creds(options, {}); + auto creds = MakeRefCounted( + options, std::vector(), event_engine_); HttpRequest::SetOverride(httpcli_get_should_not_be_called, external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - grpc_error_handle error = GRPC_ERROR_CREATE( - "Invalid service account impersonation url: " - "invalid_service_account_impersonation_url."); - grpc_error_handle expected_error = GRPC_ERROR_CREATE_REFERENCING( - "Error occurred when fetching oauth2 token.", &error, 1); + grpc_error_handle expected_error = GRPC_ERROR_CREATE( + "error fetching oauth2 token: Invalid service account impersonation url: " + "invalid_service_account_impersonation_url. Error: INVALID_ARGUMENT: " + "Could not parse 'scheme' from uri " + "'invalid_service_account_impersonation_url'. Scheme not found."); auto state = RequestMetadataState::NewInstance(expected_error, {}); - state->RunRequestMetadataTest(&creds, kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, - TestExternalAccountCredsFailureTokenExchangeResponseMissingAccessToken) { +TEST_F(ExternalAccountCredentialsTest, + FailureTokenExchangeResponseMissingAccessToken) { ExecCtx exec_ctx; Json credential_source = Json::FromString(""); TestExternalAccountCredentials::ServiceAccountImpersonation @@ -2752,29 +2811,30 @@ TEST(CredentialsTest, "client_secret", // client_secret; "", // workforce_pool_user_project; }; - TestExternalAccountCredentials creds(options, {}); + auto creds = MakeRefCounted( + options, std::vector(), event_engine_); HttpRequest::SetOverride( httpcli_get_should_not_be_called, external_account_creds_httpcli_post_failure_token_exchange_response_missing_access_token, httpcli_put_should_not_be_called); - grpc_error_handle error = GRPC_ERROR_CREATE( - "Missing or invalid access_token in " - "{\"not_access_token\":\"not_access_token\",\"expires_in\":3599,\"token_" - "type\":\"Bearer\"}."); - grpc_error_handle expected_error = GRPC_ERROR_CREATE_REFERENCING( - "Error occurred when fetching oauth2 token.", &error, 1); + grpc_error_handle expected_error = GRPC_ERROR_CREATE( + "error fetching oauth2 token: Missing or invalid access_token in " + "{\"not_access_token\":\"not_access_token\",\"expires_in\":3599, " + "\"token_type\":\"Bearer\"}."); auto state = RequestMetadataState::NewInstance(expected_error, {}); - state->RunRequestMetadataTest(&creds, kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, TestUrlExternalAccountCredsSuccessFormatText) { +TEST_F(ExternalAccountCredentialsTest, + UrlExternalAccountCredsSuccessFormatText) { ExecCtx exec_ctx; auto credential_source = JsonParse( valid_url_external_account_creds_options_credential_source_format_text); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -2792,30 +2852,31 @@ TEST(CredentialsTest, TestUrlExternalAccountCredsSuccessFormatText) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = UrlExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + UrlExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(url_external_account_creds_httpcli_get_success, external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, - TestUrlExternalAccountCredsSuccessWithQureyParamsFormatText) { +TEST_F(ExternalAccountCredentialsTest, + UrlExternalAccountCredsSuccessWithQureyParamsFormatText) { std::map emd = { {"authorization", "Bearer token_exchange_access_token"}}; ExecCtx exec_ctx; auto credential_source = JsonParse( valid_url_external_account_creds_options_credential_source_with_qurey_params_format_text); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -2833,27 +2894,29 @@ TEST(CredentialsTest, "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = UrlExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + UrlExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(url_external_account_creds_httpcli_get_success, external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, TestUrlExternalAccountCredsSuccessFormatJson) { +TEST_F(ExternalAccountCredentialsTest, + UrlExternalAccountCredsSuccessFormatJson) { ExecCtx exec_ctx; auto credential_source = JsonParse( valid_url_external_account_creds_options_credential_source_format_json); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -2871,27 +2934,28 @@ TEST(CredentialsTest, TestUrlExternalAccountCredsSuccessFormatJson) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = UrlExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + UrlExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(url_external_account_creds_httpcli_get_success, external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, - TestUrlExternalAccountCredsFailureInvalidCredentialSourceUrl) { +TEST_F(ExternalAccountCredentialsTest, + UrlExternalAccountCredsFailureInvalidCredentialSourceUrl) { auto credential_source = JsonParse(invalid_url_external_account_creds_options_credential_source); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -2909,22 +2973,23 @@ TEST(CredentialsTest, "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = UrlExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds == nullptr); + auto creds = UrlExternalAccountCredentials::Create(options, {}); + ASSERT_FALSE(creds.ok()); std::string actual_error; - CHECK(grpc_error_get_str(error, StatusStrProperty::kDescription, - &actual_error)); - CHECK(absl::StartsWith(actual_error, "Invalid credential source url.")); + grpc_error_get_str(creds.status(), StatusStrProperty::kDescription, + &actual_error); + EXPECT_THAT(actual_error, + ::testing::StartsWith("Invalid credential source url.")); } -TEST(CredentialsTest, TestFileExternalAccountCredsSuccessFormatText) { +TEST_F(ExternalAccountCredentialsTest, + FileExternalAccountCredsSuccessFormatText) { ExecCtx exec_ctx; char* subject_token_path = write_tmp_jwt_file("test_subject_token"); auto credential_source = JsonParse(absl::StrFormat( "{\"file\":\"%s\"}", absl::StrReplaceAll(subject_token_path, {{"\\", "\\\\"}}))); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -2942,24 +3007,26 @@ TEST(CredentialsTest, TestFileExternalAccountCredsSuccessFormatText) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = FileExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + FileExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(httpcli_get_should_not_be_called, external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); gpr_free(subject_token_path); } -TEST(CredentialsTest, TestFileExternalAccountCredsSuccessFormatJson) { +TEST_F(ExternalAccountCredentialsTest, + FileExternalAccountCredsSuccessFormatJson) { ExecCtx exec_ctx; char* subject_token_path = write_tmp_jwt_file("{\"access_token\":\"test_subject_token\"}"); @@ -2973,7 +3040,7 @@ TEST(CredentialsTest, TestFileExternalAccountCredsSuccessFormatJson) { "}\n" "}", absl::StrReplaceAll(subject_token_path, {{"\\", "\\\\"}}))); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -2991,27 +3058,29 @@ TEST(CredentialsTest, TestFileExternalAccountCredsSuccessFormatJson) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = FileExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + FileExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(httpcli_get_should_not_be_called, external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); gpr_free(subject_token_path); } -TEST(CredentialsTest, TestFileExternalAccountCredsFailureFileNotFound) { +TEST_F(ExternalAccountCredentialsTest, + FileExternalAccountCredsFailureFileNotFound) { ExecCtx exec_ctx; auto credential_source = JsonParse("{\"file\":\"non_exisiting_file\"}"); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3029,24 +3098,26 @@ TEST(CredentialsTest, TestFileExternalAccountCredsFailureFileNotFound) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = FileExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); + auto creds = + FileExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); HttpRequest::SetOverride(httpcli_get_should_not_be_called, httpcli_post_should_not_be_called, httpcli_put_should_not_be_called); - error = GRPC_ERROR_CREATE("Failed to load file"); - grpc_error_handle expected_error = GRPC_ERROR_CREATE_REFERENCING( - "Error occurred when fetching oauth2 token.", &error, 1); + grpc_error_handle expected_error = GRPC_ERROR_CREATE( + "error fetching oauth2 token: Failed to load file: " + "non_exisiting_file due to error(fdopen): No such file or directory"); auto state = RequestMetadataState::NewInstance(expected_error, {}); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, TestFileExternalAccountCredsFailureInvalidJsonContent) { +TEST_F(ExternalAccountCredentialsTest, + FileExternalAccountCredsFailureInvalidJsonContent) { ExecCtx exec_ctx; char* subject_token_path = write_tmp_jwt_file("not_a_valid_json_file"); auto credential_source = JsonParse(absl::StrFormat( @@ -3059,7 +3130,7 @@ TEST(CredentialsTest, TestFileExternalAccountCredsFailureInvalidJsonContent) { "}\n" "}", absl::StrReplaceAll(subject_token_path, {{"\\", "\\\\"}}))); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3077,30 +3148,30 @@ TEST(CredentialsTest, TestFileExternalAccountCredsFailureInvalidJsonContent) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = FileExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); + auto creds = + FileExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); HttpRequest::SetOverride(httpcli_get_should_not_be_called, httpcli_post_should_not_be_called, httpcli_put_should_not_be_called); - error = - GRPC_ERROR_CREATE("The content of the file is not a valid json object."); - grpc_error_handle expected_error = GRPC_ERROR_CREATE_REFERENCING( - "Error occurred when fetching oauth2 token.", &error, 1); + grpc_error_handle expected_error = GRPC_ERROR_CREATE( + "error fetching oauth2 token: The content of the file is not a " + "valid json object."); auto state = RequestMetadataState::NewInstance(expected_error, {}); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); gpr_free(subject_token_path); } -TEST(CredentialsTest, TestAwsExternalAccountCredsSuccess) { +TEST_F(ExternalAccountCredentialsTest, AwsExternalAccountCredsSuccess) { ExecCtx exec_ctx; auto credential_source = JsonParse(valid_aws_external_account_creds_options_credential_source); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3118,27 +3189,28 @@ TEST(CredentialsTest, TestAwsExternalAccountCredsSuccess) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = AwsExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + AwsExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success, aws_external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, TestAwsImdsv2ExternalAccountCredsSuccess) { +TEST_F(ExternalAccountCredentialsTest, AwsImdsv2ExternalAccountCredsSuccess) { ExecCtx exec_ctx; auto credential_source = JsonParse( valid_aws_imdsv2_external_account_creds_options_credential_source); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3156,25 +3228,26 @@ TEST(CredentialsTest, TestAwsImdsv2ExternalAccountCredsSuccess) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = AwsExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + AwsExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride( aws_imdsv2_external_account_creds_httpcli_get_success, aws_external_account_creds_httpcli_post_success, aws_imdsv2_external_account_creds_httpcli_put_success); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, - TestAwsImdsv2ExternalAccountCredShouldNotUseMetadataServer) { +TEST_F(ExternalAccountCredentialsTest, + AwsImdsv2ExternalAccountCredShouldNotUseMetadataServer) { ExecCtx exec_ctx; SetEnv("AWS_REGION", "test_regionz"); SetEnv("AWS_ACCESS_KEY_ID", "test_access_key_id"); @@ -3182,7 +3255,7 @@ TEST(CredentialsTest, SetEnv("AWS_SESSION_TOKEN", "test_token"); auto credential_source = JsonParse( valid_aws_imdsv2_external_account_creds_options_credential_source); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3200,19 +3273,20 @@ TEST(CredentialsTest, "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = AwsExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + AwsExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success, aws_external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); UnsetEnv("AWS_REGION"); UnsetEnv("AWS_ACCESS_KEY_ID"); @@ -3220,16 +3294,16 @@ TEST(CredentialsTest, UnsetEnv("AWS_SESSION_TOKEN"); } -TEST( - CredentialsTest, - TestAwsImdsv2ExternalAccountCredShouldNotUseMetadataServerOptionalTokenMissing) { +TEST_F( + ExternalAccountCredentialsTest, + AwsImdsv2ExternalAccountCredShouldNotUseMetadataServerOptionalTokenMissing) { ExecCtx exec_ctx; SetEnv("AWS_REGION", "test_regionz"); SetEnv("AWS_ACCESS_KEY_ID", "test_access_key_id"); SetEnv("AWS_SECRET_ACCESS_KEY", "test_secret_access_key"); auto credential_source = JsonParse( valid_aws_imdsv2_external_account_creds_options_credential_source); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3247,30 +3321,31 @@ TEST( "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = AwsExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + AwsExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success, aws_external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); UnsetEnv("AWS_REGION"); UnsetEnv("AWS_ACCESS_KEY_ID"); UnsetEnv("AWS_SECRET_ACCESS_KEY"); } -TEST(CredentialsTest, TestAwsExternalAccountCredsSuccessIpv6) { +TEST_F(ExternalAccountCredentialsTest, AwsExternalAccountCredsSuccessIpv6) { ExecCtx exec_ctx; auto credential_source = JsonParse( valid_aws_external_account_creds_options_credential_source_ipv6); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3288,29 +3363,31 @@ TEST(CredentialsTest, TestAwsExternalAccountCredsSuccessIpv6) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = AwsExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + AwsExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride( aws_imdsv2_external_account_creds_httpcli_get_success, aws_external_account_creds_httpcli_post_success, aws_imdsv2_external_account_creds_httpcli_put_success); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, TestAwsExternalAccountCredsSuccessPathRegionEnvKeysUrl) { +TEST_F(ExternalAccountCredentialsTest, + AwsExternalAccountCredsSuccessPathRegionEnvKeysUrl) { ExecCtx exec_ctx; SetEnv("AWS_REGION", "test_regionz"); auto credential_source = JsonParse(valid_aws_external_account_creds_options_credential_source); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3328,30 +3405,31 @@ TEST(CredentialsTest, TestAwsExternalAccountCredsSuccessPathRegionEnvKeysUrl) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = AwsExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + AwsExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success, aws_external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); UnsetEnv("AWS_REGION"); } -TEST(CredentialsTest, - TestAwsExternalAccountCredsSuccessPathDefaultRegionEnvKeysUrl) { +TEST_F(ExternalAccountCredentialsTest, + AwsExternalAccountCredsSuccessPathDefaultRegionEnvKeysUrl) { ExecCtx exec_ctx; SetEnv("AWS_DEFAULT_REGION", "test_regionz"); auto credential_source = JsonParse(valid_aws_external_account_creds_options_credential_source); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3369,32 +3447,33 @@ TEST(CredentialsTest, "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = AwsExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + AwsExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success, aws_external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); UnsetEnv("AWS_DEFAULT_REGION"); } -TEST(CredentialsTest, - TestAwsExternalAccountCredsSuccessPathDuplicateRegionEnvKeysUrl) { +TEST_F(ExternalAccountCredentialsTest, + AwsExternalAccountCredsSuccessPathDuplicateRegionEnvKeysUrl) { ExecCtx exec_ctx; // Make sure that AWS_REGION gets used over AWS_DEFAULT_REGION SetEnv("AWS_REGION", "test_regionz"); SetEnv("AWS_DEFAULT_REGION", "ERROR_REGION"); auto credential_source = JsonParse(valid_aws_external_account_creds_options_credential_source); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3412,32 +3491,34 @@ TEST(CredentialsTest, "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = AwsExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + AwsExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success, aws_external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); UnsetEnv("AWS_REGION"); UnsetEnv("AWS_DEFAULT_REGION"); } -TEST(CredentialsTest, TestAwsExternalAccountCredsSuccessPathRegionUrlKeysEnv) { +TEST_F(ExternalAccountCredentialsTest, + AwsExternalAccountCredsSuccessPathRegionUrlKeysEnv) { ExecCtx exec_ctx; SetEnv("AWS_ACCESS_KEY_ID", "test_access_key_id"); SetEnv("AWS_SECRET_ACCESS_KEY", "test_secret_access_key"); SetEnv("AWS_SESSION_TOKEN", "test_token"); auto credential_source = JsonParse(valid_aws_external_account_creds_options_credential_source); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3455,26 +3536,28 @@ TEST(CredentialsTest, TestAwsExternalAccountCredsSuccessPathRegionUrlKeysEnv) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = AwsExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + AwsExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success, aws_external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); UnsetEnv("AWS_ACCESS_KEY_ID"); UnsetEnv("AWS_SECRET_ACCESS_KEY"); UnsetEnv("AWS_SESSION_TOKEN"); } -TEST(CredentialsTest, TestAwsExternalAccountCredsSuccessPathRegionEnvKeysEnv) { +TEST_F(ExternalAccountCredentialsTest, + AwsExternalAccountCredsSuccessPathRegionEnvKeysEnv) { ExecCtx exec_ctx; SetEnv("AWS_REGION", "test_regionz"); SetEnv("AWS_ACCESS_KEY_ID", "test_access_key_id"); @@ -3482,7 +3565,7 @@ TEST(CredentialsTest, TestAwsExternalAccountCredsSuccessPathRegionEnvKeysEnv) { SetEnv("AWS_SESSION_TOKEN", "test_token"); auto credential_source = JsonParse(valid_aws_external_account_creds_options_credential_source); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3500,19 +3583,20 @@ TEST(CredentialsTest, TestAwsExternalAccountCredsSuccessPathRegionEnvKeysEnv) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = AwsExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + AwsExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success, aws_external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); UnsetEnv("AWS_REGION"); UnsetEnv("AWS_ACCESS_KEY_ID"); @@ -3520,8 +3604,8 @@ TEST(CredentialsTest, TestAwsExternalAccountCredsSuccessPathRegionEnvKeysEnv) { UnsetEnv("AWS_SESSION_TOKEN"); } -TEST(CredentialsTest, - TestAwsExternalAccountCredsSuccessPathDefaultRegionEnvKeysEnv) { +TEST_F(ExternalAccountCredentialsTest, + AwsExternalAccountCredsSuccessPathDefaultRegionEnvKeysEnv) { std::map emd = { {"authorization", "Bearer token_exchange_access_token"}}; ExecCtx exec_ctx; @@ -3531,7 +3615,7 @@ TEST(CredentialsTest, SetEnv("AWS_SESSION_TOKEN", "test_token"); auto credential_source = JsonParse(valid_aws_external_account_creds_options_credential_source); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3549,19 +3633,20 @@ TEST(CredentialsTest, "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = AwsExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + AwsExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success, aws_external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); UnsetEnv("AWS_DEFAULT_REGION"); UnsetEnv("AWS_ACCESS_KEY_ID"); @@ -3569,8 +3654,8 @@ TEST(CredentialsTest, UnsetEnv("AWS_SESSION_TOKEN"); } -TEST(CredentialsTest, - TestAwsExternalAccountCredsSuccessPathDuplicateRegionEnvKeysEnv) { +TEST_F(ExternalAccountCredentialsTest, + AwsExternalAccountCredsSuccessPathDuplicateRegionEnvKeysEnv) { ExecCtx exec_ctx; // Make sure that AWS_REGION gets used over AWS_DEFAULT_REGION SetEnv("AWS_REGION", "test_regionz"); @@ -3580,7 +3665,7 @@ TEST(CredentialsTest, SetEnv("AWS_SESSION_TOKEN", "test_token"); auto credential_source = JsonParse(valid_aws_external_account_creds_options_credential_source); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3598,19 +3683,20 @@ TEST(CredentialsTest, "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = AwsExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + auto creds = + AwsExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); auto state = RequestMetadataState::NewInstance( absl::OkStatus(), "authorization: Bearer token_exchange_access_token"); HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success, aws_external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); UnsetEnv("AWS_REGION"); UnsetEnv("AWS_DEFAULT_REGION"); @@ -3619,7 +3705,7 @@ TEST(CredentialsTest, UnsetEnv("AWS_SESSION_TOKEN"); } -TEST(CredentialsTest, TestExternalAccountCredentialsCreateSuccess) { +TEST_F(ExternalAccountCredentialsTest, CreateSuccess) { // url credentials const char* url_options_string = "{\"type\":\"external_account\",\"audience\":\"audience\",\"subject_" @@ -3636,7 +3722,7 @@ TEST(CredentialsTest, TestExternalAccountCredentialsCreateSuccess) { const char* url_scopes_string = "scope1,scope2"; grpc_call_credentials* url_creds = grpc_external_account_credentials_create( url_options_string, url_scopes_string); - CHECK_NE(url_creds, nullptr); + ASSERT_NE(url_creds, nullptr); url_creds->Unref(); // file credentials const char* file_options_string = @@ -3652,7 +3738,7 @@ TEST(CredentialsTest, TestExternalAccountCredentialsCreateSuccess) { const char* file_scopes_string = "scope1,scope2"; grpc_call_credentials* file_creds = grpc_external_account_credentials_create( file_options_string, file_scopes_string); - CHECK_NE(file_creds, nullptr); + ASSERT_NE(file_creds, nullptr); file_creds->Unref(); // aws credentials const char* aws_options_string = @@ -3672,15 +3758,15 @@ TEST(CredentialsTest, TestExternalAccountCredentialsCreateSuccess) { const char* aws_scopes_string = "scope1,scope2"; grpc_call_credentials* aws_creds = grpc_external_account_credentials_create( aws_options_string, aws_scopes_string); - CHECK_NE(aws_creds, nullptr); + ASSERT_NE(aws_creds, nullptr); aws_creds->Unref(); } -TEST(CredentialsTest, - TestAwsExternalAccountCredsFailureUnmatchedEnvironmentId) { +TEST_F(ExternalAccountCredentialsTest, + AwsExternalAccountCredsFailureUnmatchedEnvironmentId) { auto credential_source = JsonParse( invalid_aws_external_account_creds_options_credential_source_unmatched_environment_id); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3698,22 +3784,21 @@ TEST(CredentialsTest, "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = AwsExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds == nullptr); - std::string expected_error = "environment_id does not match."; + auto creds = + AwsExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_FALSE(creds.ok()); std::string actual_error; - CHECK(grpc_error_get_str(error, StatusStrProperty::kDescription, - &actual_error)); - CHECK(expected_error == actual_error); + grpc_error_get_str(creds.status(), StatusStrProperty::kDescription, + &actual_error); + EXPECT_EQ("environment_id does not match.", actual_error); } -TEST(CredentialsTest, - TestAwsExternalAccountCredsFailureInvalidRegionalCredVerificationUrl) { +TEST_F(ExternalAccountCredentialsTest, + AwsExternalAccountCredsFailureInvalidRegionalCredVerificationUrl) { ExecCtx exec_ctx; auto credential_source = JsonParse( invalid_aws_external_account_creds_options_credential_source_invalid_regional_cred_verification_url); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3731,29 +3816,30 @@ TEST(CredentialsTest, "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = AwsExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); - error = GRPC_ERROR_CREATE("Creating aws request signer failed."); - grpc_error_handle expected_error = GRPC_ERROR_CREATE_REFERENCING( - "Error occurred when fetching oauth2 token.", &error, 1); + auto creds = + AwsExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + grpc_error_handle expected_error = GRPC_ERROR_CREATE( + "error fetching oauth2 token: Creating aws request signer failed."); auto state = RequestMetadataState::NewInstance(expected_error, {}); HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success, aws_external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, TestAwsExternalAccountCredsFailureMissingRoleName) { +TEST_F(ExternalAccountCredentialsTest, + AwsExternalAccountCredsFailureMissingRoleName) { ExecCtx exec_ctx; auto credential_source = JsonParse( invalid_aws_external_account_creds_options_credential_source_missing_role_name); - CHECK_OK(credential_source); + ASSERT_TRUE(credential_source.ok()) << credential_source.status(); TestExternalAccountCredentials::ServiceAccountImpersonation service_account_impersonation; service_account_impersonation.token_lifetime_seconds = 3600; @@ -3771,43 +3857,41 @@ TEST(CredentialsTest, TestAwsExternalAccountCredsFailureMissingRoleName) { "client_secret", // client_secret; "", // workforce_pool_user_project; }; - grpc_error_handle error; - auto creds = AwsExternalAccountCredentials::Create(options, {}, &error); - CHECK(creds != nullptr); - CHECK_OK(error); - CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); - error = GRPC_ERROR_CREATE("Missing role name when retrieving signing keys."); - grpc_error_handle expected_error = GRPC_ERROR_CREATE_REFERENCING( - "Error occurred when fetching oauth2 token.", &error, 1); + auto creds = + AwsExternalAccountCredentials::Create(options, {}, event_engine_); + ASSERT_TRUE(creds.ok()) << creds.status(); + ASSERT_NE(*creds, nullptr); + EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY); + grpc_error_handle expected_error = GRPC_ERROR_CREATE( + "error fetching oauth2 token: " + "Missing role name when retrieving signing keys."); auto state = RequestMetadataState::NewInstance(expected_error, {}); HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success, aws_external_account_creds_httpcli_post_success, httpcli_put_should_not_be_called); - state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority, + state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority, kTestPath); ExecCtx::Get()->Flush(); + event_engine_->TickUntilIdle(); HttpRequest::SetOverride(nullptr, nullptr, nullptr); } -TEST(CredentialsTest, - TestExternalAccountCredentialsCreateFailureInvalidJsonFormat) { +TEST_F(ExternalAccountCredentialsTest, CreateFailureInvalidJsonFormat) { const char* options_string = "invalid_json"; grpc_call_credentials* creds = grpc_external_account_credentials_create(options_string, ""); - CHECK(creds == nullptr); + EXPECT_EQ(creds, nullptr); } -TEST(CredentialsTest, - TestExternalAccountCredentialsCreateFailureInvalidOptionsFormat) { +TEST_F(ExternalAccountCredentialsTest, CreateFailureInvalidOptionsFormat) { const char* options_string = "{\"random_key\":\"random_value\"}"; grpc_call_credentials* creds = grpc_external_account_credentials_create(options_string, ""); - CHECK(creds == nullptr); + EXPECT_EQ(creds, nullptr); } -TEST( - CredentialsTest, - TestExternalAccountCredentialsCreateFailureInvalidOptionsCredentialSource) { +TEST_F(ExternalAccountCredentialsTest, + CreateFailureInvalidOptionsCredentialSource) { const char* options_string = "{\"type\":\"external_account\",\"audience\":\"audience\",\"subject_" "token_type\":\"subject_token_type\",\"service_account_impersonation_" @@ -3820,11 +3904,10 @@ TEST( "secret\"}"; grpc_call_credentials* creds = grpc_external_account_credentials_create(options_string, ""); - CHECK(creds == nullptr); + EXPECT_EQ(creds, nullptr); } -TEST(CredentialsTest, - TestExternalAccountCredentialsCreateSuccessWorkforcePool) { +TEST_F(ExternalAccountCredentialsTest, CreateSuccessWorkforcePool) { const char* url_options_string = "{\"type\":\"external_account\",\"audience\":\"//iam.googleapis.com/" "locations/location/workforcePools/pool/providers/provider\",\"subject_" @@ -3842,12 +3925,12 @@ TEST(CredentialsTest, const char* url_scopes_string = "scope1,scope2"; grpc_call_credentials* url_creds = grpc_external_account_credentials_create( url_options_string, url_scopes_string); - CHECK_NE(url_creds, nullptr); + ASSERT_NE(url_creds, nullptr); url_creds->Unref(); } -TEST(CredentialsTest, - TestExternalAccountCredentialsCreateFailureInvalidWorkforcePoolAudience) { +TEST_F(ExternalAccountCredentialsTest, + CreateFailureInvalidWorkforcePoolAudience) { const char* url_options_string = "{\"type\":\"external_account\",\"audience\":\"invalid_workforce_pool_" "audience\",\"subject_" @@ -3865,10 +3948,10 @@ TEST(CredentialsTest, const char* url_scopes_string = "scope1,scope2"; grpc_call_credentials* url_creds = grpc_external_account_credentials_create( url_options_string, url_scopes_string); - CHECK_EQ(url_creds, nullptr); + ASSERT_EQ(url_creds, nullptr); } -TEST(CredentialsTest, TestInsecureCredentialsCompareSuccess) { +TEST_F(CredentialsTest, TestInsecureCredentialsCompareSuccess) { auto insecure_creds_1 = grpc_insecure_credentials_create(); auto insecure_creds_2 = grpc_insecure_credentials_create(); ASSERT_EQ(insecure_creds_1->cmp(insecure_creds_2), 0); @@ -3881,7 +3964,7 @@ TEST(CredentialsTest, TestInsecureCredentialsCompareSuccess) { grpc_channel_credentials_release(insecure_creds_2); } -TEST(CredentialsTest, TestInsecureCredentialsCompareFailure) { +TEST_F(CredentialsTest, TestInsecureCredentialsCompareFailure) { auto* insecure_creds = grpc_insecure_credentials_create(); auto* fake_creds = grpc_fake_transport_security_credentials_create(); ASSERT_NE(insecure_creds->cmp(fake_creds), 0); @@ -3895,18 +3978,18 @@ TEST(CredentialsTest, TestInsecureCredentialsCompareFailure) { grpc_channel_credentials_release(insecure_creds); } -TEST(CredentialsTest, TestInsecureCredentialsSingletonCreate) { +TEST_F(CredentialsTest, TestInsecureCredentialsSingletonCreate) { auto* insecure_creds_1 = grpc_insecure_credentials_create(); auto* insecure_creds_2 = grpc_insecure_credentials_create(); EXPECT_EQ(insecure_creds_1, insecure_creds_2); } -TEST(CredentialsTest, TestFakeCallCredentialsCompareSuccess) { +TEST_F(CredentialsTest, TestFakeCallCredentialsCompareSuccess) { auto call_creds = MakeRefCounted(); CHECK_EQ(call_creds->cmp(call_creds.get()), 0); } -TEST(CredentialsTest, TestFakeCallCredentialsCompareFailure) { +TEST_F(CredentialsTest, TestFakeCallCredentialsCompareFailure) { auto fake_creds = MakeRefCounted(); auto* md_creds = grpc_md_only_test_credentials_create("key", "value"); CHECK_NE(fake_creds->cmp(md_creds), 0); @@ -3914,20 +3997,20 @@ TEST(CredentialsTest, TestFakeCallCredentialsCompareFailure) { grpc_call_credentials_release(md_creds); } -TEST(CredentialsTest, TestHttpRequestSSLCredentialsCompare) { +TEST_F(CredentialsTest, TestHttpRequestSSLCredentialsCompare) { auto creds_1 = CreateHttpRequestSSLCredentials(); auto creds_2 = CreateHttpRequestSSLCredentials(); EXPECT_EQ(creds_1->cmp(creds_2.get()), 0); EXPECT_EQ(creds_2->cmp(creds_1.get()), 0); } -TEST(CredentialsTest, TestHttpRequestSSLCredentialsSingleton) { +TEST_F(CredentialsTest, TestHttpRequestSSLCredentialsSingleton) { auto creds_1 = CreateHttpRequestSSLCredentials(); auto creds_2 = CreateHttpRequestSSLCredentials(); EXPECT_EQ(creds_1, creds_2); } -TEST(CredentialsTest, TestCompositeChannelCredsCompareSuccess) { +TEST_F(CredentialsTest, TestCompositeChannelCredsCompareSuccess) { auto* insecure_creds = grpc_insecure_credentials_create(); auto fake_creds = MakeRefCounted(); auto* composite_creds_1 = grpc_composite_channel_credentials_create( @@ -3941,7 +4024,7 @@ TEST(CredentialsTest, TestCompositeChannelCredsCompareSuccess) { grpc_channel_credentials_release(composite_creds_2); } -TEST(CredentialsTest, RecursiveCompositeCredsDuplicateWithoutCallCreds) { +TEST_F(CredentialsTest, RecursiveCompositeCredsDuplicateWithoutCallCreds) { auto* insecure_creds = grpc_insecure_credentials_create(); auto inner_fake_creds = MakeRefCounted(); auto outer_fake_creds = MakeRefCounted(); @@ -3957,8 +4040,8 @@ TEST(CredentialsTest, RecursiveCompositeCredsDuplicateWithoutCallCreds) { grpc_channel_credentials_release(outer_composite_creds); } -TEST(CredentialsTest, - TestCompositeChannelCredsCompareFailureDifferentChannelCreds) { +TEST_F(CredentialsTest, + TestCompositeChannelCredsCompareFailureDifferentChannelCreds) { auto* insecure_creds = grpc_insecure_credentials_create(); auto* fake_channel_creds = grpc_fake_transport_security_credentials_create(); auto fake_creds = MakeRefCounted(); @@ -3974,8 +4057,8 @@ TEST(CredentialsTest, grpc_channel_credentials_release(composite_creds_2); } -TEST(CredentialsTest, - TestCompositeChannelCredsCompareFailureDifferentCallCreds) { +TEST_F(CredentialsTest, + TestCompositeChannelCredsCompareFailureDifferentCallCreds) { auto* insecure_creds = grpc_insecure_credentials_create(); auto fake_creds = MakeRefCounted(); auto* md_creds = grpc_md_only_test_credentials_create("key", "value"); @@ -3991,7 +4074,7 @@ TEST(CredentialsTest, grpc_channel_credentials_release(composite_creds_2); } -TEST(CredentialsTest, TestTlsCredentialsCompareSuccess) { +TEST_F(CredentialsTest, TestTlsCredentialsCompareSuccess) { auto* tls_creds_1 = grpc_tls_credentials_create(grpc_tls_credentials_options_create()); auto* tls_creds_2 = @@ -4002,7 +4085,7 @@ TEST(CredentialsTest, TestTlsCredentialsCompareSuccess) { grpc_channel_credentials_release(tls_creds_2); } -TEST(CredentialsTest, TestTlsCredentialsWithVerifierCompareSuccess) { +TEST_F(CredentialsTest, TestTlsCredentialsWithVerifierCompareSuccess) { auto* options_1 = grpc_tls_credentials_options_create(); options_1->set_certificate_verifier( MakeRefCounted()); @@ -4017,7 +4100,7 @@ TEST(CredentialsTest, TestTlsCredentialsWithVerifierCompareSuccess) { grpc_channel_credentials_release(tls_creds_2); } -TEST(CredentialsTest, TestTlsCredentialsCompareFailure) { +TEST_F(CredentialsTest, TestTlsCredentialsCompareFailure) { auto* options_1 = grpc_tls_credentials_options_create(); options_1->set_check_call_host(true); auto* tls_creds_1 = grpc_tls_credentials_create(options_1); @@ -4030,7 +4113,7 @@ TEST(CredentialsTest, TestTlsCredentialsCompareFailure) { grpc_channel_credentials_release(tls_creds_2); } -TEST(CredentialsTest, TestTlsCredentialsWithVerifierCompareFailure) { +TEST_F(CredentialsTest, TestTlsCredentialsWithVerifierCompareFailure) { auto* options_1 = grpc_tls_credentials_options_create(); options_1->set_certificate_verifier( MakeRefCounted()); @@ -4047,7 +4130,7 @@ TEST(CredentialsTest, TestTlsCredentialsWithVerifierCompareFailure) { grpc_channel_credentials_release(tls_creds_2); } -TEST(CredentialsTest, TestXdsCredentialsCompareSucces) { +TEST_F(CredentialsTest, TestXdsCredentialsCompareSucces) { auto* insecure_creds = grpc_insecure_credentials_create(); auto* xds_creds_1 = grpc_xds_credentials_create(insecure_creds); auto* xds_creds_2 = grpc_xds_credentials_create(insecure_creds); @@ -4058,7 +4141,7 @@ TEST(CredentialsTest, TestXdsCredentialsCompareSucces) { grpc_channel_credentials_release(xds_creds_2); } -TEST(CredentialsTest, TestXdsCredentialsCompareFailure) { +TEST_F(CredentialsTest, TestXdsCredentialsCompareFailure) { auto* insecure_creds = grpc_insecure_credentials_create(); auto* fake_creds = grpc_fake_transport_security_credentials_create(); auto* xds_creds_1 = grpc_xds_credentials_create(insecure_creds); @@ -4077,8 +4160,6 @@ TEST(CredentialsTest, TestXdsCredentialsCompareFailure) { int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); grpc::testing::TestEnvironment env(&argc, argv); - grpc_init(); auto result = RUN_ALL_TESTS(); - grpc_shutdown(); return result; } diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal index 4065842b4ed2c..bdcc33a9a7009 100644 --- a/tools/doxygen/Doxyfile.c++.internal +++ b/tools/doxygen/Doxyfile.c++.internal @@ -2608,6 +2608,8 @@ src/core/lib/security/credentials/tls/tls_credentials.cc \ src/core/lib/security/credentials/tls/tls_credentials.h \ src/core/lib/security/credentials/tls/tls_utils.cc \ src/core/lib/security/credentials/tls/tls_utils.h \ +src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.cc \ +src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h \ src/core/lib/security/credentials/xds/xds_credentials.cc \ src/core/lib/security/credentials/xds/xds_credentials.h \ src/core/lib/security/security_connector/alts/alts_security_connector.cc \ diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal index 74453cdad03a8..978ef442a3620 100644 --- a/tools/doxygen/Doxyfile.core.internal +++ b/tools/doxygen/Doxyfile.core.internal @@ -2379,6 +2379,8 @@ src/core/lib/security/credentials/tls/tls_credentials.cc \ src/core/lib/security/credentials/tls/tls_credentials.h \ src/core/lib/security/credentials/tls/tls_utils.cc \ src/core/lib/security/credentials/tls/tls_utils.h \ +src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.cc \ +src/core/lib/security/credentials/token_fetcher/token_fetcher_credentials.h \ src/core/lib/security/credentials/xds/xds_credentials.cc \ src/core/lib/security/credentials/xds/xds_credentials.h \ src/core/lib/security/security_connector/alts/alts_security_connector.cc \