Skip to content
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
05ca9b4
add gcpServiceAccountIdentityCallCredentials call credential type
Pranjali-2501 Mar 15, 2026
17e2395
resolving comments
Pranjali-2501 Apr 1, 2026
9991c91
Merge branch 'master' into call-credential-changes
Pranjali-2501 Apr 14, 2026
bcf534a
update go.mod and go.sum
Pranjali-2501 Apr 14, 2026
127a77d
Merge branch 'master' into call-credential-changes
Pranjali-2501 Apr 16, 2026
1292ed3
modify go.mod
Pranjali-2501 Apr 16, 2026
c8233cc
update go.mod
Pranjali-2501 Apr 16, 2026
67e45d9
added tests and map errors returned
Pranjali-2501 May 7, 2026
9cefff0
update go.mod
Pranjali-2501 May 7, 2026
af5aa9d
resolving comments
Pranjali-2501 May 8, 2026
c9f8d44
addressing comments
Pranjali-2501 May 13, 2026
52ef70c
update tests
Pranjali-2501 May 14, 2026
0b0b753
resolving comments
Pranjali-2501 May 20, 2026
73fc463
resolve comments
Pranjali-2501 May 26, 2026
29ac2ed
gcp_authn filter implementation
Pranjali-2501 May 12, 2026
c4d6b54
minor changes
Pranjali-2501 May 12, 2026
1ff1886
changes from pr-9140
Pranjali-2501 May 26, 2026
1c010a2
add tests
Pranjali-2501 May 27, 2026
f742a06
Merge branch 'master' into http_filter
Pranjali-2501 May 27, 2026
3ce7954
Merge branch 'http_filter' into http-authn-filter
Pranjali-2501 May 27, 2026
f925bf2
update test
Pranjali-2501 May 27, 2026
7a82ab1
fix flaky test
Pranjali-2501 May 27, 2026
5e2a782
handle error statements
Pranjali-2501 Jun 1, 2026
e38cbc5
addressed comments
Pranjali-2501 Jun 5, 2026
ad889dc
minor test fixes
Pranjali-2501 Jun 9, 2026
b192e1d
fixed minor nits
Pranjali-2501 Jun 9, 2026
61921d3
Merge branch 'master' into http-authn-filter
Pranjali-2501 Jun 9, 2026
705045d
resolve conflicts
Pranjali-2501 Jun 9, 2026
4ff3369
resolve comments
Pranjali-2501 Jun 10, 2026
1d8e3f6
resolve comments
Pranjali-2501 Jun 11, 2026
7838ed6
Merge branch 'master' into http-authn-filter
Pranjali-2501 Jun 11, 2026
c8dc70d
merge conflicts
Pranjali-2501 Jun 11, 2026
1a28b0f
Merge branch 'master' into http-authn-filter
Pranjali-2501 Jun 12, 2026
9f7bbb0
merge master
Pranjali-2501 Jun 12, 2026
e0861b0
minor nits
Pranjali-2501 Jun 12, 2026
834dfcc
update test
Pranjali-2501 Jun 15, 2026
25afc97
update tests
Pranjali-2501 Jun 16, 2026
f88b9cc
add ctx in ClientFilter
Pranjali-2501 Jun 18, 2026
5490ad3
resolve comments
Pranjali-2501 Jun 20, 2026
1cf1741
minor changes
Pranjali-2501 Jun 22, 2026
9ffad06
resolve comments
Pranjali-2501 Jun 24, 2026
9e4c6a5
Merge branch 'master' into http-authn-filter
Pranjali-2501 Jun 24, 2026
da47c5f
fix
Pranjali-2501 Jun 24, 2026
6efc822
resolve comments
Pranjali-2501 Jun 25, 2026
5513377
nits
Pranjali-2501 Jun 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 53 additions & 25 deletions credentials/google/gcp_service_account_identity_credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,28 +36,37 @@ import (
"google.golang.org/grpc/status"
)

