-
Notifications
You must be signed in to change notification settings - Fork 15
feat: implement credential caching and retrieval mechanism in auth #1017
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: implement credential caching and retrieval mechanism in auth #1017
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request implements a credential caching and retrieval mechanism to address the lack of credential reuse in the authentication layer. The changes ensure that Azure SDK credential objects are created once and reused across subsequent calls, enabling proper utilization of the SDK's built-in token caching and automatic renewal capabilities.
Key changes:
- Introduces a credential holder pattern with thread-safe lazy initialization using
sync.Onceandsync.RWMutex - Refactors all eight authentication methods to use the new
getOrCreateCredentialhelper - Adds comprehensive unit tests with concurrency validation, improving code coverage from 23.2% to 54.8%
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| internal/api/auth.go | Adds credential caching infrastructure (credentialType enum, credentialHolder struct, credentials map with RWMutex) and refactors all authentication methods to use the new getOrCreateCredential helper for credential reuse |
| internal/api/auth_test.go | Implements comprehensive unit tests covering credential caching behavior, thread safety with concurrent access, error handling, and authentication method functionality using mock credentials |
| holder.once.Do(func() { | ||
| tflog.Debug(ctx, fmt.Sprintf("Initializing credential for type: %s", credType)) | ||
| holder.credential, holder.err = factory() | ||
| if holder.err != nil { | ||
| tflog.Error(ctx, fmt.Sprintf("Failed to create credential for type %s: %s", credType, holder.err.Error())) | ||
| } else { | ||
| tflog.Debug(ctx, fmt.Sprintf("Successfully created credential for type: %s", credType)) | ||
| } | ||
| }) | ||
|
|
||
| return holder.credential, holder.err |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The credential caching mechanism caches errors permanently using sync.Once. This means if credential creation fails due to a transient error (e.g., temporary network issue, rate limiting), all subsequent attempts will return the same cached error without retrying credential creation. Consider whether this behavior is acceptable for your use case, or if you need a mechanism to retry credential creation after transient failures. For long-running provider instances, this could cause persistent authentication failures even after the underlying issue is resolved.
| holder.once.Do(func() { | |
| tflog.Debug(ctx, fmt.Sprintf("Initializing credential for type: %s", credType)) | |
| holder.credential, holder.err = factory() | |
| if holder.err != nil { | |
| tflog.Error(ctx, fmt.Sprintf("Failed to create credential for type %s: %s", credType, holder.err.Error())) | |
| } else { | |
| tflog.Debug(ctx, fmt.Sprintf("Successfully created credential for type: %s", credType)) | |
| } | |
| }) | |
| return holder.credential, holder.err | |
| tflog.Debug(ctx, fmt.Sprintf("Initializing credential for type: %s", credType)) | |
| credential, err := factory() | |
| if err != nil { | |
| holder.err = err | |
| tflog.Error(ctx, fmt.Sprintf("Failed to create credential for type %s: %s", credType, err.Error())) | |
| return nil, err | |
| } | |
| holder.credential = credential | |
| holder.err = nil | |
| tflog.Debug(ctx, fmt.Sprintf("Successfully created credential for type: %s", credType)) | |
| return holder.credential, nil |
Co-authored-by: Copilot <[email protected]>
Fixes: #711
With this pull request we address the lack of credential reuse in the
GetTokenForScopesmethod.The authentication methods in
auth.gocreated a new credential object on every call, bypassing the Azure SDK's built-in token caching and renewal mechanisms. This change implements a credential caching system that creates credentials once and reuses them across subsequent calls.The
GetTokenForScopesmethod and its associated authentication methods (AuthenticateClientSecret,AuthenticateUsingCli,AuthenticateClientCertificate, etc.) were creating new Azure SDK credential objects on every invocation.This approach had these issues:
In this pull request we implement an interface-based credential holder pattern with map-based lookup.
credentialTypeenum representing the eight different credential types supported by the providercredentialHolderstruct that wraps anazcore.TokenCredentialwith thread-safe lazy initialization usingsync.OnceAuthstruct that stores cached credential holdersgetOrCreateCredentialhelper method that handles the lazy initialization and caching logicWe also introduced a set of tests to improve code coverage;
go test -coverprofile=coverage.out -run "TestUnit" ./internal/api/... && go tool cover -func=coverage.out | grep -E "(auth\.go|total)"Before:
After: