Skip to content

Commit 1b9b3c1

Browse files
authored
impl(oauth2): add RegionalAccessBoundary Credentials decorator (#16081)
1 parent f46998c commit 1b9b3c1

10 files changed

Lines changed: 971 additions & 20 deletions

google/cloud/google_cloud_cpp_rest_internal.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ google_cloud_cpp_rest_internal_hdrs = [
5454
"internal/oauth2_logging_credentials.h",
5555
"internal/oauth2_minimal_iam_credentials_rest.h",
5656
"internal/oauth2_refreshing_credentials_wrapper.h",
57+
"internal/oauth2_regional_access_boundary_token_manager.h",
5758
"internal/oauth2_service_account_credentials.h",
5859
"internal/oauth2_universe_domain.h",
5960
"internal/parse_service_account_p12_file.h",
@@ -113,6 +114,7 @@ google_cloud_cpp_rest_internal_srcs = [
113114
"internal/oauth2_logging_credentials.cc",
114115
"internal/oauth2_minimal_iam_credentials_rest.cc",
115116
"internal/oauth2_refreshing_credentials_wrapper.cc",
117+
"internal/oauth2_regional_access_boundary_token_manager.cc",
116118
"internal/oauth2_service_account_credentials.cc",
117119
"internal/oauth2_universe_domain.cc",
118120
"internal/openssl/parse_service_account_p12_file.cc",

google/cloud/google_cloud_cpp_rest_internal.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ add_library(
9292
internal/oauth2_minimal_iam_credentials_rest.h
9393
internal/oauth2_refreshing_credentials_wrapper.cc
9494
internal/oauth2_refreshing_credentials_wrapper.h
95+
internal/oauth2_regional_access_boundary_token_manager.cc
96+
internal/oauth2_regional_access_boundary_token_manager.h
9597
internal/oauth2_service_account_credentials.cc
9698
internal/oauth2_service_account_credentials.h
9799
internal/oauth2_universe_domain.cc
@@ -284,6 +286,7 @@ if (BUILD_TESTING)
284286
internal/oauth2_logging_credentials_test.cc
285287
internal/oauth2_minimal_iam_credentials_rest_test.cc
286288
internal/oauth2_refreshing_credentials_wrapper_test.cc
289+
internal/oauth2_regional_access_boundary_token_manager_test.cc
287290
internal/oauth2_service_account_credentials_test.cc
288291
internal/oauth2_universe_domain_test.cc
289292
internal/populate_rest_options_test.cc

google/cloud/google_cloud_cpp_rest_internal_unit_tests.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ google_cloud_cpp_rest_internal_unit_tests = [
5151
"internal/oauth2_logging_credentials_test.cc",
5252
"internal/oauth2_minimal_iam_credentials_rest_test.cc",
5353
"internal/oauth2_refreshing_credentials_wrapper_test.cc",
54+
"internal/oauth2_regional_access_boundary_token_manager_test.cc",
5455
"internal/oauth2_service_account_credentials_test.cc",
5556
"internal/oauth2_universe_domain_test.cc",
5657
"internal/populate_rest_options_test.cc",

google/cloud/internal/oauth2_decorate_credentials.cc

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,26 @@
1616
#include "google/cloud/common_options.h"
1717
#include "google/cloud/internal/oauth2_cached_credentials.h"
1818
#include "google/cloud/internal/oauth2_logging_credentials.h"
19+
#include "google/cloud/internal/oauth2_regional_access_boundary_token_manager.h"
1920

2021
namespace google {
2122
namespace cloud {
2223
namespace oauth2_internal {
2324
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
2425

2526
std::shared_ptr<oauth2_internal::Credentials> Decorate(
26-
std::shared_ptr<oauth2_internal::Credentials> impl, Options const& opts) {
27+
std::shared_ptr<oauth2_internal::Credentials> impl,
28+
HttpClientFactory client_factory, Options const& opts) {
2729
impl = WithLogging(std::move(impl), opts, "refresh");
2830
impl = WithCaching(std::move(impl));
29-
return WithLogging(std::move(impl), opts, "cached");
31+
impl = WithLogging(std::move(impl), opts, "cached");
32+
if (!std::holds_alternative<std::monostate>(
33+
impl->AllowedLocationsRequest())) {
34+
impl = WithRegionalAccessBoundary(std::move(impl),
35+
std::move(client_factory), opts);
36+
impl = WithLogging(std::move(impl), opts, "rab");
37+
}
38+
return impl;
3039
}
3140

3241
std::shared_ptr<oauth2_internal::Credentials> WithLogging(
@@ -42,6 +51,13 @@ std::shared_ptr<oauth2_internal::Credentials> WithCaching(
4251
return std::make_shared<oauth2_internal::CachedCredentials>(std::move(impl));
4352
}
4453

54+
std::shared_ptr<oauth2_internal::Credentials> WithRegionalAccessBoundary(
55+
std::shared_ptr<oauth2_internal::Credentials> impl,
56+
HttpClientFactory client_factory, Options options) {
57+
return RegionalAccessBoundaryTokenManager::Create(
58+
std::move(impl), std::move(client_factory), std::move(options));
59+
}
60+
4561
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
4662
} // namespace oauth2_internal
4763
} // namespace cloud

google/cloud/internal/oauth2_decorate_credentials.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_OAUTH2_DECORATE_CREDENTIALS_H
1717

1818
#include "google/cloud/internal/oauth2_credentials.h"
19+
#include "google/cloud/internal/oauth2_http_client_factory.h"
1920
#include "google/cloud/options.h"
2021
#include "google/cloud/version.h"
2122
#include <memory>
@@ -29,7 +30,8 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
2930
/// Add a full stack of logging (if requested in @p opts) and caching decorators
3031
/// to the credentials.
3132
std::shared_ptr<oauth2_internal::Credentials> Decorate(
32-
std::shared_ptr<oauth2_internal::Credentials> impl, Options const& opts);
33+
std::shared_ptr<oauth2_internal::Credentials> impl,
34+
HttpClientFactory client_factory, Options const& opts);
3335

3436
/// Add only a logging decorator to the credentials if requested in @p opts
3537
std::shared_ptr<oauth2_internal::Credentials> WithLogging(
@@ -40,6 +42,11 @@ std::shared_ptr<oauth2_internal::Credentials> WithLogging(
4042
std::shared_ptr<oauth2_internal::Credentials> WithCaching(
4143
std::shared_ptr<oauth2_internal::Credentials> impl);
4244

45+
/// Add regional access boundary decorator to the credentials.
46+
std::shared_ptr<oauth2_internal::Credentials> WithRegionalAccessBoundary(
47+
std::shared_ptr<oauth2_internal::Credentials> impl,
48+
HttpClientFactory client_factory, Options options);
49+
4350
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
4451
} // namespace oauth2_internal
4552
} // namespace cloud
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/cloud/internal/oauth2_regional_access_boundary_token_manager.h"
16+
#include "google/cloud/internal/algorithm.h"
17+
#include "google/cloud/internal/rest_response.h"
18+
#include "google/cloud/log.h"
19+
#include "absl/strings/str_cat.h"
20+
21+
namespace google {
22+
namespace cloud {
23+
namespace oauth2_internal {
24+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
25+
namespace {
26+
27+
auto constexpr kTokenTtl = std::chrono::seconds(6 * 3600);
28+
auto constexpr kTtlGracePeriod = std::chrono::seconds(3600);
29+
auto constexpr kMaximumRetryDuration = std::chrono::seconds(60);
30+
auto constexpr kInitialBackoffDelay = std::chrono::seconds(1);
31+
auto constexpr kMaximumBackoffDelay = std::chrono::seconds(5);
32+
auto constexpr kBackoffScaling = 2.0;
33+
auto constexpr kFailedLookupInitialBackoffDelay = std::chrono::seconds(15);
34+
auto constexpr kFailedLookupMaximumBackoffDelay = std::chrono::seconds(120);
35+
auto constexpr kFailedLookupBackoffScaling = 1.75;
36+
37+
} // namespace
38+
39+
bool RegionalAccessBoundaryTokenManager::RetryTraits::IsPermanentFailure(
40+
Status const& s) {
41+
// Http status codes 500, 502, 503, and 504 are mapped to kUnavailable, and
42+
// some others that we don't mind retrying.
43+
return s.code() != StatusCode::kUnavailable;
44+
}
45+
46+
class RegionalAccessBoundaryTokenManager::RefreshTokenLimitedTimeRetryPolicy
47+
: public RefreshTokenRetryPolicy {
48+
public:
49+
template <typename DurationRep, typename DurationPeriod>
50+
explicit RefreshTokenLimitedTimeRetryPolicy(
51+
std::chrono::duration<DurationRep, DurationPeriod> maximum_duration)
52+
: impl_(maximum_duration) {}
53+
54+
RefreshTokenLimitedTimeRetryPolicy(
55+
RefreshTokenLimitedTimeRetryPolicy&& rhs) noexcept
56+
: RefreshTokenLimitedTimeRetryPolicy(rhs.maximum_duration()) {}
57+
RefreshTokenLimitedTimeRetryPolicy(
58+
RefreshTokenLimitedTimeRetryPolicy const& rhs) noexcept
59+
: RefreshTokenLimitedTimeRetryPolicy(rhs.maximum_duration()) {}
60+
61+
std::chrono::milliseconds maximum_duration() const {
62+
return impl_.maximum_duration();
63+
}
64+
65+
bool OnFailure(Status const& status) override {
66+
return impl_.OnFailure(status);
67+
}
68+
bool IsExhausted() const override { return impl_.IsExhausted(); }
69+
bool IsPermanentFailure(Status const& status) const override {
70+
return impl_.IsPermanentFailure(status);
71+
}
72+
std::unique_ptr<RefreshTokenRetryPolicy> clone() const override {
73+
return std::make_unique<RefreshTokenLimitedTimeRetryPolicy>(
74+
maximum_duration());
75+
}
76+
77+
// This is provided only for backwards compatibility.
78+
using BaseType = RefreshTokenRetryPolicy;
79+
80+
private:
81+
google::cloud::internal::LimitedTimeRetryPolicy<RetryTraits> impl_;
82+
};
83+
84+
std::shared_ptr<RegionalAccessBoundaryTokenManager>
85+
RegionalAccessBoundaryTokenManager::Create(std::shared_ptr<Credentials> child,
86+
HttpClientFactory client_factory,
87+
Options options) {
88+
auto iam_stub = MakeMinimalIamCredentialsRestStub(child, options,
89+
std::move(client_factory));
90+
return std::shared_ptr<RegionalAccessBoundaryTokenManager>(
91+
new RegionalAccessBoundaryTokenManager(
92+
std::move(child), std::move(iam_stub),
93+
std::make_unique<
94+
rest_internal::AutomaticallyCreatedRestPureBackgroundThreads>(),
95+
std::move(options), FailedLookupBackoffPolicy,
96+
std::make_shared<Clock>()));
97+
}
98+
99+
std::shared_ptr<RegionalAccessBoundaryTokenManager>
100+
RegionalAccessBoundaryTokenManager::Create(
101+
std::shared_ptr<Credentials> child,
102+
std::shared_ptr<MinimalIamCredentialsRest> iam_stub, Options options) {
103+
return std::shared_ptr<RegionalAccessBoundaryTokenManager>(
104+
new RegionalAccessBoundaryTokenManager(
105+
std::move(child), std::move(iam_stub),
106+
std::make_unique<
107+
rest_internal::AutomaticallyCreatedRestPureBackgroundThreads>(),
108+
std::move(options), FailedLookupBackoffPolicy,
109+
std::make_shared<Clock>()));
110+
}
111+
112+
std::shared_ptr<RegionalAccessBoundaryTokenManager>
113+
RegionalAccessBoundaryTokenManager::Create(
114+
std::shared_ptr<Credentials> child,
115+
std::shared_ptr<MinimalIamCredentialsRest> iam_stub, Options options,
116+
std::function<std::unique_ptr<BackoffPolicy>()>
117+
failed_lookup_backoff_policy_fn,
118+
std::shared_ptr<Clock> clock, AllowedLocationsResponse allowed_locations) {
119+
return std::shared_ptr<RegionalAccessBoundaryTokenManager>(
120+
new RegionalAccessBoundaryTokenManager(
121+
std::move(child), std::move(iam_stub),
122+
std::make_unique<
123+
rest_internal::AutomaticallyCreatedRestPureBackgroundThreads>(),
124+
std::move(options), std::move(failed_lookup_backoff_policy_fn),
125+
std::move(clock), std::move(allowed_locations)));
126+
}
127+
128+
std::unique_ptr<BackoffPolicy>
129+
RegionalAccessBoundaryTokenManager::FailedLookupBackoffPolicy() {
130+
return std::make_unique<ExponentialBackoffPolicy>(
131+
kFailedLookupInitialBackoffDelay, kFailedLookupMaximumBackoffDelay,
132+
kFailedLookupBackoffScaling);
133+
}
134+
135+
RegionalAccessBoundaryTokenManager::RegionalAccessBoundaryTokenManager(
136+
std::shared_ptr<Credentials> child,
137+
std::shared_ptr<MinimalIamCredentialsRest> iam_stub,
138+
std::unique_ptr<rest_internal::RestPureBackgroundThreads> background,
139+
Options options,
140+
std::function<std::unique_ptr<BackoffPolicy>()>
141+
failed_lookup_backoff_policy_fn,
142+
std::shared_ptr<Clock> clock, AllowedLocationsResponse allowed_locations)
143+
: child_(std::move(child)),
144+
background_(std::move(background)),
145+
options_(std::move(options)),
146+
clock_(std::move(clock)),
147+
retry_policy_(std::make_unique<RefreshTokenLimitedTimeRetryPolicy>(
148+
kMaximumRetryDuration)),
149+
backoff_policy_(std::make_unique<ExponentialBackoffPolicy>(
150+
kInitialBackoffDelay, kMaximumBackoffDelay, kBackoffScaling)),
151+
failed_lookup_backoff_policy_fn_(
152+
std::move(failed_lookup_backoff_policy_fn)),
153+
iam_stub_(std::move(iam_stub)),
154+
allowed_locations_(std::move(allowed_locations)) {
155+
if (!allowed_locations_.encoded_locations.empty()) {
156+
expire_time_ = clock_->Now() + TokenTtl();
157+
}
158+
}
159+
160+
bool RegionalAccessBoundaryTokenManager::DoesEndpointRequireToken(
161+
std::string_view endpoint) {
162+
return absl::EndsWithIgnoreCase(endpoint, ".googleapis.com") &&
163+
!absl::EndsWithIgnoreCase(endpoint, ".rep.googleapis.com") &&
164+
!absl::EndsWithIgnoreCase(endpoint, (".rep.sandbox.googleapis.com"));
165+
}
166+
167+
bool RegionalAccessBoundaryTokenManager::IsTokenValid(
168+
std::scoped_lock<std::mutex> const&,
169+
std::chrono::system_clock::time_point tp) const {
170+
return !allowed_locations_.encoded_locations.empty() && tp < expire_time_;
171+
}
172+
173+
std::chrono::seconds RegionalAccessBoundaryTokenManager::TtlGracePeriod() {
174+
return kTtlGracePeriod;
175+
}
176+
177+
std::chrono::seconds RegionalAccessBoundaryTokenManager::TokenTtl() {
178+
return kTokenTtl;
179+
}
180+
181+
StatusOr<rest_internal::HttpHeader>
182+
RegionalAccessBoundaryTokenManager::AllowedLocations(
183+
std::chrono::system_clock::time_point tp, std::string_view endpoint) {
184+
auto request = child_->AllowedLocationsRequest();
185+
struct Visitor {
186+
StatusOr<rest_internal::HttpHeader> operator()(std::monostate) const {
187+
return rest_internal::HttpHeader{};
188+
}
189+
StatusOr<rest_internal::HttpHeader> operator()(
190+
ServiceAccountAllowedLocationsRequest const& r) const {
191+
return m.GetAllowedLocationsHeader(r, tp, endpoint);
192+
}
193+
StatusOr<rest_internal::HttpHeader> operator()(
194+
WorkforceIdentityAllowedLocationsRequest const& r) const {
195+
return m.GetAllowedLocationsHeader(r, tp, endpoint);
196+
}
197+
StatusOr<rest_internal::HttpHeader> operator()(
198+
WorkloadIdentityAllowedLocationsRequest const& r) const {
199+
return m.GetAllowedLocationsHeader(r, tp, endpoint);
200+
}
201+
202+
RegionalAccessBoundaryTokenManager& m;
203+
std::chrono::system_clock::time_point tp;
204+
std::string_view endpoint;
205+
};
206+
return std::visit(Visitor{*this, tp, endpoint}, request);
207+
}
208+
209+
StatusOr<std::vector<std::uint8_t>>
210+
RegionalAccessBoundaryTokenManager::SignBlob(
211+
absl::optional<std::string> const& signing_service_account,
212+
std::string const& string_to_sign) const {
213+
return child_->SignBlob(signing_service_account, string_to_sign);
214+
}
215+
216+
std::string RegionalAccessBoundaryTokenManager::AccountEmail() const {
217+
return child_->AccountEmail();
218+
}
219+
220+
std::string RegionalAccessBoundaryTokenManager::KeyId() const {
221+
return child_->KeyId();
222+
}
223+
224+
StatusOr<std::string> RegionalAccessBoundaryTokenManager::universe_domain()
225+
const {
226+
return child_->universe_domain();
227+
}
228+
229+
StatusOr<std::string> RegionalAccessBoundaryTokenManager::universe_domain(
230+
google::cloud::Options const& options) const {
231+
return child_->universe_domain(options);
232+
}
233+
234+
StatusOr<std::string> RegionalAccessBoundaryTokenManager::project_id() const {
235+
return child_->project_id();
236+
}
237+
238+
StatusOr<std::string> RegionalAccessBoundaryTokenManager::project_id(
239+
Options const& options) const {
240+
return child_->project_id(options);
241+
}
242+
243+
StatusOr<rest_internal::HttpHeader>
244+
RegionalAccessBoundaryTokenManager::Authorization(
245+
std::chrono::system_clock::time_point tp) {
246+
return child_->Authorization(tp);
247+
}
248+
249+
StatusOr<AccessToken> RegionalAccessBoundaryTokenManager::GetToken(
250+
std::chrono::system_clock::time_point tp) {
251+
return child_->GetToken(tp);
252+
}
253+
254+
Credentials::AllowedLocationsRequestType
255+
RegionalAccessBoundaryTokenManager::AllowedLocationsRequest() const {
256+
return child_->AllowedLocationsRequest();
257+
}
258+
259+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
260+
} // namespace oauth2_internal
261+
} // namespace cloud
262+
} // namespace google

0 commit comments

Comments
 (0)