4

I have developed a service running in an Azure Virtual Machine Scale Set that accesses configuration from the Azure Key Vault. Recently, when debugging this service, I have started to receive a RequestFailedException when accessing the configuration.

In Azure, the Key Vault is accessed through a User-Assigned Managed Identity that has the appropriate permissions. The service is currently working fine in Azure, but I am unable to debug locally. I have selected my Azure account in the Azure Service Authentication configuration section of Visual Studio 2019 version 16.8.6, and this account has permissions to the Key Vault (which is configured to allow access from all networks).

Here is a snippet showing how I access the key vault using Azure Identity 1.2.2 and Azure.Security.KeyVault.Secrets 4.1.0 on the .NET Core 3.1 platform:

private const string KeyVault = "https://<key vault name>.vault.azure.net/";

var client = new SecretClient(new Uri(KeyVault), new DefaultAzureCredential());
var secret = client.GetSecret("<name of secret in key vault>");

Apparently the DefaultAzureCredential uses the VisualStudioCredential to resolve access, but this has somehow been broken by an update to Visual Studio (the only thing changed since my last successful debug session) or possibly a change in policy from Azure Key Vault.

Full exception

Exception thrown: 'Azure.RequestFailedException' in Azure.Security.KeyVault.Secrets.dll
An unhandled exception of type 'Azure.RequestFailedException' occurred in Azure.Security.KeyVault.Secrets.dll
Service request failed.
Status: 403 (Forbidden)

Content:
{"error":{"code":"Forbidden","message":"Access denied to first party service.\r\nCaller: name=from-infra;tid=<redacted>;appid=<redacted>;iss=https://sts.windows.net/<appid>/\r\nVault: <key vault name>;location=australiaeast","innererror":{"code":"AccessDenied"}}}

Upgrading the library

I upgraded the Azure.Identity library to version 1.3.0. This produced a more detailed exception:

Microsoft.Extensions.Configuration.AzureAppConfiguration.KeyVaultReferenceException: SharedTokenCacheCredential authentication failed: AADSTS9002332: Application 'cfa8b339-82a2-471a-a3c9-0fc0be7a4093'(Azure Key Vault) is configured for use by Azure Active Directory users only.

The full exception is described in this question. It seems the DefaultAzureCredential is attempting to use a SharedTokenCacheCredential. I updated my code to explicitly disable shared token cache credentials:

private const string KeyVault = "https://<key vault name>.vault.azure.net/";

var options = new DefaultAzureCredentialOptions { ExcludeSharedTokenCacheCredential = true };
var client = new SecretClient(new Uri(KeyVault), new DefaultAzureCredential(options));

However this produces the original exception as described above. The accepted answer to the linked question encodes the tenant id, client id, and user secret into the application which I am reluctant to do. Example documentation uses the alternate libraries Microsoft.Azure.Services.AppAuthentication and Microsoft.Azure.KeyVault.

My service is running fine in Azure. I would like to debug it locally, as I was a couple of months ago. Is there anything I can do?

simonhaines
  • 481
  • 7
  • 22

3 Answers3

7

To work around this issue without hard-coding secrets, disable the Visual Studio authentication credential and use an alternate authentication method.

In my case, I used the AzureCliCredential. First I logged into Azure in my development environment with the account that has access to the Key Vault (the same account selected in Visual Studio's 'Azure Service Authentication' configuration):

az login

Then I updated the code to either use the DefaultAzureCredential with the VisualStudioCredential disabled, or used a ChainedTokenCredential selecting the appropriate methods:

var options = new DefaultAzureCredentialOptions { ExcludeVisualStudioCredential = true };
var client = new SecretClient(new Uri(KeyVault), new DefaultAzureCredential(options));

Or:

var credentials = new ChainedTokenCredential(new ManagedIdentityCredential(...), new AzureCliCredential());
var client = new SecretClient(new Uri(KeyVault), credentials);

I believe this is a bug with Visual Studio and an issue has been logged.

simonhaines
  • 481
  • 7
  • 22
  • Thanks @simonhaines - I was stuck on this for most of the day whilst upgrading away from Microsoft.Azure.Services.AppAuthentication. – huwparry22 Aug 18 '21 at 14:57
  • Thanks @simonhaines. I think if you have multiple subscriptions under your azure account, you also need to run az account set -s "subscription-name" to select the subscription in which the vault resides – dayderluv Aug 25 '21 at 22:03
  • Thank you @simonhaines - I was stuck for some time and this answer was far down the list of google for me :/ There is a bug in latest Rider that doesn't pick up the AZ login information and my Visual Studio wouldn't either.... But adding this worked like a charm. – Kiksen Aug 09 '22 at 07:02
0

If you want to debug your app locally and you need to access Azure Key vault, but DefaultAzureCredential() function does not work for you locally for some reason, you can try to use ClientSecretCredential as a workaround to access your Azure Key Vault from local.

For this, you need to register a new Azure AD App with a secret: enter image description here

And add access policy for this app in your key vault: enter image description here enter image description here

With this step is done, this app has been granted permission to access key vault.

Try the simple console app to access your key vault by ClientSecretCredential:

using System;
using System.Threading.Tasks;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

namespace key_vault_console_app
{
    class Program
    {
        static async Task Main(string[] args)
        {
            const string secretName = "<secret name>";
            var keyVaultName = "<kv name>";
            var kvUri = $"https://{keyVaultName}.vault.azure.net";


            var tenantID = "<your tenant id>";
            var appID = "<new registered azure ad app>";
            var appSec = "<app secret>";

            var userCred = new ClientSecretCredential(tenantID, appID, appSec);

            var client = new SecretClient(new Uri(kvUri), userCred);

            Console.Write(client.GetSecret(secretName).Value.Value);
           

        }
    }
}

Result:

enter image description here enter image description here

Stanley Gong
  • 11,522
  • 1
  • 8
  • 16
  • Thanks for taking the time and effort here. I can't accept this answer because it hard-codes credentials in software. If I was to work around this issue in this way, I would just hard-code the Key Vault secrets in the code instead. – simonhaines Mar 15 '21 at 22:58
  • @simonhaines,I see, if DefaultAzureCredential is necessary for you, have you tried add the user account that you logged in VS into your keyvault access policy? – Stanley Gong Mar 18 '21 at 02:37
  • yes the same user account that I logged in VS is used with the AzureCliCredential to access the key vault. This account is an owner of the Resource Group for the Key Vault. I also use it in the Portal to manage secrets, so I don't think it is an issue with the account. – simonhaines Mar 20 '21 at 02:53
0

This is a bit late to the party, but I overcame this issue by explicitly setting my tenant ids via DefaultAzureCredentialOptions. It seems that if you are using a tenant that you set up using a personal account via an 'onmicrosoft.com' domain, then Visual Studio/Code gets a bit confused.

Just add code that looks like this:

var options = new DefaultAzureCredentialOptions();
options.VisualStudioCodeTenantId = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx"; // substitute your Azure AD tenant ID
options.VisualStudioTenantId = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx";
options.SharedTokenCacheTenantId = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx";
var credential = new DefaultAzureCredential(options);

var client = new SecretClient(new Uri(<kvUri>), credential); // substitute your KV Uri

await client.SetSecretAsync(<secretName>, <secretValue>);

Cheers,

Tim.

Tim Pedersen
  • 103
  • 5