Skip to content

Give iamSigner pointer receivers so the email cache works - #778

Open
omlahore wants to merge 2 commits into
firebase:masterfrom
omlahore:fix/iamsigner-caches-service-account
Open

Give iamSigner pointer receivers so the email cache works#778
omlahore wants to merge 2 commits into
firebase:masterfrom
omlahore:fix/iamsigner-caches-service-account

Conversation

@omlahore

@omlahore omlahore commented Aug 31, 2026

Copy link
Copy Markdown

iamSigner.Email is written to discover the service account once and cache it:

func (s iamSigner) Email(ctx context.Context) (string, error) {
	if s.serviceAcct != "" {
		return s.serviceAcct, nil
	}

	s.mutex.Lock()
	defer s.mutex.Unlock()
	result, err := s.callMetadataService(ctx)
	...
	s.serviceAcct = result
	return result, nil
}

The receiver is a value, so s.serviceAcct = result writes to a copy that is discarded when the method returns. The guard at the top therefore never sees a cached value, and every call re-queries the GCE metadata server over HTTP. Sign calls Email, so that is an extra round trip per custom token minted through the IAM path.

Two things say caching is the intent rather than the value receiver being deliberate: the early-return check exists at all, and mutex is held as a *sync.Mutex so that copies of the struct still share one lock. Guarding a write that cannot persist is only explicable as an oversight.

staticcheck reports it as SA4005, "ineffective assignment to field iamSigner.serviceAcct".

The change moves all four iamSigner methods to pointer receivers together, so the method set stays consistent. newIAMSigner already returns *iamSigner, newCryptoSigner passes that straight through, and auth_test.go:129 asserts client.signer.(*iamSigner), so nothing stores a bare iamSigner value and the cryptoSigner interface is still satisfied.

Verified: go build ./... passes, go test ./auth/... green, gofmt -l clean, SA4005 gone under GOOS=linux and GOOS=darwin.

Separately, and not included here since it is a different concern: auth/user_mgt.go:1463-1464 and auth/project_config_mgt.go:29 spell the struct tag option omitEmpty, which encoding/json does not recognise (it only accepts omitempty). Both structs are decode-only today so nothing misbehaves at runtime, but the tags do not do what they read as. Say the word and it goes in a separate PR.

Email() checks s.serviceAcct for a cached value, takes the mutex, and
assigns the discovered account back. With a value receiver that write
lands on a copy, so the cache never fills and every Email() call makes a
fresh HTTP request to the metadata server. Sign() calls Email(), so this
is once per custom token.

The struct holds mutex as a *sync.Mutex, which only makes sense if the
signer is meant to be shared and mutated, and newIAMSigner already
returns *iamSigner. Nothing constructs or stores a bare iamSigner value.

@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 updates the receiver type of iamSigner methods in auth/token_generator.go from value to pointer receivers. The review feedback highlights that this change introduces a data race and a concurrency bug in the Email method due to unprotected concurrent access to s.serviceAcct, and suggests acquiring the lock at the beginning of the method to ensure thread safety and prevent redundant metadata service calls.

Comment thread auth/token_generator.go
Moving to pointer receivers made serviceAcct genuinely shared, so the
unlocked read at the top of Email raced the locked write at the bottom.
It also let every concurrent caller past the empty check, so each one
queried the metadata server in turn.

Reading under the same lock fixes both. Added a test that runs 50
concurrent Email calls and asserts exactly one metadata request; under
-race it reports the data race and 50 requests without this change.
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.

1 participant