Allow Entra authentication when connecting to Redis#3047
Allow Entra authentication when connecting to Redis#3047pharring wants to merge 7 commits intoAzure:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds support for Azure Entra authentication when connecting to Azure Managed Redis or Azure Cache for Redis instances. The implementation automatically detects when Entra authentication should be used based on whether a password is present in the connection string and whether the endpoint is localhost.
Changes:
- Added new
CreateConnectionMultiplexerAsyncmethod that handles both traditional password-based and Entra-based Redis authentication - Added Microsoft.Azure.StackExchangeRedis package to enable Azure-specific Redis features
- Updated Azure.Identity and Microsoft.Extensions.Configuration.Binder package versions
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/Service/Startup.cs | Added CreateConnectionMultiplexerAsync method with Entra authentication support and localhost detection logic |
| src/Service/Azure.DataApiBuilder.Service.csproj | Added Microsoft.Azure.StackExchangeRedis package reference |
| src/Directory.Packages.props | Updated Azure.Identity to 1.17.1, Microsoft.Extensions.Configuration.Binder to 8.0.2, and added Microsoft.Azure.StackExchangeRedis 3.3.1 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| options = await options.ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential()); | ||
| } | ||
|
|
||
| return await ConnectionMultiplexer.ConnectAsync(options); |
There was a problem hiding this comment.
The CreateConnectionMultiplexerAsync method lacks error handling for Azure credential failures or Redis connection failures. When DefaultAzureCredential fails to authenticate (e.g., no managed identity is available, or credentials are misconfigured), or when ConfigureForAzureWithTokenCredentialAsync fails, the method will throw an unhandled exception. Consider adding try-catch blocks with more descriptive error messages to help users troubleshoot authentication or connection issues.
| 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.
@pharring, this suggestion from copilot seems reasonable to fix, thoughts?
There was a problem hiding this comment.
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.
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?
| 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, | ||
| }; | ||
|
|
||
| // 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(IsLocalhostEndpoint)) | ||
| { | ||
| options = await options.ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential()); | ||
| } | ||
|
|
||
| return await ConnectionMultiplexer.ConnectAsync(options); | ||
| } |
There was a problem hiding this comment.
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.
@pharring, would these unit tests be useful at least - since we are not adding integration tests?
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
souvikghosh04
left a comment
There was a problem hiding this comment.
Added some review comments. also, it would be good to mention how this was tested as I don't see any supporting tests for the change. having test coverage with different scenarios would be great.
| options = await options.ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential()); | ||
| } | ||
|
|
||
| return await ConnectionMultiplexer.ConnectAsync(options); |
There was a problem hiding this comment.
Copilot is right to suggest the improvement. also, we should have an additional catch block to catch rest of the exceptions if any.
| { | ||
| // 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); |
There was a problem hiding this comment.
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.
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.
| 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, | ||
| }; | ||
|
|
||
| // 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()); | ||
| } |
There was a problem hiding this comment.
it would be good to have some error handling and loggings for this function.
There was a problem hiding this comment.
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.
Why make this change?
So you can use an Azure Managed Redis or Azure Cache for Redis instance with Entra authentication as a level 2 cache.
What is this change?
If the Redis connection string does not include a password and you are NOT connecting to localhost, then we assume you are connecting to an instance that requires Entra authentication. In that case, we use the DefaultAzureCredential (managed identity) to connect.
How was this tested?
Tested manually and ran unit tests.
Sample Request(s)
N/A