-
Notifications
You must be signed in to change notification settings - Fork 304
Allow Entra authentication when connecting to Redis #3047
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?
Changes from all commits
a02948a
35e4e75
62d1ceb
2617bec
e98df40
b7e008e
115b6a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.IO.Abstractions; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Linq; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Net; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Net.Http; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Net.Http.Headers; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Threading.Tasks; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -435,7 +437,7 @@ public void ConfigureServices(IServiceCollection services) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // NOTE: this is done to reuse the same connection multiplexer for both the cache and backplane | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Task<ConnectionMultiplexer> connectionMultiplexerTask = ConnectionMultiplexer.ConnectAsync(level2CacheOptions.ConnectionString); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Task<IConnectionMultiplexer> connectionMultiplexerTask = CreateConnectionMultiplexerAsync(level2CacheOptions.ConnectionString); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fusionCacheBuilder | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .WithSerializer(new FusionCacheSystemTextJsonSerializer()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -470,6 +472,33 @@ public void ConfigureServices(IServiceCollection services) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| services.AddControllers(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Creates a ConnectionMultiplexer for Redis with support for Azure Entra authentication. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="connectionString">The Redis connection string.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <returns>A task that represents the asynchronous operation. The task result contains the connected IConnectionMultiplexer.</returns> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static async Task<IConnectionMultiplexer> CreateConnectionMultiplexerAsync(string connectionString) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ConfigurationOptions options = ConfigurationOptions.Parse(connectionString); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Determine if an endpoint is localhost/loopback | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static bool IsLocalhostEndpoint(EndPoint ep) => ep switch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DnsEndPoint dns => string.Equals(dns.Host, "localhost", StringComparison.OrdinalIgnoreCase), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IPEndPoint ip => IPAddress.IsLoopback(ip.Address), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _ => false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pharring marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If no password is provided, and the endpoint (or at least one of them) is non-localhost, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // attempt to use Entra authentication. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (string.IsNullOrEmpty(options.Password) && options.EndPoints.Any(static ep => !IsLocalhostEndpoint(ep))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| options = await options.ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+482
to
+497
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it would be good to have some error handling and loggings for this function.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry for being dense, but can you show me what that might look like for your code base? Remember this is in startup, so exceptions are generally fatal and will be handled at the top-level.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can be addressed based on the discussion here- #3047 (comment). feel free to close this as duplicate. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return await ConnectionMultiplexer.ConnectAsync(options); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+496
to
+499
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| options = await options.ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential()); | |
| } | |
| return await ConnectionMultiplexer.ConnectAsync(options); | |
| try | |
| { | |
| options = await options.ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential()); | |
| } | |
| catch (CredentialUnavailableException ex) | |
| { | |
| throw new InvalidOperationException( | |
| "Failed to acquire Azure credentials for Redis. " + | |
| "Ensure that a managed identity or other Azure credentials are correctly configured " + | |
| "for this environment before enabling Entra authentication for Redis.", | |
| ex); | |
| } | |
| catch (AuthenticationFailedException ex) | |
| { | |
| throw new InvalidOperationException( | |
| "Azure authentication for Redis failed while using DefaultAzureCredential. " + | |
| "Verify that the configured identity has permission to access the Redis resource " + | |
| "and that all required environment settings are correct.", | |
| ex); | |
| } | |
| } | |
| try | |
| { | |
| return await ConnectionMultiplexer.ConnectAsync(options); | |
| } | |
| catch (RedisConnectionException ex) | |
| { | |
| throw new InvalidOperationException( | |
| "Failed to connect to Redis using the provided configuration. " + | |
| "Verify that the Redis connection string, network connectivity, and Redis instance " + | |
| "configuration are correct.", | |
| ex); | |
| } |
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.
@pharring, this suggestion from copilot seems reasonable to fix, thoughts?
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.
Copilot is right to suggest the improvement. also, we should have an additional catch block to catch rest of the exceptions if any.
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.
Again, sorry for being dense/uninformed here, but what's the guidance in this repo regarding exception handling? Copilot's suggestion is to wrap inner exceptions in InvalidOperationException. I don't see that pattern anywhere else in startup. Is it what you want?
We could create a specialized "L2CacheException" to wrap any exceptions thrown in L2 cache setup. Is that what you want?
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.
yes, that should be fine. DAB is self-hosted and users would look for the logs to troubleshoot during a failure. for this scenario we don't need a special wrapper since we just want to log it based on possible error types and possibly guide the user based on the failure may be.. feel free to close this if you feel that's not needed.
Copilot
AI
Jan 21, 2026
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 new CreateConnectionMultiplexerAsync method and its Entra authentication logic lack test coverage. Consider adding unit tests to verify:
- Correct behavior when password is provided (should not use Entra)
- Correct behavior when connecting to localhost without password (should not use Entra)
- Correct behavior when connecting to a non-localhost endpoint without password (should use Entra)
- Behavior with mixed localhost and non-localhost endpoints
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.
@pharring, would these unit tests be useful at least - since we are not adding integration tests?
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.
can we put this under a feature switch or DAB config flag for safer side or an option to opt-in/opt-out?
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.
I don't think it needs a switch because it's automatic. If you don't supply a password, and you're not using localhost, then we assume you want Entra auth.
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.
that's fine. we will have this documented. thanks!