// preemptiveRefresh is the window before a token's actual expiration during
// which the token is considered stale. Requests using a stale but still valid
// token will trigger a background asynchronous refresh. This avoids blocking
// the current RPC, preventing periodic latency spikes during token refresh.
const preemptiveRefresh = 1 * time.Minute
const (
// preemptiveRefresh is the window before a token's actual expiration during
// which the token is considered stale. Requests using a stale but still
// valid token will trigger a background asynchronous refresh. This avoids
// blocking the current RPC, preventing periodic latency spikes during token
// refresh.
preemptiveRefresh = 1 * time.Minute

// metadataTimeout is the timeout for the call to the metadata server to
// prevent the background token fetch from hanging indefinitely in case
// of connection issues.
metadataTimeout = 60 * time.Second
)

type gcpServiceAccountIdentityCallCreds struct {
// The following fields are initialized at creation time and are read-only
// after that.
ctx context.Context
audience string
creds *auth.Credentials
backoff backoff.Strategy

// The following fields are protected by mu.
mu sync.Mutex
token *auth.Token
tokenExpiry time.Time // timestamp after which the cached token is considered invalid
preemptiveTokenRefresh time.Time // timestamp after which background preemptive refresh is triggered
fetching bool // true if a background token fetch is in progress
nextRetryTime time.Time // timestamp after which we can attempt the next token fetch
retryAttempt int // consecutive fetch failure count used to compute backoff delay
lastErr error // cached error returned from the most recent token fetch attempt
tokenExpiry time.Time // timestamp after which the cached token is considered invalid
preemptiveTokenRefresh time.Time // timestamp after which background preemptive refresh is triggered
fetching chan struct{} // used to deduplicate concurrent background token fetches
nextRetryTime time.Time // timestamp after which we can attempt the next token fetch
retryAttempt int // consecutive fetch failure count used to compute backoff delay
lastErr error // cached error returned from the most recent token fetch attempt
}

