AzureAppConfiguration does not work with Azure.Identity.TokenCredentials - azure

I try to add AzureAppConfiguration to my dotnet core web application using the following Code:
var azureCredential = new DefaultAzureCredential();
config.AddAzureAppConfiguration(options =>
options.Connect(new Uri("https://MYCONFIGURATION.azconfig.io"), azureCredential).ConfigureKeyVault(kv =>
{
kv.SetCredential(azureCredential);
})
....
this fails with the Following Error:
Azure.RequestFailedException: Service request failed.
Status: 403 (Forbidden)
If I do use the Connection String to connect to the AzureAppConfiguration itself it does work:
var azureCredential = new DefaultAzureCredential();
config.AddAzureAppConfiguration(options =>
options.Connect("Endpoint=https://ac-mobileapps-dev.azconfig.io;Id=MYID;Secret=MYSECRET").ConfigureKeyVault(kv =>
{
kv.SetCredential(azureCredential);
})
....
I run this on my local machine, so the default credentials return my AzureCLI Creds. With those same creds I can run
az appconfig kv list -n MYCONFIGURATION
and retrieve all values.

Please make sure you grant your identity App Configuration Data Reader or App Configuration Data Owner role in the Access Control of your App Configuration instance and wait for ~15 minutes for the permission to propagate.
More details can be found at
https://learn.microsoft.com/en-us/azure/azure-app-configuration/concept-enable-rbac
BTW, the CLI command you used doesn't use AAD auth. Use the --auth-mode parameter to specify how you want to authenticate.
az appconfig kv list -n MYCONFIGURATION --auth-mode login

Related

How to ask DefaultAzureCredential to use my user credential locally

I'm trying to develop a web app on an Azure VM that uses Azure Key Vault. Later this app will also be deployed to Azure. As far as I know, the most straight forward way to make the app work, both locally and deployed, with the key vault, is to use the DefaultAzureCredential class. The code would be like this:
string kvUri = "https://" + keyvaultName + ".vault.azure.net";
SecretClient client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());
KeyVaultSecret secret = await client.GetSecretAsync(secretName);
At runtime, the provider will try different credential types in order.
This sounds exactly what I want:
When developing locally (on the Azure VM, though), I want to use my user credential (user identity added to the key vault's permission) without any configuration, since I have already logged into the Visual Studio using the same user credential.
Once deployed to Azure, I want to use the app registration credential (also added to the key vault's permission).
But when running the app locally, I'm getting a 403 error The user, group or application .... does not have secrets get permission on key vault ...
After looking up the object id in the error message, I realize it's the dev machine Azure VM's credential that the application uses, not my user credential.
Is there a way to change this behavior?
To prevent the Azure VM from getting a token, you can exclude the ManagedIdentityCredential in your Development environment and only enable it in a Non-Development environment.
if (environment.IsDevelopment())
{
var credentials = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
ExcludeManagedIdentityCredential = true,
ExcludeAzureCliCredential = true
});
}
else
{
var credentials = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
ExcludeVisualStudioCodeCredential = true,
ExcludeVisualStudioCredential = true
});
}
Once deployed to Azure, I want to use the app registration credential (also added to the key vault's permission).
An Azure App Service can use a managed identity as well. There is no need for a separate App Registration.
See https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme#key-concepts for more information.
Create and identity if you wish to use (default identity)
appservice -> select you application -> identity->enable it ->should give you a Id
and than add it to key Vault Access policy
alternatively app registration can be used with tenantId,clientId,secret to connect to keyvault

Trying to use Managed Identity with Azure Service Bus

I've tried following this tutorial in order to authenticate my service bus against DefaultAzureCredentials, however, I get a 401.
I'm using the following code in the set-up:
services.AddAzureClients(x =>
{
x.AddServiceBusClientWithNamespace("myns.servicebus.windows.net")
.WithCredential(new Azure.Identity.DefaultAzureCredential());
});
I then call the SB client like this:
var sender = client.CreateSender("myqueue");
var message = new ServiceBusMessage(Encoding.UTF8.GetBytes("test"));
await sender.SendMessageAsync(message);
When I call SendMessageAsync I get a 401 error:
fail: Azure-Messaging-ServiceBus[82]
An exception occurred while creating send link for Identifier: myqueue-578624f3-f732-4a9b-2ab0-9adc01949a5a. Error Message:
'System.UnauthorizedAccessException: Put token failed. status-code:
401, status-description: InvalidIssuer: Token issuer is invalid.
TrackingId:cde3a89c-8108-48d1-8b8f-dacde18e176f,
SystemTracker:NoSystemTracker, Timestamp:2021-05-19T07:18:44.
Before I run this, I call az login. I have access to the the namespace to both send and receive. My guess is that I need to allocate some kind of permission between the service bus and ... something - but since I'm running this as a console app, I'm running with my own credentials. Clearly there's something about managed identity that I don't understand.
EDIT:
Following advice from #juunas, I tried the following:
services.AddHostedService<ConsoleHostedService>();
services.AddAzureClients(x =>
{
//var creds = new Azure.Identity.EnvironmentCredential(); // 1st - EnvironmentCredential authentication unavailable. Environment variables are not fully configured.'
//var creds = new Azure.Identity.ManagedIdentityCredential(); // 2nd - No Managed Identity endpoint found
//var creds = new Azure.Identity.SharedTokenCacheCredential(); // 3rd - 'SharedTokenCacheCredential authentication unavailable. No accounts were found in the cache.'
//var creds = new Azure.Identity.VisualStudioCodeCredential(); // 4th - 'Stored credentials not found. Need to authenticate user in VSCode Azure Account.'
//var creds = new Azure.Identity.AzureCliCredential(); // 5th
var creds = new Azure.Identity.DefaultAzureCredential();
x.AddServiceBusClientWithNamespace("myns.servicebus.windows.net")
.WithCredential(creds);
It says the "token issuer is invalid".
That means it got an access token, but it was issued by the wrong Azure AD tenant.
The Az CLI allows you to specify the Azure AD tenant id with the -t tenant-id-here argument on az login.
DefaultAzureCredential could also be using some other credential (it attempts multiple credentials like VisualStudioCredential before the AzureCliCredential).
You could instead try to use AzureCliCredential directly and see if it works.
That of course won't use Managed Identity so you'd need to use ChainedTokenCredential with the AZ CLI credential + ManagedIdentityCredential to support both.

Get Secret from Azure Keyvault using nodejs

I need to read the list of users in the Azure active directory. The client has created a Graph API application but they do not want to share the client secret of the application, instead they asked us to use the Key vault. How to access from the node.js application the key to retrieve the list of users?
I tried the below one but gave error and I am not sure how to authenticate.
const { DefaultAzureCredential } = require("#azure/identity");
const { SecretClient } = require("#azure/keyvault-secrets");
const credential = new DefaultAzureCredential();
const vaultName = "lsm-keyvault";
const url = `https://${vaultName}.vault.azure.net`;
const client = new SecretClient(url, credential);
const secretName = "Demo";
async function main() {
const result = await client.setSecret(secretName, "MySecretValue", {
enabled: false
});
console.log(result)
}
Well, if you run the code in local, the DefaultAzureCredential will use the environmental variables automatically.
So in your case, you need to register an application with Azure AD, and get the tenant id, client id(i.e. application id), client secret(i.e. application secret), set the environmental variables, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and AZURE_TENANT_ID.
For the 403 error you got, I notice you said It added as a compound entity, based on my experience, you did not add the correct service principal related to the AD App correctly to the Access policies of the keyvault. If you add it correctly, it will appear as APPLICATION, not COMPOUND IDENTITY.
So when you add it, you could search for the client Id(i.e. application Id) or the name of your App Registration directly, make sure you add the correct one. I gave the details in this similar issue, you could refer to it.
To retrieve the secret, the Get permission is enough, the code should be
const retrievedSecret = await client.getSecret(secretName);
I notice you use client.setSecret in your code, it is used to save a secret, to use it, you may need the Set permission.
For more details, see Quickstart: Azure Key Vault client library for Node.js (v4).
Update:
I have to eventually need to deploy this but not in azure but in another environment. How do I set the environment variables and access it.
If so, you need to change your code to authenticate, use the three values directly in the code.
Change the lines
const { DefaultAzureCredential } = require("#azure/identity");
const credential = new DefaultAzureCredential();
To
const { ClientSecretCredential } = require("#azure/identity");
const credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
See - https://www.npmjs.com/package/#azure/identity/v/1.0.3#authenticating-as-a-service-principal
All you need to do is follow the below steps:
Create an App in the Azure Active Directory (Service Principal) from App Registrations.
Go to Key Vault resource, Access Policy blade, assign read access to this Azure AD App (Service Principal) that we created in the above step.
Set these 3 Environment variables AZURE_CLIENT_ID, AZURE_TENANT_ID, and AZURE_CLIENT_SECRET in your App Service. Get the values of these variables from the app that we created in step 1.
Use DefaultAzureCredential that we are already using now. This will automatically pick the credentials from the environment variables that we defined in App Service for the authentication.
Another way is to obtain Key Vault token dynamically and use that token to get the secrets from the Key Vault - https://learn.microsoft.com/en-us/samples/azure-samples/app-service-msi-keyvault-node/app-service-msi-keyvault-node/
Helpful Reference:
https://www.rahulpnath.com/blog/defaultazurecredential_from_azure_sdk/

Accessing the Azure Key Vault from a .Net Application - getting DefaultAzureCredential authentication failed

I'm trying to connect my .Net Core 3.1 app up to an Azure Key Vault. I've followed the quickstart tutorial, and am getting the following error:
Microsoft.Extensions.Configuration.AzureAppConfiguration.KeyVaultReferenceException:
'DefaultAzureCredential authentication failed.. ErrorCode:,
Key:Authentication:Twitter:ConsumerAPIKey
The inner exception is:
MsalServiceException: AADSTS70002: The client does not exist or is not enabled for consumers
The CreateHostBuilder method looks like this:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.ConfigureAppConfiguration((hostingContext, config) =>
{
var settings = config.Build();
config.AddAzureAppConfiguration(options =>
{
options.Connect(settings["ConnectionStrings:AppConfig"])
.ConfigureKeyVault(kv =>
{
kv.SetCredential(new DefaultAzureCredential());
});
});
})
.UseStartup<Startup>();
});
I've found very little reference to this online, except one post relating to using multiple credentials (which I am not).
Can anyone give me a way forward on this: some clue as to what might be causing it?
EDIT
The following seems to work:
var defaultAzureCredentialsOptions = new DefaultAzureCredentialOptions()
{
SharedTokenCacheTenantId = <tenant id>,
SharedTokenCacheUsername = <my azure username>,
ExcludeInteractiveBrowserCredential = false,
ExcludeEnvironmentCredential = false,
InteractiveBrowserTenantId = <tenant id>
};
config.AddAzureAppConfiguration(options =>
{
options.Connect(settings["ConnectionStrings:AppConfig"])
.ConfigureKeyVault(kv =>
{
kv.SetCredential(new DefaultAzureCredential(defaultAzureCredentialsOptions));
});
});
Whilst this does work (as far as it goes), I now have the Tenant ID and my username hard-coded; along with a pop-up when I launch the site asking me to log-in.
The DefaultAzureCredential goes through a number of credentials, such as Managed Identity which is recommended for Azure services as being more secure (no shared access tokens).
You can, however, use environment variables set for your application or even during local development, namely:
AZURE_TENANT_ID : tenant ID
AZURE_CLIENT_ID : the service principal ID, which must have been granted necessary permissions like list and get for how you're using them in your example
AZURE_CLIENT_SECRET : the service principal secret (password), which was shown to you only after it was created
If you use the new preview version of Azure.Identity, it also supports Azure CLI, Visual Studio, and other credentials for development environments. For exampe, if you use the Azure CLI, once you az login, DefaultAzureCredential will just work.

How to access Azure Keyvault from docker container running locally?

I have a docker image containing an ASP.NET Core app that uses Azure Key vault to access things like connection strings. When I run the image locally, I get this error:
Unhandled Exception: Microsoft.Azure.Services.AppAuthentication.AzureServiceTokenProviderException: Parameters: Connection String: [No connection string specified], Resource: https://vault.azure.net, Authority: https://login.windows.net/[guid]. Exception Message: Tried the following 3 methods to get an access token, but none of them worked.
Parameters: Connection String: [No connection string specified], Resource: https://vault.azure.net, Authority: https://login.windows.net/[guid]. Exception Message: Tried to get token using Managed Service Identity. Unable to connect to the Managed Service Identity (MSI) endpoint. Please check that you are running on an Azure resource that has MSI setup.
Parameters: Connection String: [No connection string specified], Resource: https://vault.azure.net, Authority: https://login.windows.net/[guid]. Exception Message: Tried to get token using Visual Studio. Access token could not be acquired. Environment variable LOCALAPPDATA not set.
Parameters: Connection String: [No connection string specified], Resource: https://vault.azure.net, Authority: https://login.windows.net/[guid]. Exception Message: Tried to get token using Azure CLI. Access token could not be acquired. /bin/bash: az: No such file or directory
From what I understand, it first tries to get the access token as a managed service identity. As it's not running in the Azure cloud, it can't do this and tries to get it through visual studio connected service. As this won't be on the docker image, it tries using the Azure CLI, but this isn't installed on the docker image.
So I need to install the Azure CLI into the docker image. How is this done, given that the base image of the Dockerfile is FROM microsoft/dotnet:2.1-aspnetcore-runtime?
Is this base image an Alpine OS image, so do I need to look at installing Azure CLI with Alpine?
Assuming I have Azure CLI installed, is there a way to access Key vault without storing any credentials in Dockerfile source code or passing them to the container through plain text?
More generally, what is the best approach here.
My current solution is to use an environment variable with the access token.
Get the key and store in environment variable (after you did an az login and set the correct subscription):
$Env:ACCESS_TOKEN=(az account get-access-token --resource=https://vault.azure.net | ConvertFrom-Json).accessToken
The we add that environment variable in Visual Studio:
Change the code to:
config.AddEnvironmentVariables();
KeyVaultClient keyVaultClient;
var accessToken = Environment.GetEnvironmentVariable("ACCESS_TOKEN");
if (accessToken != null)
{
keyVaultClient = new KeyVaultClient(
async (string a, string r, string s) => accessToken);
}
else
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
}
config.AddAzureKeyVault(
$"https://{builtConfig["KeyVaultName"]}.vault.azure.net/",
keyVaultClient,
new DefaultKeyVaultSecretManager());
Solution (not for production use)
A possible Solution to your Problem is to generate a Service Principal (SP) and grant this Service Principal access to the key vault (via RBAC or IAM). Microsoft Documentation on creating a SP
Using the credentials of the SP as client-id and client-secret (Random example) you can then log into the vault and retrieve the secrets.
Concerns
with this approach, you will introduce secrets into the code (propably the exact reason why you use the key vault). I suppose the local docker image is for development use only. Therefore I would recommend creating a Keyvault just for development (and access it using SP) while using a separate Kevault for Production where one of the established, secret-less authentication schemes is used.
You must make sure that the key vault allows access from outside the azure cloud (see the access policies on portal.azure.com)
In an attempt to simplify and automate E. Staal's answer, I came up with this:
Update your .gitignore file, by adding the following line to the bottom of it:
appsettings.local.json
Right click on the project in Solution Explorer, and click on Properties; in the Build Events tab, find the Pre-build event command line text box and add the following code:
cd /d "$(ProjectDir)"
if exist "appsettings.local.json" del "appsettings.local.json"
if "$(ConfigurationName)" == "Debug" (
az account get-access-token --resource=https://vault.azure.net > appsettings.local.json
)
In your launchSettings.json (or using the Visual Editor under project settings) configure the following values:
{
"profiles": {
// ...
"Docker": {
"commandName": "Docker",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development",
"AZURE_TENANT_ID": "<YOUR-AZURE-TENANT-ID-HERE>"
}
}
}
}
In your Program.cs file find the CreateHostBuilder method and update the ConfigureAppConfiguration block accordingly -- here is mine as an example:
Host.CreateDefaultBuilder(args).ConfigureAppConfiguration
(
(ctx, cfg) =>
{
if (ctx.HostingEnvironment.IsDevelopment())
{
cfg.AddJsonFile("appsettings.local.json", true);
}
var builtConfig = cfg.Build();
var keyVault = builtConfig["KeyVault"];
if (!string.IsNullOrWhiteSpace(keyVault))
{
var accessToken = builtConfig["accessToken"];
cfg.AddAzureKeyVault
(
$"https://{keyVault}.vault.azure.net/",
new KeyVaultClient
(
string.IsNullOrWhiteSpace(accessToken)
? new KeyVaultClient.AuthenticationCallback
(
new AzureServiceTokenProvider().KeyVaultTokenCallback
)
: (x, y, z) => Task.FromResult(accessToken)
),
new DefaultKeyVaultSecretManager()
);
}
}
)
If this still doesn't work, verify that az login has been performed and that az account get-access-token --resource=https://vault.azure.net works correctly for you.
Although there's some time since you make this question, another option, suitable for production environments, would be using an x509 certificate.
Microsoft has this article explaining how to do this. You can use self-signed certificates or any other valid SSL certificate. That depends on your needs.
This is because your docker container is running as root user and the user registered in key vault is some other user (yourusername#yourcmpany.com)

Resources