No XML encryptor configured - When using Key Vault - linux

I have an netcoreapp2.2 containerized application that uses azure key vault to store keys and also uses:
app.UseAuthentication();
And
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
I am building/running a docker image in a hosted linux environment under App Services. I am using the azure container registry and dev ops pipe line to maintain my app. Azure controls the deployment process and the "docker run" command.
My app works great, however in the container logs I see:
2019-12-13T17:18:12.207394900Z [40m[1m[33mwarn[39m[22m[49m: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
2019-12-13T17:18:12.207436700Z No XML encryptor configured. Key {...} may be persisted to storage in unencrypted form.
...
2019-12-13T17:18:14.540484659Z Application started. Press Ctrl+C to shut down.
I realize there are many other posts on this that allude to using other storage mechanisms, however I am using key vault to store my sensitive data. JWT is all handled by key vault. I have a few application settings that control static variables for DEV/QA/PROD but they are not sensitive data at all.
I am also not sure what key is being stored in memory as all my sensitive keys are completely outside of the application and are called by:
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
config.AddAzureKeyVault(
$"https://{builtConfig["MY_KEY_VAULT_ID"]}.vault.azure.net/",
keyVaultClient,
new DefaultKeyVaultSecretManager());
I am having a difficult time understanding why this warning is being thrown and also if I should take additional steps to mitigate the issue. I have not personally seen side effects, and app restarts do not seem to have any effect as I am using bearer tokens and other concerns such as token expiration, password resets and the like are not applicable.
So I am left with asking are there any additional steps I can take to avoid this warning? Do I need to ensure that there is a better data at rest mechanism for any configuration settings that may be in my linux environment? Can I safely ignore this warning?

It took me a while to find a way that suited the needs that I have for my application but I wanted to lend some clarity to a number of other stack answers that just did not make sense to me and how I finally understood the problem.
TLDR; Since I was already using key vault, I was confusing how .net core works. I didn't realize that config.AddAzureKeyVault() has nothing to do with how .net core decides to store data at rest on your app service.
When you see this warning:
No XML encryptor configured. Key {GUID} may be persisted to storage in unencrypted form.
it really doesn't matter what GUID was being set: that string of data was not being stored encrypted at rest.
For my risk analysis any information that is not being encrypted at rest is a bad idea as it could mean at anytime in the future some sort of sensitive data could leak and then be exposed to an attacker. In the end, I chose to classify my data at rest as sensitive and err on the side of caution with a potential attack surface.
I have been struggling to try and explain this in a clear and concise way and it is difficult to sum up in a few words. This is what I learned.
Access control (IAM) is your friend in this situation as you can declare a system assigned identity for your application and use role based accessed control. In my case I used my application identity to control access to both key vault and azure storage with RBAC. This makes it much easier to get access without SAS tokens or access keys.
Azure storage will be the final destination for the file you are creating, but it will be the vault that controls the encryption key. I created an RSA key in key vault, and that key is what encrypts the XML file that is throwing the original error.
One of the mistakes I was making in my head was that I wanted two write the encrypted XML to key vault. However, that is not really the use case Microsoft describes. There are two Mechanisms: PersistKeysTo and ProtectKeysWith. As soon as I got that through my thick head, it all made sense.
I used the following to remove the warning and create encrypted data at rest:
services.AddDataProtection()
// Create a CloudBlockBlob with AzureServiceTokenProvider
.PersistKeysToAzureBlobStorage(...)
// Create a KeyVaultClient with AzureServiceTokenProvider
// And point to the RSA key by id
.ProtectKeysWithAzureKeyVault(...);
I had already used RBAC for my application with key vault (with wrap/unwrap permissions), but I also added Storage Blob Data Contributor to the storage account.
How you create your blob is up to you, but one gotcha is creating the access token synchronously:
// GetStorageAccessToken()
var token = new AzureServiceTokenProvider();
return token.GetAccessTokenAsync("https://storage.azure.com/")
.GetAwaiter()
.GetResult();
Then I called it from a method:
var uri = new Uri($"https://{storageAccount}.blob.core.windows.net/{containerName}/{blobName}");
//Credentials.
var tokenCredential = new TokenCredential(GetStorageAccessToken());
var storageCredentials = new StorageCredentials(tokenCredential);
return new CloudBlockBlob(uri, storageCredentials);
After this hurdle was overcame, putting the encryption in was straight forward. The Keyvault ID is the location of the encryption key you are using.
https://mykeyvaultname.vault.azure.net/keys/my-key-name/{VersionGuid}
And creating the client is
var token = new AzureServiceTokenProvider();
var client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(token.KeyVaultTokenCallback));
services.AddDataProtection()
.ProtectKeysWithAzureKeyVault(client, keyVaultId);
I also have to give credit to this blog: https://joonasw.net/view/using-azure-key-vault-and-azure-storage-for-asp-net-core-data-protection-keys as this pointed me in the right direction.
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/default-settings?view=aspnetcore-2.2 this also pointed out why keys are not encrypted
https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles - RBAC for apps
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-3.1 this was confusing at first but has a good warning about how to grant access and limit access in production.

Might be you have to configure your data protection policy to use CryptographicAlogrithms as follow:
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
});
Also, following are few warning which you get around Data protection policy
ASP.Net core DataProtection stores keys in the HOME directory (/root/.aspnet/DataProtection-Keys) so when container restart keys are lost and this might crash the service.
This can be resolve by persisting key at
Persist key at the persistent location (volume) and mount that volume
to docker container
Persist key at the external key store like Azure or Redis
More details about ASP.NET DataProtection:
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-3.1
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/introduction?view=aspnetcore-3.1
To mount an external volume (C:/temp-kyes) to docker container volume (/root/.aspnet/DataProtection-Keys) using following command
docker run -d -v /c/temp-keys:/root/.aspnet/DataProtection-Keys container-name
Also, You need to update your Starup.cs - ConfigureServices to configure DataProtection policy
services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(#"C:\temp-keys\"))
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
});

Related

Azure Key Vault: The user, group, or app does not have secrets set permission on key vault

I am creating a script using Azure CLI that will automatically generate an App Registration (service principal), and then use that App Registration to create a secret that will be stored in Azure Key Vault.
However, I am getting the following error:
The user, group or application 'appid=04b07795-8ddb-461a-bbee-02f9e1bf7b46;oid=0ec2b0e8-daeb-46a8-b627-0d4f61f87157;numgroups=134;iss=https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/' does not have secrets set permission on key vault 'asakeyabcfelaqpgsfnxcy;location=eastus'. For help resolving this issue, please see https://go.microsoft.com/fwlink/?linkid=2125287
Can anyone provide guidance on what this ID is and how to resolve this error? This is not my App Registration Object ID or App ID.
I think there're 2 points you're now concerning, one is you failed to add secret, another is the app id in the error message is not the one you registered.
I think you've followed the document to execute the cli command, so I just want to explain the second point. Pls allow me show you a code sample for a better explanation.
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
public async Task<IActionResult> PrivacyAsync()
{
var kvUri = "https://your_vault_name.vault.azure.net/";
var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());
_ = await client.SetSecretAsync("test0311", "hello");
return View();
}
When we want to add key vault secret to azure, we need to provide a credential so that our operations are authenticated. This is the DefaultAzureCredential() here, and it has several sources to get the authentication like screenshot below.
That means if someone sets the environment variables for authentication, then it will cover the information you entered when executing cli command, this may usually cause the issue that the app is different from what you set. I think you may follow this document to check all your configurations and try again, or you can directly add environment variables with the app you registered on your computer.
By the way, pls don't forget to add access policy in azure portal for the azure ad app you registered.

How to use Azure Key Vault in npmrc file?

I have a secret personal access token (only for building purposes) in my .npmrc file. As this secret is exposed, I thought of replacing this using Azure Key Vault. I haven't found any documentation around it. When I created the personal token before, I had given it only packaging/building access. How can I achieve this, please help me with this? Or is there any better way to include the personal access token in the .npmrc file?
Since you confirmed you are using Azure DevOps for your build, you don't need to maintain PAT in the .npmrc file. Just keep your npm registry URL there (I assume the private npm registry is also in the Azure DevOps) like below:
registry={your npm registry URL}
always-auth=false
Now, in the build pipeline, add npm Authenticate task before npm install.
- task: npmAuthenticate#0
inputs:
workingFile: <relative path to your .npmrc file>
Providing secrets to your resource can be done in many ways.
Some resources in Azure allow you to specify environment variables through the Azure CLI. Here's an example with the Azure container instances: link.
On Azure, once you have a Key Vault instance, you can use your Key Vault to provide secrets to your App Service and Azure Function instances. This is documented here: link, with a focus for Azure Resource Manager templates, which is specially useful for automated deployments.
Although the following is explained in the documentation link above, the general picture on how to use Key Vault secrets from other Azure resources requires the following:
Make a user assigned identity or Azure Active Directory application.
Grant access to this identity (or AAD app) by going to the Access Policies of your Key Vault (this can be done through the portal, of course), and giving your identity at least read access to your Key Vault.
After that, create a secret on your Key Vault, go to the secret details and copy the "Secret Identifier". This will be a URI similar to: https://myvault.vault.azure.net/secrets/mysecret/.
That's the URI you can use to bring Key Vault secrets to other resources.
You'll be able to access this secret from other resources by ensuring the resource has access to the same identity, and by providing the URI through a syntax similar to: #Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/mysecret/).
For example, if you link an Azure Function to the same identity you granted read access to your Key Vault, you can provide a secret through environment variables by setting configuration properties in your resource. By going to the Azure Portal, locating your resource, then going to Configuration, then to Application settings, if you proceed to add the name of your environment variable, and as the value something similar to: #Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/mysecret/), you'll be providing the expected environment variable with the expected secret value to your resource.
The final approach I can think of is by using the #azure/keyvault-secrets client. If using an NPM library to retrieve Key Vault secrets sounds interesting, this is the dependency for you. All the information needed to work with this library should be available on NPM: same link. But in any case, a sample using this client would look as follows:
const { DefaultAzureCredential } = require("#azure/identity");
const { SecretClient } = require("#azure/keyvault-secrets");
const credential = new DefaultAzureCredential();
const client = new SecretClient(`https://my-key-vault.vault.azure.net`, credential);
async function main() {
const secretName = "MySecretName";
const latestSecret = await client.getSecret(secretName);
console.log(`Latest version of the secret ${secretName}: `, latestSecret);
}
main();
You could use this library to load your secrets at any point while your service or program is running.
Please let me know if this information is useful for you. I'm here to help!

Combine Azure Key Vault with Azure App Configuration

I am pretty new to Azure and I am trying to simulate a real production environment.
So, I've deployed a .netcore 3.1 app to an AppService resource. This web app is able to get the configuration from two other services - KeyVault and App Configuration. I already have configuraed the AppService identity.
I can get the keys from KeyVault using the following code:
var settings = config.Build();
var web = settings["KeyVault:Name"];
var clientId = settings["KeyVault:ClientId"];
var clientSecret = settings["KeyVault:ClientSecret"];
config.AddAzureKeyVault($"https://{web}.vault.azure.net/",
clientId,
clientSecret);
As I can get the keys from the App Configuration:
var settings = config.Build();
config.AddAzureAppConfiguration(settings["AppSettings:Endpoint"]);
And I am able to use them both at the same time through identities and AppConfiguration's key reference
var settings = config.Build();
config.AddAzureAppConfiguration(options =>
{
options
.Connect(settings["AppSettings:Endpoint"])
.ConfigureKeyVault(kv =>
{
kv.SetCredential(new DefaultAzureCredential());
});
});
So, my question is about the code snippet #3: is it safe to store the AppSettings:Endpoint value the appconfig.json in a production environment? To be clear, I am referring to this value:
Moreover, I found here and explanation on how to use the two services together. But first of all, that solution doesn't work for me - I get an Azure.Identity.CredentialUnavailableException: 'ManagedIdentityCredential authentication unavailable, no managed identity endpoint found.'.
On the second hand, he's not clear on where to store the AppSettings:AppConfiguration:Endpoint and AppSettings:Identity:ClientId values, it doesn't answer to my first question then.
Thanks in advance
Short answer - the endpoint https://[your_app_name].azconfig.io is safe to leave anywhere. It does nothing without the other parts that make up the connection string.
Long answer - you can and probably should store the parts that are sensitive, connection strings and their parts, in Key Vault as secrets. If your code needs those values you have options on how to get the values to it. Consider using the web config (it's equivalent as a place that would ordinarily have the secret values but put Key Vault references there instead. Your way works also. Keep in mind that your way may require a code change if your config shifts away from what you've coded.
Be sure that your access policy on KV is using the service principal of your app. I repeat, be sure that your access policy on KV is using the service principal of your app. It will likely only need permissions "list" and "get" for secrets and NOTHING ELSE.
Do not store secrets in your web config or its equivalent. Key Vault is almost free, it's so cheap. I don't deploy apps without them getting their own vault as part of the solution.
Leave comments if this is unclear or needs web references.
You should be able to use Azure Marange Identity to connect to APP Configuration. Use Azure.Identity preview version.
c# Example
https://github.com/nishanperera/Azure-App-Configuration-With-Key-Vault

Azure Identity: Trying to get GetUserDelegationKey to work with an application Service Principal

It's not a good sign when the method I'm asking about, GetUserDelegationKey, yields zero search results on SO. Good luck, me.
I have a C# console app, .Net framework 4.8, using Azure.Storage.Blobs and Azure.Identity that will run on customer servers and access Azure blob storage to hold some stuff. I'm doing all of this with the library, not rolling my own REST. Built with VS2019, testing on Win10.
The plan is to use a single Azure storage account that I own, and create one Container per customer project with per-customer credentials that permit them only their own container. Projects never ever talk to each other.
I could set up credentials in the Azure portal by hand, but I am stubbornly trying to do this in software, where a simple project-management app connects as the project app's service principal (which I defined in Azure AD), creates the container, then creates the shared access signatures with a limited lifetime.
The storage account name / container name / access signature would then be configured on the customer server.
I'm having a terrible time.
Note: this is using the newer BlobClient mechanisms, not the older CloudBlob stuff. Dunno if that matters.
This is all documented here at Microsoft, and following even the simple example gets me the same failure.
using System;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Identity;
namespace Azure.Test
{
class Program
{
static void Main(string[] args)
{
var serviceClient = new BlobServiceClient(
new Uri("https://stevestorageacct.blob.core.windows.net"),
new DefaultAzureCredential(true)); // true=pop up login dlg
/*BOOM*/ UserDelegationKey key = serviceClient.GetUserDelegationKey(
DateTimeOffset.UtcNow,
DateTimeOffset.UtcNow.AddDays(30));
// use the key to create the signatures
}
}
}
Even though this program couldn't be simpler, it fails every time with an XML error calling GetUserDelegationKey
Unhandled Exception: Azure.RequestFailedException: The value for one of the XML nodes is not in the correct format.
RequestId:c9b7d324-401e-0127-4a4c-1fe6ce000000
Time:2020-05-01T00:06:21.3544489Z
Status: 400 (The value for one of the XML nodes is not in the correct format.)
ErrorCode: InvalidXmlNodeValue
The XML being sent is supposed to be super simple, I think just the start/end dates for validity, but I have no idea how to get to it to inspect, and http is forbidden for this kind of call, so no Wireshark.
It also fails the same way when I use my application's service principal:
static void Main(string[] args)
{
var tokenCredential = new ClientSecretCredential(
"xxxx-xxxx-xxxx-xxxxx", // tenant ID
"yyyy-yyyy-yyyy-yyyyy, // application ID
"**************"); // client secret
var serviceClient = new BlobServiceClient(
new Uri("https://stevestorageacct.blob.core.windows.net"),
tokenCredential);
UserDelegationKey key = serviceClient.GetUserDelegationKey(
DateTimeOffset.UtcNow,
DateTimeOffset.UtcNow.AddDays(30));
// ALSO: boom
I'm really at a loss.
I suppose I could try rolling my own REST and playing with it that way, but it doesn't feel like this should be necessary: this kind of error feels like a bug even if I'm doing something wrong. XML nodes?
Also open to entirely different ways of approaching this problem if they are superior, but would like to at least find out why this is failing.
I've had some issues with this also. The first things to try is removing the start time (pass null) or setting it ~15 minutes in the past. This is to avoid clock skew between the requesting pc and azure servers.
The second thing to verify is that the user that you are using has the "Storage Blob Data Contributor" role on the storage account. I had to grant it at the storage account level in the end otherwise it just refused to work for me. However in your use case it might be that you need to grant it at the container level to allow you to have one container per client.
Hope this helps.

Unable to connect to Azure Key Vault from Azure Web App

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();

Resources