Skip to content

Conversation

@vverman
Copy link
Contributor

@vverman vverman commented Jan 17, 2026

Contains changes for the feature Regional Access Boundary (Previously Called Trust Boundaries).

The following are salient changes:

  1. Calls to refresh RAB are now all async and in a separate thread.
  2. Logic for refreshing RAB now exists in its own class for cleaner maintenance.
  3. Self-signed jwts are within scope.
  4. Changes to how we trigger RAB refresh and deal with refresh errors.

@vverman vverman requested a review from a team as a code owner January 17, 2026 01:31

const log = makeLog('auth');

// googleapis.com
Copy link

Choose a reason for hiding this comment

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

Suggested change
// googleapis.com

export interface RegionalAccessBoundaryData {
/**
* The readable text format of the allowed regional access boundary locations.
* This is optional, as it might not be present if no regional access boundary is enforced.
Copy link

Choose a reason for hiding this comment

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

This is not accurate. We can't tell if RAB is enforced or not by the list being present.
Also, in the last iteration of design when we removed no-op, this field is not considered optional anymore either. I believe a valid response would have both, but we mostly care about the encodedLocations as that's the field that'd be present in the header.

Copy link
Contributor Author

@vverman vverman Jan 28, 2026

Choose a reason for hiding this comment

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

Thanks for the catch! I think I missed this while making the changes from the TB -> RAB.

I believe as per the latest confirmation, we now expect both the encoded and the readable locations as part of the response from lookup. So I will make the changes to the comments accordingly.

Comment on lines 149 to 152
private isGlobalEndpoint(url?: string | URL): boolean {
if (!url) {
return true;
}
Copy link

Choose a reason for hiding this comment

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

The isGlobalEndpoint(url?) method currently accepts an optional URL and defaults to true if it's missing. While this works, it makes the method's contract a bit less explicit.

What do you think about refactoring this to make isGlobalEndpoint's signature stricter by always requiring a URL? The logic for handling a missing URL could be moved up to the caller (maybeTriggerRegionalAccessBoundaryRefresh).

This would make it clearer that isGlobalEndpoint's single responsibility is to check a given URL, and the calling method is responsible for the "default to global" logic when no URL is present.

const maxRetryTime = 60 * 1000;
let shouldContinue = true;

while (shouldContinue) {
Copy link

Choose a reason for hiding this comment

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

This library already uses gaxios, which provides a powerful and configurable built-in retry mechanism (including exponential backoff) via its retryConfig. This library already uses gaxios, which provides a powerful and configurable built-in retry mechanism (including exponential backoff) via its retryConfig.

The fetchRegionalAccessBoundary method already enables gaxios retries, and then we're wrapping it here in the custom retry logic. I'd recommend refactoring to remove this while loop. Instead, you can add retry conditions to the retryConfig in fetchRegionalAccessBoundary method to achieve the desired behavior. This would leverage existing gaxios functionality, simplify the code, and align with the library's established patterns for request retries.

Comment on lines 427 to 435
async getRequestHeaders(url?: string | URL): Promise<Headers> {
const accessTokenResponse = await this.getAccessToken();
const headers = new Headers({
authorization: `Bearer ${accessTokenResponse.token}`,
});
this.maybeTriggerRegionalAccessBoundaryRefresh(
url,
accessTokenResponse.token!,
);
Copy link

Choose a reason for hiding this comment

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

I noticed two problems with this change.

  1. The caller is not passing the URL to the getRequestHeaders() method, both in this file and in externalAccountAuthorizedUserClient.ts, so url is always going to be null.

  2. Even though this is not a breaking change since we're allowing url parameter to be null, I think we can still make the design better by moving the maybeTriggerRegionalAccessBoundaryRefresh() call to the caller method (requestAsync()). This way the getRequestHeaders() doesn't have to know a url, and it can do only 1 task which is getting the headers without potentially invoking a background fetch. Another benefit is that we can enforce that url is not empty, and update the downstream methods such as regionalAccessBoundaryManager.maybeTriggerRegionalAccessBoundaryRefresh and isGlobalEndpoint to expect a non-null URL as well.

Comment on lines 525 to 528
!reAuthRetried
) {
this.clearRegionalAccessBoundaryCache();
return await this.requestAsync<T>(opts, true);
Copy link

Choose a reason for hiding this comment

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

I think we should have two separate flags for the auth retry, and this one (retryWithoutRAB maybe?). Consider this scenario which the single flag approach would fail to handle:

  1. Initial Call: requestAsync(opts) is called.
  2. First Failure: The request fails with a stale RAB error. The catch block clears the RAB cache and retries
    the request, with reAuthRetried set to True.
  3. Second Failure: The retried request (now sent without the x-allowed-locations header) fails, but this time with a 401 Unauthorized error because the access token has just expired.

With a single reAuthRetried flag, this second failure would not be handled because it thinks the token has already been refreshed and the request has been retried with the updated credential, and an error would be thrown.

If we use two flags, we can ensure that we only try once without the allowed location header, and then we attempt a true reAuth.

}

async getRequestHeaders(): Promise<Headers> {
async getRequestHeaders(url?: string | URL): Promise<Headers> {
Copy link

Choose a reason for hiding this comment

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

Same concern with the getRequestHeaders method in the baseexternalclient.ts file.

response = await this.transporter.request<T>(opts);
response = await this.transporter.request<T>(requestOpts);
} catch (e) {
if (
Copy link

Choose a reason for hiding this comment

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

Same concern with reusing the reAuthRetried flag for the two different scenario.

* @throws {Error} If the URL cannot be constructed for a compatible client.
*/
protected async getTrustBoundaryUrl(): Promise<string | null> {
public async getRegionalAccessBoundaryUrl(): Promise<string | null> {
Copy link

Choose a reason for hiding this comment

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

Why this method became public?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made it so for ease of testing the getRegionalAccessBoundaryUrl logic. For ex: in the base external client we have scenarios where we want to test if the right url is returned or error is thrown in case of workload, workforce or a null audience.

'https://staging-iamcredentials.sandbox.{universe_domain}/v1/projects/-/serviceAccounts/{service_account_email}/allowedLocations';

export const WORKLOAD_LOOKUP_ENDPOINT =
'https://iamcredentials.{universe_domain}/v1/projects/{project_id}/locations/global/workloadIdentityPools/{pool_id}/allowedLocations';
Copy link

Choose a reason for hiding this comment

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

We heard back from the backend team that the TPC won't be supported in the future either, so we don't have to consider universe domains other than GDU in here. Let's hardcode it in the URLs and simplify our logic too.

Comment on lines 144 to 147
this.maybeTriggerRegionalAccessBoundaryRefresh(
url ?? undefined,
(tokens.access_token ?? tokens.id_token)!,
);
Copy link

Choose a reason for hiding this comment

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

IIUC the flow correctly, this block is for requesting an id token, and id tokens are not supported by RAB, so there is no need to trigger a refresh here.

@vverman vverman force-pushed the trust-boundary-upto-speed branch from 238aed8 to f40a3e1 Compare January 29, 2026 02:12
…e requestAsync level. RAB urls now have googleapis as universe domain.
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