Skip to content

feat(auth): implement regional access boundary support for standalone JWT and async service accounts#17025

Merged
nbayati merged 34 commits into
googleapis:mainfrom
nbayati:rab-support-async-sa-jwt
Jun 5, 2026
Merged

feat(auth): implement regional access boundary support for standalone JWT and async service accounts#17025
nbayati merged 34 commits into
googleapis:mainfrom
nbayati:rab-support-async-sa-jwt

Conversation

@nbayati

@nbayati nbayati commented May 11, 2026

Copy link
Copy Markdown
Contributor

This PR implements the following changes:

  • Add RAB support to async service account credential type, by providing async manager and fetching methods.
  • Update unit tests to accept both mtls and standard lookup endpoint urls.
  • Refactor before_request to use a _after_refresh hook so we don't have to override the method.
  • Add RAb support for self signed jwt flow through jwt.py
  • some small enhancements for test coverage and backward compatibility

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements asynchronous support for Regional Access Boundary (RAB) management, including background refresh tasks and mTLS endpoint support. Key changes include the addition of _AsyncRegionalAccessBoundaryRefreshManager, updates to JWT and service account credentials to handle RAB state during cloning and serialization, and comprehensive test coverage for these new flows. However, a critical issue was identified where the refresh method in google/auth/jwt.py was renamed to _perform_refresh_token, which will break token updates as the base class expects a refresh method. Additionally, a typo was found in a test assertion URL.

Comment thread packages/google-auth/google/auth/jwt.py
Comment thread packages/google-auth/tests/test_external_account_authorized_user.py Outdated
@nbayati nbayati changed the title Add RAB support for async SA and jwt.py feat(auth): implement regional access boundary support for standalone JWT and async service accounts May 11, 2026
@nbayati nbayati marked this pull request as ready for review May 11, 2026 20:40
@nbayati nbayati requested review from a team as code owners May 11, 2026 20:40
@macastelaz macastelaz self-assigned this May 12, 2026
Comment thread packages/google-auth/google/auth/_regional_access_boundary_utils.py Outdated
Comment thread packages/google-auth/google/auth/_regional_access_boundary_utils.py Outdated
Comment thread packages/google-auth/google/oauth2/_client_async.py Outdated
Comment thread packages/google-auth/google/oauth2/_client_async.py Outdated
Comment thread packages/google-auth/google/oauth2/_client_async.py Outdated
Comment thread packages/google-auth/google/auth/credentials.py Outdated
Comment thread packages/google-auth/google/oauth2/_client_async.py Outdated
Comment thread packages/google-auth/google/oauth2/_client_async.py Outdated
Comment thread packages/google-auth/google/oauth2/_client_async.py
Comment thread packages/google-auth/google/oauth2/_service_account_async.py Outdated
Comment thread packages/google-auth/google/auth/_regional_access_boundary_utils.py
Comment thread packages/google-auth/google/oauth2/_service_account_async.py Outdated
Comment thread packages/google-auth/google/auth/credentials.py
Comment thread packages/google-auth/google/oauth2/_client_async.py
Comment thread packages/google-auth/google/oauth2/_client_async.py
Comment thread packages/google-auth/tests/test_jwt.py Outdated
nbayati and others added 19 commits May 19, 2026 03:10
@nbayati nbayati force-pushed the rab-support-async-sa-jwt branch from be67ab6 to 7e65ee7 Compare May 19, 2026 03:14
@nbayati nbayati requested review from daniel-sanche and lsirac May 27, 2026 00:13
new_credentials = credentials.with_claims(audience=new_audience)
"""

def __setstate__(self, state):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_jwt_async.OnDemandCredentials inherits from jwt.OnDemandCredentials first. Because jwt.OnDemandCredentials implements before_request synchronously, Python's MRO resolves before_request to the synchronous parent, shadowing the async base.
When called via the experimental async HTTP transport (_aiohttp_requests.py), which has no initialization type-checking and explicitly awaits before_request(...), the synchronous method executes, returns None, and the event loop attempts to await None, raising:
TypeError: object NoneType can't be used in 'await' expression

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this. I've resolved this by adding an async def before_request override to the OnDemandCredentials class that delegates to the parent class, and updated the corresponding async test cases to properly await the call.

As a side note, I have OnDemandCredentials on my radar as a class that might need to support RAB in the future, but I have decided to keep it out of scope for this PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok sounds good. I think we have a few open items to follow up on IIRC. Where are they being tracked?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm tracking the additional credential types in the RAB google sheet tracker for a follow up release.
For the items that weren't RAB specific, I have made a github issue:

@nbayati nbayati requested a review from lsirac May 29, 2026 17:24
@nbayati

nbayati commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

Note to reviewers regarding mTLS transport gap:
Please note that while the RAB implementation in this PR dynamically switches endpoints to iamcredentials.mtls.googleapis.com when mTLS is enabled, the actual mTLS handshake will silently fall back to standard TLS (or fail under strict CBA enforcement) due to a library-wide transport gap.

The internal helper transport adapter (self._auth_request / self._request) passed to _regional_access_boundary_utils.py is not configured with the client certificate.

I have filed a tracking issue to address this transport gap: #17282

Because resolving the transport gap requires structural changes to google-auth's transport adapter initialization (requests.py and urllib3.py), we will track and merge the transport fix separately to keep the scope of this PR clean and focused on the core RAB logic. Once the transport issue is resolved, the RAB lookups in this feature will automatically execute over secure mTLS channels.

@lsirac lsirac left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job!

last thing - consider validating lookup response, but can be done in a followup

@daniel-sanche daniel-sanche left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments from my first pass. I'll try to take another look soon

Comment thread packages/google-auth/google/auth/_jwt_async.py Outdated
self.process_regional_access_boundary_info(regional_access_boundary_info)

async def start_blocking_refresh_async(self, credentials, request):
"""Initiates a blocking lookup of the Regional Access Boundary asynchronously.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems confusing. If it's async, is it really blocking?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this context, "blocking" refers to the logical flow of the request (i.e., we wait for the RAB lookup to complete before proceeding with the main request), as opposed to the background refresh mode. In the async implementation, this is achieved by awaiting the lookup, which suspends the coroutine but does not block the OS thread. The name matches the _use_blocking_regional_access_boundary_lookup configuration.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest trying to clarify that in the docstring