func init() {
Expand All @@ -79,7 +88,7 @@ func init() {
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func NewServiceAccountIdentityCredentials(audience string) (credentials.PerRPCCredentials, error) {
func NewServiceAccountIdentityCredentials(ctx context.Context, audience string) (credentials.PerRPCCredentials, error) {
if audience == "" {
return nil, fmt.Errorf("credentials: audience cannot be empty")
}
Expand All @@ -90,6 +99,7 @@ func NewServiceAccountIdentityCredentials(audience string) (credentials.PerRPCCr
}

return &gcpServiceAccountIdentityCallCreds{
ctx: ctx,
audience: audience,
creds: creds,
backoff: internal.BackoffStrategy,
Expand All @@ -111,33 +121,48 @@ func (c *gcpServiceAccountIdentityCallCreds) GetRequestMetadata(ctx context.Cont
}

c.mu.Lock()
defer c.mu.Unlock()

// If token is valid, return it. If it's also stale, trigger a background
// refresh if not already running and return the current token.
if c.token != nil && c.isTokenValidLocked() {
if c.isTokenStaleLocked() && !c.fetching {
c.fetching = true
if c.isTokenStaleLocked() && c.fetching == nil {
c.fetching = make(chan struct{})
go c.startFetch()
}
defer c.mu.Unlock()
return map[string]string{
"authorization": "Bearer " + c.token.Value,
}, nil
}

if c.lastErr != nil && time.Now().Before(c.nextRetryTime) {
c.mu.Unlock()
return nil, c.lastErr
}

token, err := c.creds.TokenProvider.Token(context.Background())
c.updateStateLocked(token, err)
if err != nil {
return nil, c.lastErr
if c.fetching == nil {
c.fetching = make(chan struct{})
go c.startFetch()
}
wait := c.fetching
c.mu.Unlock()

select {
case <-wait:
c.mu.Lock()
defer c.mu.Unlock()
if c.token != nil && c.isTokenValidLocked() {
return map[string]string{
"authorization": "Bearer " + c.token.Value,
}, nil
}
if c.lastErr != nil {
return nil, c.lastErr
}
return nil, status.Error(codes.Unavailable, "credentials: fetched token is expired")
case <-ctx.Done():
return nil, ctx.Err()
}

return map[string]string{
"authorization": "Bearer " + c.token.Value,
}, nil
}

// RequireTransportSecurity indicates whether the credentials requires
Expand All @@ -161,12 +186,15 @@ func (c *gcpServiceAccountIdentityCallCreds) isTokenValidLocked() bool {
// startFetch initiates a token fetch and updates the credential
// state upon completion.
func (c *gcpServiceAccountIdentityCallCreds) startFetch() {
token, err := c.creds.TokenProvider.Token(context.Background())
ctx, cancel := context.WithTimeout(c.ctx, metadataTimeout)
defer cancel()
token, err := c.creds.TokenProvider.Token(ctx)

c.mu.Lock()
defer c.mu.Unlock()

c.fetching = false
close(c.fetching)
c.fetching = nil
c.updateStateLocked(token, err)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ func setupTestGCPServiceAccountIdentityCreds(t *testing.T, stubToken *stubTokenP
}
t.Cleanup(func() { internal.NewIDTokenCredentials = origNewIDTokenCredentials })

creds, err := google.NewServiceAccountIdentityCredentials("audience")
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
creds, err := google.NewServiceAccountIdentityCredentials(ctx, "audience")
if err != nil {
t.Fatalf("NewServiceAccountIdentityCredentials() failed: %v", err)
}
Expand All @@ -141,7 +143,9 @@ func setupTestGCPServiceAccountIdentityCreds(t *testing.T, stubToken *stubTokenP
// when called with empty audience.
func (s) TestNewServiceAccountIdentityCredentials_EmptyAudience(t *testing.T) {
const wantErr = "credentials: audience cannot be empty"
if _, err := google.NewServiceAccountIdentityCredentials(""); err == nil || !strings.Contains(err.Error(), wantErr) {
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
if _, err := google.NewServiceAccountIdentityCredentials(ctx, ""); err == nil || !strings.Contains(err.Error(), wantErr) {
t.Fatalf("NewServiceAccountIdentityCredentials() returned error = %v, want error containing %q", err, wantErr)
}
}
Expand Down
11 changes: 3 additions & 8 deletions internal/xds/balancer/clustermanager/picker.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func newPickerGroup(idToPickerState map[string]*subBalancerState) *pickerGroup {
}

func (pg *pickerGroup) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
cluster := getPickedCluster(info.Ctx)
cluster := PickedCluster(info.Ctx)
if p := pg.pickers[cluster]; p != nil {
return p.Pick(info)
}
Expand All @@ -52,17 +52,12 @@ func (pg *pickerGroup) Pick(info balancer.PickInfo) (balancer.PickResult, error)

type clusterKey struct{}

func getPickedCluster(ctx context.Context) string {
// PickedCluster returns the cluster name stored in the context.
func PickedCluster(ctx context.Context) string {
cluster, _ := ctx.Value(clusterKey{}).(string)
return cluster
}

// GetPickedClusterForTesting returns the cluster in the context; to be used
// for testing only.
func GetPickedClusterForTesting(ctx context.Context) string {
return getPickedCluster(ctx)
}

// SetPickedCluster adds the selected cluster to the context for the
// xds_cluster_manager LB policy to pick.
func SetPickedCluster(ctx context.Context, cluster string) context.Context {
Expand Down
4 changes: 2 additions & 2 deletions internal/xds/httpfilter/extproc/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,7 @@ func (s) TestBuildClientInterceptor_Success(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
builder := builder{}
filter := builder.BuildClientFilter()
filter := builder.BuildClientFilter("")
defer filter.Close()

intptr, err := filter.BuildClientInterceptor(tc.cfg, tc.override)
Expand Down Expand Up @@ -818,7 +818,7 @@ func (s) TestBuildClientInterceptor_Failure(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
builder := builder{}
filter := builder.BuildClientFilter()
filter := builder.BuildClientFilter("")
defer filter.Close()

_, err := filter.BuildClientInterceptor(tc.cfg, tc.override)
Expand Down
2 changes: 1 addition & 1 deletion internal/xds/httpfilter/extproc/ext_proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ func (builder) IsTerminal() bool {
return false
}

func (builder) BuildClientFilter() httpfilter.ClientFilter {
func (builder) BuildClientFilter(string) httpfilter.ClientFilter {
return clientFilter{}
}

Expand Down
2 changes: 1 addition & 1 deletion internal/xds/httpfilter/fault/fault.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func (builder) IsTerminal() bool {
return false
}

func (builder) BuildClientFilter() httpfilter.ClientFilter {
func (builder) BuildClientFilter(string) httpfilter.ClientFilter {
return clientFilter{}
}

Expand Down
Loading
Loading