diff --git a/google/cloud/google_cloud_cpp_rest_internal.bzl b/google/cloud/google_cloud_cpp_rest_internal.bzl index 5d0a4f8e417ac..e5d3ac55933f7 100644 --- a/google/cloud/google_cloud_cpp_rest_internal.bzl +++ b/google/cloud/google_cloud_cpp_rest_internal.bzl @@ -39,6 +39,7 @@ google_cloud_cpp_rest_internal_hdrs = [ "internal/oauth2_anonymous_credentials.h", "internal/oauth2_api_key_credentials.h", "internal/oauth2_authorized_user_credentials.h", + "internal/oauth2_background_credentials.h", "internal/oauth2_cached_credentials.h", "internal/oauth2_compute_engine_credentials.h", "internal/oauth2_credential_constants.h", diff --git a/google/cloud/google_cloud_cpp_rest_internal.cmake b/google/cloud/google_cloud_cpp_rest_internal.cmake index c2fa155d7ad9e..9ce5272af8075 100644 --- a/google/cloud/google_cloud_cpp_rest_internal.cmake +++ b/google/cloud/google_cloud_cpp_rest_internal.cmake @@ -65,6 +65,7 @@ add_library( internal/oauth2_api_key_credentials.h internal/oauth2_authorized_user_credentials.cc internal/oauth2_authorized_user_credentials.h + internal/oauth2_background_credentials.h internal/oauth2_cached_credentials.cc internal/oauth2_cached_credentials.h internal/oauth2_compute_engine_credentials.cc diff --git a/google/cloud/internal/oauth2_background_credentials.h b/google/cloud/internal/oauth2_background_credentials.h new file mode 100644 index 0000000000000..37f2c8b69718e --- /dev/null +++ b/google/cloud/internal/oauth2_background_credentials.h @@ -0,0 +1,94 @@ +// Copyright 2026 Google LLC +// +// 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 +// +// https://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 GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_OAUTH2_BACKGROUND_CREDENTIALS_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_OAUTH2_BACKGROUND_CREDENTIALS_H + +#include "google/cloud/internal/http_header.h" +#include "google/cloud/internal/oauth2_credentials.h" +#include "google/cloud/internal/rest_pure_background_threads_impl.h" +#include "google/cloud/options.h" +#include "google/cloud/status_or.h" +#include "google/cloud/version.h" +#include "absl/types/optional.h" +#include +#include +#include + +namespace google { +namespace cloud { +namespace oauth2_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +// This decorator exists to manage the lifetime of any BackgroundThreads that +// may be needed by a child Credentials it decorates. This separation is +// necessary to help avoid a thread in the pool attempting to destroy the +// BackgroundThreads object, which would result in a thread attempting to join +// itself. +class BackgroundCredentials : public Credentials { + public: + BackgroundCredentials( + std::unique_ptr background, + std::shared_ptr child) + : background_(std::move(background)), child_(std::move(child)) {} + + StatusOr> SignBlob( + absl::optional const& signing_service_account, + std::string const& string_to_sign) const override { + return child_->SignBlob(signing_service_account, string_to_sign); + } + std::string AccountEmail() const override { return child_->AccountEmail(); } + std::string KeyId() const override { return child_->KeyId(); } + StatusOr universe_domain() const override { + return child_->universe_domain(); + } + StatusOr universe_domain( + google::cloud::Options const& options) const override { + return child_->universe_domain(options); + } + StatusOr project_id() const override { + return child_->project_id(); + } + StatusOr project_id(Options const& options) const override { + return child_->project_id(options); + } + StatusOr Authorization( + std::chrono::system_clock::time_point tp) override { + return child_->Authorization(tp); + } + StatusOr GetToken( + std::chrono::system_clock::time_point tp) override { + return child_->GetToken(tp); + } + Credentials::AllowedLocationsRequestType AllowedLocationsRequest() + const override { + return child_->AllowedLocationsRequest(); + } + StatusOr AllowedLocations( + std::chrono::system_clock::time_point tp, + std::string_view endpoint) override { + return child_->AllowedLocations(tp, endpoint); + } + + private: + std::unique_ptr background_; + std::shared_ptr child_; +}; + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace oauth2_internal +} // namespace cloud +} // namespace google + +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_OAUTH2_BACKGROUND_CREDENTIALS_H diff --git a/google/cloud/internal/oauth2_decorate_credentials.cc b/google/cloud/internal/oauth2_decorate_credentials.cc index 4df3206aec658..e530f55d231b2 100644 --- a/google/cloud/internal/oauth2_decorate_credentials.cc +++ b/google/cloud/internal/oauth2_decorate_credentials.cc @@ -14,6 +14,7 @@ #include "google/cloud/internal/oauth2_decorate_credentials.h" #include "google/cloud/common_options.h" +#include "google/cloud/internal/oauth2_background_credentials.h" #include "google/cloud/internal/oauth2_cached_credentials.h" #include "google/cloud/internal/oauth2_logging_credentials.h" #include "google/cloud/internal/oauth2_regional_access_boundary_token_manager.h" @@ -54,8 +55,13 @@ std::shared_ptr WithCaching( std::shared_ptr WithRegionalAccessBoundary( std::shared_ptr impl, HttpClientFactory client_factory, Options options) { - return RegionalAccessBoundaryTokenManager::Create( - std::move(impl), std::move(client_factory), std::move(options)); + auto background = std::make_unique< + rest_internal::AutomaticallyCreatedRestPureBackgroundThreads>(); + auto rab = RegionalAccessBoundaryTokenManager::Create( + std::move(impl), std::move(client_factory), background->cq(), + std::move(options)); + return std::make_shared(std::move(background), + std::move(rab)); } GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END diff --git a/google/cloud/internal/oauth2_regional_access_boundary_token_manager.cc b/google/cloud/internal/oauth2_regional_access_boundary_token_manager.cc index fb6c96819e552..ad0dabb8f681b 100644 --- a/google/cloud/internal/oauth2_regional_access_boundary_token_manager.cc +++ b/google/cloud/internal/oauth2_regional_access_boundary_token_manager.cc @@ -82,17 +82,19 @@ class RegionalAccessBoundaryTokenManager::RefreshTokenLimitedTimeRetryPolicy google::cloud::internal::LimitedTimeRetryPolicy impl_; }; +RegionalAccessBoundaryTokenManager::~RegionalAccessBoundaryTokenManager() { + if (failed_lookup_cooldown_.valid()) failed_lookup_cooldown_.cancel(); +} + std::shared_ptr -RegionalAccessBoundaryTokenManager::Create(std::shared_ptr child, - HttpClientFactory client_factory, - Options options) { +RegionalAccessBoundaryTokenManager::Create( + std::shared_ptr child, HttpClientFactory client_factory, + rest_internal::RestPureCompletionQueue cq, Options options) { auto iam_stub = MakeMinimalIamCredentialsRestStub(child, options, std::move(client_factory)); return std::shared_ptr( new RegionalAccessBoundaryTokenManager( - std::move(child), std::move(iam_stub), - std::make_unique< - rest_internal::AutomaticallyCreatedRestPureBackgroundThreads>(), + std::move(child), std::move(iam_stub), std::move(cq), std::move(options), FailedLookupBackoffPolicy, std::make_shared())); } @@ -100,12 +102,11 @@ RegionalAccessBoundaryTokenManager::Create(std::shared_ptr child, std::shared_ptr RegionalAccessBoundaryTokenManager::Create( std::shared_ptr child, - std::shared_ptr iam_stub, Options options) { + std::shared_ptr iam_stub, + rest_internal::RestPureCompletionQueue cq, Options options) { return std::shared_ptr( new RegionalAccessBoundaryTokenManager( - std::move(child), std::move(iam_stub), - std::make_unique< - rest_internal::AutomaticallyCreatedRestPureBackgroundThreads>(), + std::move(child), std::move(iam_stub), std::move(cq), std::move(options), FailedLookupBackoffPolicy, std::make_shared())); } @@ -113,15 +114,14 @@ RegionalAccessBoundaryTokenManager::Create( std::shared_ptr RegionalAccessBoundaryTokenManager::Create( std::shared_ptr child, - std::shared_ptr iam_stub, Options options, + std::shared_ptr iam_stub, + rest_internal::RestPureCompletionQueue cq, Options options, std::function()> failed_lookup_backoff_policy_fn, std::shared_ptr clock, AllowedLocationsResponse allowed_locations) { return std::shared_ptr( new RegionalAccessBoundaryTokenManager( - std::move(child), std::move(iam_stub), - std::make_unique< - rest_internal::AutomaticallyCreatedRestPureBackgroundThreads>(), + std::move(child), std::move(iam_stub), std::move(cq), std::move(options), std::move(failed_lookup_backoff_policy_fn), std::move(clock), std::move(allowed_locations))); } @@ -136,13 +136,12 @@ RegionalAccessBoundaryTokenManager::FailedLookupBackoffPolicy() { RegionalAccessBoundaryTokenManager::RegionalAccessBoundaryTokenManager( std::shared_ptr child, std::shared_ptr iam_stub, - std::unique_ptr background, - Options options, + rest_internal::RestPureCompletionQueue cq, Options options, std::function()> failed_lookup_backoff_policy_fn, std::shared_ptr clock, AllowedLocationsResponse allowed_locations) : child_(std::move(child)), - background_(std::move(background)), + cq_(std::move(cq)), options_(std::move(options)), clock_(std::move(clock)), retry_policy_(std::make_unique( diff --git a/google/cloud/internal/oauth2_regional_access_boundary_token_manager.h b/google/cloud/internal/oauth2_regional_access_boundary_token_manager.h index 09b48a2ee5e2b..a6ef56f2bf37b 100644 --- a/google/cloud/internal/oauth2_regional_access_boundary_token_manager.h +++ b/google/cloud/internal/oauth2_regional_access_boundary_token_manager.h @@ -64,13 +64,16 @@ class RegionalAccessBoundaryTokenManager static bool IsPermanentFailure(Status const&); }; + ~RegionalAccessBoundaryTokenManager() override; + static std::shared_ptr Create( std::shared_ptr child, HttpClientFactory client_factory, - Options options); + rest_internal::RestPureCompletionQueue cq, Options options); static std::shared_ptr Create( std::shared_ptr child, - std::shared_ptr iam_stub, Options options); + std::shared_ptr iam_stub, + rest_internal::RestPureCompletionQueue cq, Options options); static std::chrono::seconds TtlGracePeriod(); static std::chrono::seconds TokenTtl(); @@ -126,7 +129,8 @@ class RegionalAccessBoundaryTokenManager // Used for testing. static std::shared_ptr Create( std::shared_ptr child, - std::shared_ptr iam_stub, Options options, + std::shared_ptr iam_stub, + rest_internal::RestPureCompletionQueue cq, Options options, std::function()> failed_lookup_backoff_policy_fn, std::shared_ptr clock = std::make_shared(), @@ -159,8 +163,7 @@ class RegionalAccessBoundaryTokenManager RegionalAccessBoundaryTokenManager( std::shared_ptr child, std::shared_ptr iam_stub, - std::unique_ptr background, - Options options, + rest_internal::RestPureCompletionQueue cq, Options options, std::function()> failed_lookup_backoff_policy_fn, std::shared_ptr clock = std::make_shared(), @@ -216,21 +219,20 @@ class RegionalAccessBoundaryTokenManager self->failed_lookup_backoff_policy_ = self->failed_lookup_backoff_policy_fn_(); } - self->failed_lookup_cooldown_ = - self->background_->cq().MakeRelativeTimer( - self->failed_lookup_backoff_policy_->OnCompletion()); + self->failed_lookup_cooldown_ = self->cq_.MakeRelativeTimer( + self->failed_lookup_backoff_policy_->OnCompletion()); p.set_value(allowed_locations.status()); } self->refresh_in_progress_ = false; }; refresh_in_progress_ = true; - background_->cq().RunAsync(std::move(pending_refresh_fn)); + cq_.RunAsync(std::move(pending_refresh_fn)); } mutable std::mutex mu_; std::shared_ptr child_; - std::unique_ptr background_; + rest_internal::RestPureCompletionQueue cq_; Options options_; std::shared_ptr clock_; std::unique_ptr retry_policy_; diff --git a/google/cloud/internal/oauth2_regional_access_boundary_token_manager_test.cc b/google/cloud/internal/oauth2_regional_access_boundary_token_manager_test.cc index 46f93a70ee2ea..081b8412dfa84 100644 --- a/google/cloud/internal/oauth2_regional_access_boundary_token_manager_test.cc +++ b/google/cloud/internal/oauth2_regional_access_boundary_token_manager_test.cc @@ -104,17 +104,21 @@ class RegionalAccessBoundaryTokenManagerTest : public ::testing::Test { RegionalAccessBoundaryTokenManagerTest() : mock_credentials_(std::make_shared()), mock_iam_stub_(std::make_shared()), - fake_clock_(std::make_shared()) {} + fake_clock_(std::make_shared()), + background_(std::make_unique< + rest_internal:: + AutomaticallyCreatedRestPureBackgroundThreads>()) {} std::shared_ptr mock_credentials_; std::shared_ptr mock_iam_stub_; std::shared_ptr fake_clock_; + std::unique_ptr background_; }; TEST_F(RegionalAccessBoundaryTokenManagerTest, GetAllowedLocationsHeaderNonApplicableEndpoints) { - auto manager = RegionalAccessBoundaryTokenManager::Create(mock_credentials_, - mock_iam_stub_, {}); + auto manager = RegionalAccessBoundaryTokenManager::Create( + mock_credentials_, mock_iam_stub_, background_->cq(), {}); ServiceAccountAllowedLocationsRequest request; auto header = manager->GetAllowedLocationsHeader( @@ -151,8 +155,8 @@ TEST_F(RegionalAccessBoundaryTokenManagerTest, allowed_locations.encoded_locations = "encoded-location"; auto manager = RegionalAccessBoundaryTokenManager::Create( - mock_credentials_, mock_iam_stub_, {}, backoff_fn.AsStdFunction(), - fake_clock_, allowed_locations); + mock_credentials_, mock_iam_stub_, background_->cq(), {}, + backoff_fn.AsStdFunction(), fake_clock_, allowed_locations); fake_clock_->AdvanceTime(std::chrono::seconds(1)); @@ -211,8 +215,8 @@ TEST_F(RegionalAccessBoundaryTokenManagerTest, allowed_locations.encoded_locations = "0x0"; auto manager = RegionalAccessBoundaryTokenManager::Create( - mock_credentials_, mock_iam_stub_, {}, backoff_fn.AsStdFunction(), - fake_clock_, allowed_locations); + mock_credentials_, mock_iam_stub_, background_->cq(), {}, + backoff_fn.AsStdFunction(), fake_clock_, allowed_locations); fake_clock_->AdvanceTime(std::chrono::seconds(1)); @@ -239,8 +243,8 @@ TEST_F(RegionalAccessBoundaryTokenManagerTest, return response; }); - auto manager = RegionalAccessBoundaryTokenManager::Create(mock_credentials_, - mock_iam_stub_, {}); + auto manager = RegionalAccessBoundaryTokenManager::Create( + mock_credentials_, mock_iam_stub_, background_->cq(), {}); auto header = manager->AllowedLocations(std::chrono::system_clock::now(), "service.googleapis.com"); @@ -286,7 +290,8 @@ TEST_F(RegionalAccessBoundaryTokenManagerTest, }); auto manager = RegionalAccessBoundaryTokenManager::Create( - mock_credentials_, mock_iam_stub_, {}, backoff_fn.AsStdFunction()); + mock_credentials_, mock_iam_stub_, background_->cq(), {}, + backoff_fn.AsStdFunction()); auto header = manager->AllowedLocations(std::chrono::system_clock::now(), "service.googleapis.com"); @@ -341,8 +346,8 @@ TEST_F(RegionalAccessBoundaryTokenManagerTest, AllowedLocations(A())) .Times(0); - auto manager = RegionalAccessBoundaryTokenManager::Create(mock_credentials_, - mock_iam_stub_, {}); + auto manager = RegionalAccessBoundaryTokenManager::Create( + mock_credentials_, mock_iam_stub_, background_->cq(), {}); auto header = manager->AllowedLocations(std::chrono::system_clock::now(), "service.googleapis.com"); EXPECT_THAT(header, IsOkAndHolds(IsEmpty())); @@ -388,8 +393,8 @@ TEST_F(RegionalAccessBoundaryTokenManagerTest, DecoratorMethodPassThrough) { .WillOnce( Return(Credentials::AllowedLocationsRequestType{std::monostate{}})); - auto manager = RegionalAccessBoundaryTokenManager::Create(mock_credentials_, - mock_iam_stub_, {}); + auto manager = RegionalAccessBoundaryTokenManager::Create( + mock_credentials_, mock_iam_stub_, background_->cq(), {}); (void)manager->SignBlob("sa", "string"); (void)manager->AccountEmail(); diff --git a/google/cloud/storage/tests/object_plenty_clients_serially_integration_test.cc b/google/cloud/storage/tests/object_plenty_clients_serially_integration_test.cc index f947d62118c76..a40f36986c4f6 100644 --- a/google/cloud/storage/tests/object_plenty_clients_serially_integration_test.cc +++ b/google/cloud/storage/tests/object_plenty_clients_serially_integration_test.cc @@ -15,6 +15,7 @@ #include "google/cloud/storage/client.h" #include "google/cloud/storage/testing/object_integration_test.h" #include "google/cloud/storage/testing/storage_integration_test.h" +#include "google/cloud/internal/getenv.h" #include "google/cloud/log.h" #include "google/cloud/status_or.h" #include "google/cloud/testing_util/expect_exception.h" @@ -42,7 +43,19 @@ TEST_F(ObjectPlentyClientsSeriallyIntegrationTest, PlentyClientsSerially) { // own tests. if (UsingGrpc()) GTEST_SKIP(); - auto client = MakeIntegrationTestClient(); + // With the advent of Regional Access Boundaries, connecting to non-regional + // endpoints requires background calls to IAM. These background calls use + // additional file descriptors which causes this test to fail when using the + // default endpoint. + auto regional_bucket = google::cloud::internal::GetEnv( + "GOOGLE_CLOUD_CPP_STORAGE_TEST_DESTINATION"); + if (!regional_bucket.has_value()) GTEST_SKIP(); + bucket_name_ = *regional_bucket; + // The regional_bucket was created in the us-west2 region. + auto options = Options{}.set( + "https://storage.us-west2.rep.googleapis.com"); + + auto client = MakeIntegrationTestClient(options); auto object_name = MakeRandomObjectName(); std::string expected = LoremIpsum(); @@ -64,7 +77,7 @@ TEST_F(ObjectPlentyClientsSeriallyIntegrationTest, PlentyClientsSerially) { } std::size_t delta = 0; for (int i = 0; i != 100; ++i) { - auto read_client = MakeIntegrationTestClient(); + auto read_client = MakeIntegrationTestClient(options); auto stream = read_client.ReadObject(bucket_name_, object_name); char c; stream.read(&c, 1);