request, url, can_retry=True, headers=None, fail_fast=False
):
"""Makes a request to the Regional Access Boundary lookup endpoint. This
function doesn't throw on response errors.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find tracing some of these functions a bit confusing. There's a _lookup_regional_access_boundary_request and _lookup_regional_access_boundary_request_no_throw, but it seems like neither of them throw?

I see this is the same pattern from the sync files though, so it can probably stay as-is

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, neither throws. The _no_throw version handles the low-level exceptions (timeouts, transport errors) and returns the detailed status tuple (status, data, retryable) used for retry logic. The other function acts as a wrapper that simplifies the interface by returning just data or None.

We kept this pattern to remain consistent with the sync implementation in _client.py.

Comment thread packages/google-auth/google/oauth2/_client_async.py Outdated
Comment thread packages/google-auth/google/oauth2/_service_account_async.py Outdated
assert "x-goog-user-project" in hdrs

def test_build_regional_access_boundary_lookup_url(self):
def test_copy_regional_access_boundary_manager_state_and_config_with_scopes(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a test for external account + SA impersonation flow. This implementation will create an Impersonated cred with a new RAB manager. I think we may be dropping state that is needed (e.g. is blocking lookup enabled?).

Please add some coverage for this + validate that we are properly propagating the relevant RAB data.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! We were indeed dropping the blocking flag. I double checked the properties and I think that's the only thing we should propagate. Our helper method in the base class copies the blocking flag and the RAB data, but I decided not to use that, since the RAB data of the external account if it was seeded, would not necessarily be the RAB data of the impersonated account.

I resolved this by propagating the blocking RAB lookup configuration (_use_blocking_regional_access_boundary_lookup) from the outer ExternalAccountCredentials to the inner ImpersonatedCredentials during initialization in _initialize_impersonated_credentials. Also added a unit test (test_refresh_impersonation_propagates_rab_config in tests/test_external_account.py) to verify the propagation.

return token_json["access_token"], token_expiry


def _is_email(email):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think is_email is overpromising here for what we're checking. I think we can rename to is_service_account_email and implement with a simple regex check

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! I renamed the method and used ^[^@]+@[^@]+\.[^@]+$ which was recommended in the design for all SDKs to follow.

@nbayati nbayati requested review from daniel-sanche and lsirac June 4, 2026 19:26

@daniel-sanche daniel-sanche left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@lsirac lsirac left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@nbayati nbayati merged commit 35af616 into googleapis:main Jun 5, 2026
32 checks passed
sofisl added a commit that referenced this pull request Jun 11, 2026
PR created by the Librarian CLI to initialize a release. Merging this PR
will auto trigger a release.

Librarian Version: v0.19.0
Language Image:
us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-librarian-generator@sha256:234b9d1f2ddb057ed7ac6a38db0bf8163d839c65c6cf88ade52530cddebce59e
<details><summary>gapic-generator: v1.35.0</summary>

##
[v1.35.0](gapic-generator-v1.34.1...gapic-generator-v1.35.0)
(2026-06-11)

### Features

* setup.py matches prerelease versions (#17370)
([25b857e](25b857e1))

### Bug Fixes

* require protobuf 6.33.5 to address CVE-2026-0994 (#17349)
([6642263](66422636))

</details>


<details><summary>google-auth: v2.54.0</summary>

##
[v2.54.0](google-auth-v2.53.0...google-auth-v2.54.0)
(2026-06-11)

### Features

* implement regional access boundary support for standalone JWT and
async service accounts (#17025)
([35af616](35af6168))

### Bug Fixes

* configure mTLS for impersonated credentials (#17404)
([57269d5](57269d56))

* fail-fast on missing ECP config file to avoid 30s hang (#17377)
([e096127](e0961270))

* Rename the &#39;seed&#39; argument for setting an initial regional
access boundary for clarity (#17186)
([e5c8cf9](e5c8cf92))

* update incorrect urls in setup.py to point at monorepo vs splitrepo
(#17237)
([eaed04b](eaed04ba))

</details>


<details><summary>google-cloud-alloydb: v0.11.0</summary>

##
[v0.11.0](google-cloud-alloydb-v0.10.0...google-cloud-alloydb-v0.11.0)
(2026-06-11)

### Features

* update API sources and regenerate (#17413)
([59fe7cf](59fe7cf8))

</details>


<details><summary>google-cloud-biglake: v0.5.0</summary>

##
[v0.5.0](google-cloud-biglake-v0.4.0...google-cloud-biglake-v0.5.0)
(2026-06-11)

### Features

* update API sources and regenerate (#17431)
([2e75c78](2e75c78c))

</details>


<details><summary>google-cloud-ces: v0.7.0</summary>

##
[v0.7.0](google-cloud-ces-v0.6.0...google-cloud-ces-v0.7.0)
(2026-06-11)

### Features

* update API sources and regenerate (#17413)
([59fe7cf](59fe7cf8))

</details>


<details><summary>google-cloud-confidentialcomputing: v0.11.0</summary>

##
[v0.11.0](google-cloud-confidentialcomputing-v0.10.0...google-cloud-confidentialcomputing-v0.11.0)
(2026-06-11)

### Features

* update API sources and regenerate (#17413)
([59fe7cf](59fe7cf8))

</details>


<details><summary>google-cloud-modelarmor: v0.7.0</summary>

##
[v0.7.0](google-cloud-modelarmor-v0.6.0...google-cloud-modelarmor-v0.7.0)
(2026-06-11)

### Features

* update API sources and regenerate (#17413)
([59fe7cf](59fe7cf8))

</details>


<details><summary>google-cloud-network-services: v0.10.0</summary>

##
[v0.10.0](google-cloud-network-services-v0.9.0...google-cloud-network-services-v0.10.0)
(2026-06-11)

### Features

* update API sources and regenerate (#17431)
([2e75c78](2e75c78c))

</details>


<details><summary>google-cloud-oracledatabase: v0.6.0</summary>

##
[v0.6.0](google-cloud-oracledatabase-v0.5.0...google-cloud-oracledatabase-v0.6.0)
(2026-06-11)

### Features

* update API sources and regenerate (#17413)
([59fe7cf](59fe7cf8))

</details>


<details><summary>google-cloud-spanner: v3.68.0</summary>

##
[v3.68.0](google-cloud-spanner-v3.67.0...google-cloud-spanner-v3.68.0)
(2026-06-11)

### Features

* add asynchronous code snippets and minor cleanup changes (#17337)
([d6aaf61](d6aaf610))

### Performance Improvements

* optimize query result decoding (#17375)
([3f70b2f](3f70b2ff))

</details>


<details><summary>google-cloud-storage: v3.12.0</summary>

##
[v3.12.0](google-cloud-storage-v3.11.0...google-cloud-storage-v3.12.0)
(2026-06-11)

### Features

* full object checksum: implement rolling checksum and verification in
reads resumption strategy (#17262)
([2361ba6](2361ba6e))

* Enable full object checksum PR 1/3 : parse finalize_time and server
crc32c in async object stream (#17261)
([72c7a27](72c7a272))

* full object checksum: integrate full-object checksum in
AsyncMultiRangeDownloader (#17263)
([b6a85e4](b6a85e49))

</details>


<details><summary>google-developer-knowledge: v0.1.0</summary>

##
[v0.1.0](google-developer-knowledge-v0.0.0...google-developer-knowledge-v0.1.0)
(2026-06-11)

### Features

* add google-developer-knowledge (#17417)
([ca02afc](ca02afce))

</details>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants