Azure / Microsoft.Azure.StackExchangeRedis

Azure-specific wrapper for the StackExchange.Redis client library
MIT License
17 stars 14 forks source link

ConfigurationOptions with ConfigureForAzureAsync does not work with DefaultAzureCredential in a sovereign cloud #65

Open jftl6y opened 1 month ago

jftl6y commented 1 month ago

When trying to step through code in Visual Studio against a Redis instance in Azure Government using the DefaultAzureCredential, I was getting an error "Failed to acquire token' - CredentialUnavailableException: EnvironmentCredential authentication unavailable. Environment variables are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/environmentcredential/"

I was using the following code (note setting AzureAuthorityHosts.AzureGovernment): var configurationOptions = await ConfigurationOptions.Parse($"{_redisHostName}:6380").ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential(new DefaultAzureCredentialOptions() { AuthorityHost=AzureAuthorityHosts.AzureGovernment}));

I then tried setting the AzureCacheOptions directly, specifying the AzureGovernment cloud, as such but continued getting the same error: AzureCacheOptions options = new AzureCacheOptions() { Cloud = Microsoft.Identity.Client.AzureCloudInstance.AzureUsGovernment, TokenCredential = new DefaultAzureCredential(new DefaultAzureCredentialOptions() { AuthorityHost = AzureAuthorityHosts.AzureGovernment }) };

// Create a new connection var configurationOptions = await ConfigurationOptions.Parse($"{_redisHostName}:6380").ConfigureForAzureAsync(options);`

I did notice that my Azure Public Cloud identity retrieved a token, but was getting blocked because it does not have access to the Redis cache. So I looked at the Microsoft.Azure.StackExchangeRedis library and saw that apparently only methods using a Service Principal will respect the cloud settings in the AzureCacheOptions properties.

philon-msft commented 1 month ago

What Entra identity have you assigned to a Redis access policy in the cache? You'll need to configure the machine where you're running code such that DefaultAzureCredential picks up that identity, possibly using env vars. Also that client machine and identity will likely need to be in the gov cloud.

https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-azure-active-directory-for-authentication

https://learn.microsoft.com/en-us/dotnet/azure/sdk/authentication

jftl6y commented 1 month ago

What Entra identity have you assigned to a Redis access policy in the cache? You'll need to configure the machine where you're running code such that DefaultAzureCredential picks up that identity, possibly using env vars. Also that client machine and identity will likely need to be in the gov cloud.

https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-azure-active-directory-for-authentication

https://learn.microsoft.com/en-us/dotnet/azure/sdk/authentication

The Entra identity that is specified is in the Azure Gov cloud, not the Azure Commercial cloud. If you try to auth the Government cloud identity, the call throws an Exception. If you try to auth a Commercial identity, even specifying the Azure Gov cloud in the AzureCacheOptions object, it will succeed, but now I have a Commercial token/Identity trying to auth to a Redis resource in the Azure Government Cloud which does not work.

There is no way that I can see to set this - in the Microsoft.Azure.StackExchangeRedis library, the ConfigureForAzureWithTokenCredentialAsync method does not accept any additional parameters except for the token credential, and it is trying to auth that against the Azure Commercial endpoint (and from what I can tell this would affect the User and System Assigned Managed Identity calls as well).

Contrast this with the ConfigureForAzureWithServicePrincipalAsync method which does accept an AzureCloudInstance parameter and which if you follow the code to the GetIdentityClient you can see that the CacheIdentityClient.CreateForServicePrincipal method uses the AzureCloudInstance to point the identity client at the correct authentication endpoint for the given cloud (which for Azure Government is login.microsoftonline.us instead of login.microsoftonline.com). The DefaultAzureCredential method does not do any of this checking, but defaults to the Commercial endpoint with no way to change which cloud it is authenticating to.

I'd be happy to walk this through with you (John Scott (Azure) in the GAL).

philon-msft commented 1 month ago

Have you tried setting the Cloud or CloudUri field on an AzureCacheOptions instance and passing that in to ConfigureForAzureAsync() rather than using ConfigureForAzureWithTokenCredentialAsync()? That's the way to configure scenarios not supported by the basic ConfigureForAzureWith*() helper methods.

jftl6y commented 1 month ago

Have you tried setting the Cloud or CloudUri field on an AzureCacheOptions instance and passing that in to ConfigureForAzureAsync() rather than using ConfigureForAzureWithTokenCredentialAsync()? That's the way to configure scenarios not supported by the basic ConfigureForAzureWith*() helper methods.

Yes, I specified that I had tried that in the initial issue, but it does not have effect. If you follow the code, inside the ConfigureForAzureAsync method it calls the method AzureCacheOptionsProviderWithToken and calls GetIdentityClient(azureCacheOptions). In that method, the first thing it checks for is:

if (azureCacheOptions.TokenCredential is not null) // DefaultAzureCredential (or other TokenCredential) { return CacheIdentityClient.CreateForTokenCredential(azureCacheOptions.TokenCredential); }

As you can see, it's not carrying through the azureCacheOptions class which would specify the Cloud/CloudUri property, so the identity client is only ever going to attempt to get values from Azure Public.

Looking down a little further in that method, you can see that the CreateForServicePrincipal method DOES use these values in building the identity client.

There is no way that I can see from the code that it would ever point at anything other than Azure Commercial using the DefaultAzureCredential (and Managed Identity as well).

philon-msft commented 1 month ago

Are you running your code on a VM (or other Azure resource) in the gov cloud?

TokenCredential auth flow doesn't have any other provisions for specifying cloud/auth endpoint aside from the host specified in the token itself (e.g. DefaultAzureCredentialOptions.AuthorityHost=AzureAuthorityHosts.AzureGovernment)

jftl6y commented 4 weeks ago

Are you running your code on a VM (or other Azure resource) in the gov cloud?

TokenCredential auth flow doesn't have any other provisions for specifying cloud/auth endpoint aside from the host specified in the token itself (e.g. DefaultAzureCredentialOptions.AuthorityHost=AzureAuthorityHosts.AzureGovernment)

This would apply to running from my local workstation out of VS/VS Code or in an App Service in Azure Gov. There needs to be an option to specify the cloud specific authentication endpoint similar to the service principal authentication.