I'm working with Azure Key Vault and I'm testing the "Managed Identities" for Azure Resource. Long story short: with this feature we can easily access to a KeyVault secrets from (e.g.) VM, App Service... I wrote this simple code:
private void GetKeyVaultSecrets()
{
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
var akvCallback = new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback);
var keyVaultClient = new KeyVaultClient(akvCallback);
var secret = keyVaultClient.GetSecretAsync("https://mykeyvault.vault.azure.net/secrets/mySecret").GetAwaiter().GetResult();
string superSecret = secret.Value;
}
And I'm able to access my "Secret" without any kind of Authorization. Is it safe? Am I missing something? It looks so strange that with those 7 lines of code you have access to all your secrets. Thanks for your attention.
If you are running this on a service with a system-assigned Managed Identity, here's what actually happens (example for App Service, VM is slightly different):
Your app reads IDENTITY_ENDPOINT and IDENTITY_HEADER environment variables
HTTP call to IDENTITY_ENDPOINT using the IDENTITY_HEADER as authentication
This endpoint cannot be called from the outside, only from within the instance.
Its port is also random
In the call, your app specifies it wants a token for Key Vault (resource https://vault.azure.net)
The Managed Identity endpoint uses the certificate it has created to authenticate to Azure Active Directory with the Client Credentials flow
Azure AD verifies the request and issues a token
Managed Identity endpoint returns the token to your app
KeyVaultClient uses the token to authorize the call to Key Vault
On Virtual Machines, the Instance Metadata Service is used to get tokens.
The main thing to understand is that any process running on the instance itself is capable of getting tokens from the Managed Identity.
Though if you were to get malicious code running on your instance,
other approaches could be in trouble as well :)
If you run that code locally, it can work as well.
AzureServiceTokenProvider also attempts Visual Studio authentication as well as Azure CLI authentication.
So if you are logged in to e.g. AZ CLI, it is able to get an access token for your user to Azure Key Vault and access it.
Reference: https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity?context=azure%2Factive-directory%2Fmanaged-identities-azure-resources%2Fcontext%2Fmsi-context&tabs=dotnet#rest-protocol-examples
Example request done in VMs: https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/tutorial-windows-vm-access-nonaad#access-data
Related
I'm experimenting with various Azure features and currently want to retrieve a secret from KeyVault.
Straight to the case:
I'm using this nuget package to interact with my azure resources.
I've developed a simple .NET Core console app and run it locally.
I have a KeyVault resource with one secret defined which is active and not expired.
I've registered an App in AAD so my locally shipped .NET Core console app has an identity within AAD.
Than I've created a "client secret" within this registered app in AAD to use it to authenticate myself as an app.
After that I've added access policy in my KeyVault resource to allow GET operation for secrets for this registered app:
Then I've developed a small piece of code which should retrieve the desired secret:
public class AzureAuthentication
{
public async Task<string> GetAdminPasswordFromKeyVault()
{
const string clientId = "--my-client-id--";
const string tenantId = "--my-tenant-id--";
const string clientSecret = "--my-client-secret--";
var credentials = new ClientSecretCredential(tenantId, clientId, clientSecret);
var client = new SecretClient(new Uri("https://mykeyvaultresource.vault.azure.net"), credentials);
var secret = await client.GetSecretAsync("admincreds");
return secret.Value.Value;
}
}
However when I'm trying to do this I'm getting an AccessDenied error:
Am I missing something painfully obvious here? Or there is some latency (>30 min for this moment) for which changes from Access policies screen in KeyVault resource are applied?
I test your code and Get permission, it works fine.
From your screenshot, it looks you didn't add the correct service principal related to the AD App to the Access policies.
If you add the service principal related to the AD App, 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.
Make sure your AD App(service principal) has the correct permission in your keyvault -> Access policies
I am trying to access Azure Key Vault from my Azure App Service. I followed the steps outlined on this documentation: https://learn.microsoft.com/en-us/azure/key-vault/managed-identity (turned on system assigned identity for the app service, updated the access policy of the key vault to include the app with Get,List secret permissions).
However, when I run the web application, it is not able to get the secret from the key vault and my web service hits the following error:
502 - Web server received an invalid response while acting as a gateway or proxy server.
There is a problem with the page you are looking for, and it cannot be displayed. When the Web server (while acting as a gateway or proxy) contacted the upstream content server, it received an invalid response from the content server.
This is what my code looks like:
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
KeyVaultClient keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
var secret = keyVaultClient.GetSecretAsync(KeyVaultUrl);
authenticationKey = secret.Result.Value;
The service gets stuck on the secret.Result.Value line. Is there something else I need to do?
This is much easier with the new package, like Azure.Security.KeyVault.Secrets. Together with Azure.Identity, you can just pass a DefaultAzureCredential like in our samples.
var client = new SecretClient(
new Uri("https://myvault.vault.azure.net"),
new DefaultAzureCredential());
KeyVaultSecret secret = await client.GetSecretAsync("secret-name");
string authenticationKey = secret.Value;
The DefaultAzureCredential is optimized to work for managed identity, service principals from the environment, and interactive logins to support the same code running both in production and on developer machines. The new libraries are also faster with fewer allocations, and have much better diagnostics - defaulted to on when using Azure Application Monitor.
They target netstandard2.0 so should be compatible with the older packages these replace. Would you be able to upgrade? We're only making critical fixes for the older packages, and recommending people upgrade to Azure.* packages intead of the older Microosft.Azure.* packages.
As for the problem itself, it's hard to say without knowing when you're calling this in your application. During startup? What version of .NET? What are you using for your ASP.NET application framework?
While it's probably not the cause of the problem, it's hard to ignore that you're calling an async method synchronously, which can also cause problems. If you're in an async method, you should write your code like so:
var secret = await keyVaultClient.GetSecretAsync(KeyVaultUrl);
authenticationKey = secret.Value;
If not, call:
var secret = keyVaultClient.GetSecretAsnc(KeyVaultUrl).GetAwaiter().GetResult();
This is not recommended, though. In the new packages I mentioned above, we have both sync and async versions that are either sync or async all the way through the call stack and safer to use. Generally, though, you should use async calls - especially for network traffic like accessing Key Vault because, depending on what thread you call it, it can hang your UI.
When you test in local:
Add your vs signed account into azure keyvault. Go to keyvault> Access policy> add your account with get secret permmission.
When you publish to azure:
1.Enable webapp MSI.
2.Go to keyvault> Access policy> add your webapp's service principal with get secret permission.
The code:
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
KeyVaultClient keyVaultClient = new KeyVaultClient(new Microsoft.Azure.KeyVault.KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
var secret = keyVaultClient.GetSecretAsync("https://yourkevaultname.vault.azure.net/secrets/secretname/437d301daxxxxxx");
var authenticationKey = secret.Result.Value;
ViewBag.Message = authenticationKey.ToString();
We have a multi-tenant application in our Azure AD tenant. It is authorized in some other tenants (we know which ones). And it has multiple certificates registered to it to be used as client credentials.
We want to remove the certificates from the local stores and use a certificate in the key vault to request a token for one of the external tenant. According to the documentation this is one of the use cases.
Our tenant (id: xxxx):
Has app registration (app id: abcd-xxx-xxxx-xxx)
has keyvault
has managed service principal (with access to the key vault)
other tenant (id: yyyy):
Executed Admin consent for our application.
Question 1:
How do I create a certificate in the Key vault that is connected to an existing application (app id: abcd-xxx-xxxx-xxx)? It is important to note that since the application is already approved by several third party admins, it cannot be recreated. Same counts for creating a new certificate after it would be expired.
Question 2:
How to I setup the Microsoft.Azure.Services.AppAuthentication library to:
Use the managed identity to access the key vault in our tenant (xxxx).
Use the certificate in the key vault to request a token for our app (abcd-xxx-xxxx-xxx) in other companies tenant (yyyy)
Answer 1:
You could use az ad sp credential reset command like below. If you don't want to overwrite the existing certificate of the App, please pass the --append parameter.
az ad sp credential reset --name '<application-id>' --keyvault joykeyvault --cert cer136 --create-cert --append
Answer 2:
1.To use the MSI access the keyvault in your tenant, just use the code below.
No code changes are required, when you run your code on an Azure App Service or an Azure VM with a managed identity enabled, the library automatically uses the managed identity, see this link.
The environment variable AzureServicesAuthConnectionString has to be set to any credential with access to the keyvault. RunAs=Developer; DeveloperTool=AzureCli for dev or RunAs=App; for managed service identity (automatically in azure).
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Azure.KeyVault;
// Instantiate a new KeyVaultClient object, with an access token to Key Vault
var azureServiceTokenProvider1 = new AzureServiceTokenProvider();
var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider1.KeyVaultTokenCallback));
2.If you want to use the service principal along with its certificate stored in the keyvault to get the token for the resources in another tenant, the connection string on the AzureServiceTokenProvider has to be set to RunAs=App;AppId={TestAppId};KeyVaultCertificateSecretIdentifier={KeyVaultCertificateSecretIdentifier} then you can get tokens for other tenants like.
const string appWithCertConnection = "RunAs=App;AppId={TestAppId};KeyVaultCertificateSecretIdentifier=https://myKeyVault.vault.azure.net/secrets/myCert";
Then use the code to get the token, e.g. for the resource https://management.azure.com/.
var azureServiceTokenProvider2 = new AzureServiceTokenProvider(appWithCertConnection);
string accessToken = await azureServiceTokenProvider2.GetAccessTokenAsync("https://management.azure.com/", "tenant-id-of-thridh-party-tenant").ConfigureAwait(false);
I've created an App Service that is running a container running Identity Server. This container needs a certificate that I'm loading from Key Vault. To get the content of the certificate what I've done is:
Upload the certificate into Key Vault
Access the content accessing the secret endpoint of the Key Vault (https://mykeyvault.vault.azure.net/secrets/IdentityCert)
In my first attempt, I was storing just the URI of the secret in the App Settings and try to get the value using the following code:
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
var cert = keyVaultClient
.GetSecretAsync(
Env.GetString("CERTIFICATE_KEY_VAULT_KEY"))
.ConfigureAwait(false).GetAwaiter().GetResult();
identityServerBuilder.AddSigningCredential(new X509Certificate2(Convert.FromBase64String(cert.Value)));
This works if I deploy the code into a VM. But it doesn't if I deploy the code into an App Service running a container. So I decided to try another option which is to use the Key Vault reference thing. So, I've created a new App Settings like this:
CERTIFICATE_CONTENT = #Microsoft.KeyVault(SecretUri=https://mykeyvault.vault.azure.net/secrets/IdentityCert/5221036c6b734d5fa69cba29976a8592)
And then just use this value inside my code:
var certificateContent = Env.GetString("CERTIFICATE_CONTENT");
identityServerBuilder.AddSigningCredential(new X509Certificate2(Convert.FromBase64String(certificateContent)));
But this doesn't work either.
I've enabled the managed identity in the App Service and added it to the Access Policies in the Key Vault.
How can I get the value from Key Vault? Is there anything I'm missing?
So, the error was the way I was adding the new access policy. I was selecting the principal Id and the Authorized application. It turns out that I only need to select the principal, leaving the Authorized application as "None selected".
I have a Function App with Managed service identity (MSI) enabled.
I'm trying to use this Function App to access a secret from my Key Vault.
I have added the code into my Function App to retrieve the secret.
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
var secret = await keyVaultClient.GetSecretAsync("https://test-prototype-vault.vault.azure.net/secrets/batman/guidhere").ConfigureAwait(false);
I receive the following error:
Microsoft.Azure.WebJobs.Script: One or more errors occurred. Microsoft.Azure.KeyVault: Access denied.
I think this is because I need to (as described in the link above).
You may need to configure the target resource to allow access from
your application. For example, if you request a token to Key Vault,
you need to make sure you have added an access policy that includes
your application's identity.
I don't know how to do that. I have gone to my Key Vault and tried to add an Access Policy - I can't find the application to Select Principle option.
Setup of Azure Function.
What happens when I try to add the principal.
This blog has details but you need to go into key vault and give the function app access to secrets in a new access policy
https://medium.com/#jeffhollan/getting-key-vault-secrets-in-azure-functions-37620fd20a0b
The name of your function app should show in list of users