Skip to content

feat: Regional Access Boundary Update#1880

Open
vverman wants to merge 4 commits intogoogleapis:feat-tb-safrom
vverman:regional-access-boundary-update
Open

feat: Regional Access Boundary Update#1880
vverman wants to merge 4 commits intogoogleapis:feat-tb-safrom
vverman:regional-access-boundary-update

Conversation

@vverman
Copy link
Contributor

@vverman vverman commented Jan 25, 2026

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

The following are salient changes:

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

@vverman vverman requested review from a team as code owners January 25, 2026 21:35
@vverman vverman requested a review from a team January 25, 2026 21:35
@product-auto-label product-auto-label bot added the size: xl Pull request size is extra large. label Jan 25, 2026
@vverman vverman requested review from lqiu96 and nbayati January 25, 2026 21:49
@lqiu96 lqiu96 changed the base branch from feat-tb-sa to feat/agentic-identities-cloudrun February 3, 2026 19:16
@lqiu96 lqiu96 requested a review from a team as a code owner February 3, 2026 19:16
return false;
}

private boolean handleStaleRegionalAccessBoundaryError(
Copy link
Member

Choose a reason for hiding this comment

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

IIUC, this method looks like it's doing two things:

  1. Checking if there we should check for stale RAB Error
  2. And if so, checking if there is a stable RAB error message

Can this be broken into two helper methods to simply this? e.g. perhaps somehting like shouldhandleStableRAB... and handleStaleRAB...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, thanks for the suggestion.

try {
// Check for the stale regional access boundary error message in the response body.
// Note: This consumes the response stream.
String content = response.parseAsString();
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be fine since this is part of the flow to initialize this request. But if it consumes the content and closes the stream, I wonder if this may break anything that tries to consume it later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason is that there is no easy way to create a copy of an HTTP response or peek into its contents without consuming the response stream.

I decided to implement it for the reason that this flow makes a few assumptions before consuming the error:

  1. The HTTP code is a 406 which is relatively uncommon
  2. The request actually carried RAB headers

This should ensure that for the majority of scenarios this downstream error won't be a problem.

* @param token The access token to use for the refresh.
* @throws IOException If getting the universe domain fails.
*/
public final void reactiveRefreshRegionalAccessBoundary(@Nullable URI uri, AccessToken token)
Copy link
Member

Choose a reason for hiding this comment

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

are the uri and accesstoken parameters used here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got rid of uri thanks for the catch.

// this thread "won the race" and is responsible for starting the background task.
// All other concurrent threads will return false and exit immediately.
if (refreshFuture.compareAndSet(null, future)) {
CompletableFuture.runAsync(
Copy link
Member

Choose a reason for hiding this comment

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

Can you use Guava's ListeningFuture to keep with the rest of the auth-library?

Since this is called from both getReqestMetadata(...) methods (one of which takes in an executor), I think we'll also need to respect that.

I think this method should take in the executor as a param

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The way I understand: the executor in getRequestMetadataAsync is used to provide a pool of threads to execute the token refresh in the background.

My initial assumption with doing this via CompletableFuture was that the user might want the background thread to exclusively focus on token refresh and not the RAB refresh.

If we are okay with the user's background thread waiting longer for the RAB to refresh then I am okay with making this change.

Comment on lines 212 to 213
if (accessToken.getExpirationTime() != null
&& accessToken.getExpirationTime().before(new Date())) {
Copy link
Member

Choose a reason for hiding this comment

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

Since we can inject the Clock object for tests, can we do comparisons using the clock object instead of using new Date()

e.g. something like accessToken.getExpirationTime().getInstant().isBefore(clock.getInstant())?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

used getExpirationTimeMilis in the clock object.

}

/**
* Refreshes the Regional Access Boundary if it is expired or not yet fetched.
Copy link
Member

Choose a reason for hiding this comment

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

nit: the method name and docs isn't fully accurate as it does more than just checking if the boundary has expired.

If possible, can this be broken into two methods:

  1. shouldRefresh...()
  2. refresh...()

I think it might be easier to test as well. Let me know what you think

metadata.put(
RegionalAccessBoundary.HEADER_KEY, Collections.singletonList(rab.getEncodedLocations()));
}
refreshRegionalAccessBoundaryIfExpired(uri, getAccessToken());
Copy link
Member

Choose a reason for hiding this comment

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

IIUC this correctly, refreshRegionalAccessBoundaryIfExpired is not blocking right?

Shouldn't we have a blocking variant to ensure this gets the proper headers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't wish to block the main request's flow for RAB refresh. The request will still succeed without RAB headers they could face some added latency due to being routed to non-mandatory regions.

currentCooldownMillis = INITIAL_COOLDOWN_MILLIS;
}

synchronized boolean isCooldownActive() {
Copy link
Member

Choose a reason for hiding this comment

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

This method is synchronized but is going to be called often due it being called from getRequestMetadata() call. Logic doesn't run that long but it is a possibility for contention.

I'll need think about this a bit more, but I wonder if we can format this so it doesn't need a lock.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One consideration could be to have the cooldown variable as an atomic reference reducing the need for the synchronized function. I too can explore this a bit.

@vverman vverman changed the base branch from feat/agentic-identities-cloudrun to feat-tb-sa February 6, 2026 22:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: xl Pull request size is extra large.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants