Skip to content

feat: Introducing StreamingCredentialsProvider for token based authentication #3320

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

Open
wants to merge 25 commits into
base: master
Choose a base branch
from

Conversation

ndyakov
Copy link
Member

@ndyakov ndyakov commented Mar 24, 2025

Implementation Details and Key Changes

  1. New Authentication Package

    • Introduced auth package to hold types related to authentication
    • Added auth.Credentials interface to facilitate integration with external credential sources
    • Implemented basicAuth for username/password credentials
  2. Streaming Credentials Provider

    • Introduced StreamingCredentialsProvider interface for dynamic credential updates
    • Added support for managed identity scenarios where credentials are retrieved from external sources
    • Implemented ReAuthCredentialsListener for handling credential updates
    • Added comprehensive test coverage for all authentication components
  3. Documentation Updates

    • Added detailed Authentication section to README.md
    • Documented all credential provider types and their priority order:
      • Streaming Credentials Provider (highest priority)
      • Context-based Credentials Provider
      • Regular Credentials Provider
      • Username/Password fields (lowest priority)
    • Added example of Entra ID (Azure AD) authentication integration
  4. Hooks System Enhancement

    • Refactored hooksMixin to propagate hooks to child connections
    • Hooks are now triggered prior to the initialization process
    • This change addresses reported issues with hook propagation
  5. Testing

    • Added unit tests for all authentication components
    • Tested credential provider priority order
    • Tested streaming credentials provider with mock implementation
    • Tested error handling and edge cases

Security Considerations

Important Note: The hooksMixin refactor will now propagate hooks to child connections and trigger them prior to the initialization process. This change may have security implications, particularly regarding the visibility of authentication commands. For example, Redis MONITOR doesn't report AUTH commands, and this change could potentially expose sensitive authentication information through hooks. This should be carefully considered and potentially documented for users implementing custom hooks.

Related Issues

@ndyakov ndyakov self-assigned this Mar 24, 2025
@ndyakov ndyakov requested a review from htemelski March 24, 2025 15:22
@ndyakov ndyakov marked this pull request as ready for review March 24, 2025 15:22
vladvildanov
vladvildanov previously approved these changes Mar 24, 2025
Copy link
Contributor

@elena-kolevska elena-kolevska left a comment

Choose a reason for hiding this comment

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

LGTM but I think the PR title is misleading. From what I understand, this PR doesn't provide the possibility for authentication with a StreamingCredentialsProvider just yet, instead it introduces the necessary interfaces and already prepares the ground by refactoring the basic auth we were using to use the new Credentials interface.

@ndyakov ndyakov marked this pull request as draft March 25, 2025 12:11
@ndyakov
Copy link
Member Author

ndyakov commented Mar 25, 2025

Reverting this back to Draft, will continue working on it before v9.8.0-beta.2

ndyakov added 3 commits April 1, 2025 00:27
Introduces the StreamingCredentialsProvider as the CredentialsProvider
with the highest priority.

TODO: needs to be tested
ndyakov and others added 2 commits April 16, 2025 12:12
@ndyakov ndyakov marked this pull request as ready for review April 22, 2025 09:05
@ndyakov ndyakov force-pushed the ndyakov/token-based-auth branch from ff04986 to 44628c5 Compare April 22, 2025 12:41
@ndyakov ndyakov force-pushed the ndyakov/token-based-auth branch from 8fd3bd5 to 5fac913 Compare April 22, 2025 18:11
Comment on lines 17 to 24
if c.reAuth != nil {
err := c.reAuth(credentials)
if err != nil {
if c.onErr != nil {
c.onErr(err)
}
}
}

Choose a reason for hiding this comment

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

What do you think about using early returns here?

Choose a reason for hiding this comment

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

Also instead of doing the c.OnErr != nil check, can we instead directly call c.OnError?

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree on calling c.OnError. As for early returns - i don't have a preference when the function is as short as this, but makes sense if we are gonna use them elsewhere and would like to be consistent. Thank you for the suggestions!

Comment on lines +365 to +383
username, password := "", ""
if c.opt.StreamingCredentialsProvider != nil {
credentials, cancelCredentialsProvider, err := c.opt.StreamingCredentialsProvider.
Subscribe(c.newReAuthCredentialsListener(ctx, conn))
if err != nil {
return fmt.Errorf("failed to subscribe to streaming credentials: %w", err)
}
c.onClose = c.wrappedOnClose(cancelCredentialsProvider)
username, password = credentials.BasicAuth()
} else if c.opt.CredentialsProviderContext != nil {
username, password, err = c.opt.CredentialsProviderContext(ctx)
if err != nil {
return fmt.Errorf("failed to get credentials from context provider: %w", err)
}
} else if c.opt.CredentialsProvider != nil {
username, password = c.opt.CredentialsProvider()
} else if c.opt.Username != "" || c.opt.Password != "" {
username, password = c.opt.Username, c.opt.Password
}

Choose a reason for hiding this comment

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

Suggested change
username, password := "", ""
if c.opt.StreamingCredentialsProvider != nil {
credentials, cancelCredentialsProvider, err := c.opt.StreamingCredentialsProvider.
Subscribe(c.newReAuthCredentialsListener(ctx, conn))
if err != nil {
return fmt.Errorf("failed to subscribe to streaming credentials: %w", err)
}
c.onClose = c.wrappedOnClose(cancelCredentialsProvider)
username, password = credentials.BasicAuth()
} else if c.opt.CredentialsProviderContext != nil {
username, password, err = c.opt.CredentialsProviderContext(ctx)
if err != nil {
return fmt.Errorf("failed to get credentials from context provider: %w", err)
}
} else if c.opt.CredentialsProvider != nil {
username, password = c.opt.CredentialsProvider()
} else if c.opt.Username != "" || c.opt.Password != "" {
username, password = c.opt.Username, c.opt.Password
}
username, password := c.opt.Username, c.opt.Password
switch {
case c.opt.StreamingCredentialsProvider != nil:
credentials, cancelCredentialsProvider, err := c.opt.StreamingCredentialsProvider.
Subscribe(c.newReAuthCredentialsListener(ctx, conn))
if err != nil {
return fmt.Errorf("failed to subscribe to streaming credentials: %w", err)
}
c.onClose = c.wrappedOnClose(cancelCredentialsProvider)
username, password = credentials.BasicAuth()
case c.CredentialsProviderContext != nil:
username, password, err = c.opt.CredentialsProviderContext(ctx)
if err != nil {
return fmt.Errorf("failed to get credentials from context provider: %w", err)
}
case c.opt.CredentialsProvider != nil:
username, password = c.opt.CredentialsProvider()
}

Do you think this will improve the readability?

Copy link
Member Author

Choose a reason for hiding this comment

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

Although both versions are identical, I think the if / else if/ else makes it easier to follow the priority of the credentials providers. This is a personal preference, let me know if you have strong opinion on the topic.

@ndyakov ndyakov requested a review from Copilot April 24, 2025 14:33
Copy link
Contributor

@Copilot Copilot AI left a 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 PR introduces a streaming credentials provider mechanism for token‐based authentication, refactoring connection initialization and hook propagation while also updating tests and documentation for authentication.

  • Introduces new types and interfaces in the auth package for dynamic credential updates.
  • Refactors core connection initialization to use the streaming provider and re-authentication listener.
  • Updates tests and docs (including README and examples) to cover new authentication flows and hook propagation improvements.

Reviewed Changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tx.go Moves hooksMixin from Tx struct into baseClient; updates newTx() accordingly.
sentinel.go Updates onClose hook assignment using wrappedOnClose.
ring_test.go Enhances pipeline hook tests by skipping connection initialization commands.
redis_test.go Introduces tests verifying proper priority usage of Streaming, context, regular, and static credentials.
redis.go Adds functions to support re-authentication on credentials updates; adjusts error wrapping and hook usage.
probabilistic_test.go Modifies expectations to be more flexible about command lengths.
osscluster*.go Minor nil-check improvements and injection of streaming authentication options.
options.go Updates option comments and adds StreamingCredentialsProvider field.
internal_test.go, example_instrumentation_test.go, doctests, command_recorder_test.go Additions and tweaks to tests and examples for improved instrumentation and command recording support.
auth/* New auth package files implementing the streaming credentials API and its tests.
README.md Updates documentation describing the new authentication priority order and provider usage.
.github/wordlist.txt Updates wordlist with new authentication-related keywords.
Comments suppressed due to low confidence (2)

tx.go:29

  • Verify that removing hooksMixin from the Tx struct and relying on baseClient.hooksMixin is an intentional design change to ensure that all hook functionality remains effective.
hooksMixin: c.hooksMixin.clone(),

redis.go:441

  • There appears to be a potential naming inconsistency between 'DisableIdentity' and 'DisableIndentity'. Please confirm that the correct option name is used to avoid confusion.
if !c.opt.DisableIdentity && !c.opt.DisableIndentity {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants