Azure App Service Fails to Find Newly Created Certificate - azure

I have an Azure app service I did not create but now maintain. The app service finds a certificate in a Key Vault by thumbprint and in turn uses that to get a token for doing some SQL work via nightly jobs.
It appears the certificate was set to auto renew after 80% of its valid date (12 months). The day the cert renewed my nightly jobs started to fail. I'm reasonably certain the new certificate is at the root of the problem.
As best I can tell it designed to work like this:
Job fires via Azure Logic App
annomyous POST to a reports processing API (end result should be .PDF report creation for email atachment)
API has Appsetting.json that contains the current certificates thumbprint
Thumbprint is used in the line of code below to find the certificate in the cert store
Cert is used to aquire access token and perform work
When I install both the old certificate and the new certificate on my local machine and run the entire process it works find with the old certificate and fails on this line with the new auto-generated certificate. It also fails with any new certificates I try to make in Azure and export from Azure and import to my dev machine. I've double/triple checked the appsettings to make sure the Thumbprint in question is correct and updated.
var signingCert = store.Certificates.OfType<X509Certificate2>().FirstOrDefault(x => x.Thumbprint == _appSettings.AzureAD.CertificateThumbprint);
When this was configured a year ago the process was to go into App Service, TLS/SSL setting blade, select Private Key Certificates (.pfx) and finally + Import Key Vault Certificate.
From there you selected the Key Vault and Certificate, then changed the Appsettings.Json to have the new Thumbprint.
Why will it work with the old (soon to expire) certificate and corresponding Thumbprint entry into appsettings but fails to work with any newly created certificates and corresponding correct Thumbprint entry?
I've looked at the Configuration for the App Service in question and it has the following setting when I understand is supposed to let the app service see all certificates registered to it, right?
WEBSITE_LOAD_CERTIFICATES = *
EDIT:
After some more testing I find that any cert I export form Azure and import on my computer will successfully iterate the cert store and find any Certificate I provide a valid Thumbprint. What it won't do is use that cert to obtain a access token. The complete code is below.
The certificate that is due to expire soon will get a proper access token and run the rest of the process by getting the proper data from the DB.
The exception I get with all the other certificates suggest something about base64 encoding but I can't quite figure that out. Any ideas?
Exception for all but the original certificate:
Client assertion contains an invalid signature
Successful Access token with this code only with original certificate:
private async Task<string> GetDatabaseTokenFromCert()
{
X509Certificate2 cert;
var store = new X509Store(StoreLocation.CurrentUser);
var authContext = new AuthenticationContext(_appSettings.AzureAD.AADInstance + _appSettings.AzureAD.TenantId);
try
{
store.Open(OpenFlags.ReadOnly);
var signingCert = store.Certificates.OfType<X509Certificate2>().FirstOrDefault(x => x.Thumbprint == _appSettings.AzureAD.CertificateThumbprint);
if (signingCert == null)
{
throw new FileNotFoundException("Cannot locate certificate for DB access!", _appSettings.AzureAD.CertificateThumbprint);
}
cert = signingCert;
}
finally
{
store.Close();
}
var certCred = new ClientAssertionCertificate(_appSettings.AzureAD.ClientId, cert);
var result = await Retry(() => authContext.AcquireTokenAsync(_appSettings.SqlConfig.ResourceId, certCred));
return result?.AccessToken;
}

Turns out you need to also add the new certificate to the app registration. As a .cer file without the private key, obviously.
So if you get the error message:
AADSTS700027: Client assertion contains an invalid signature. [Reason - The key was not found., Thumbprint of key used by client: 'YourNewCert'
Go to Key Vault, export new cert as .cer file and import it into the App Service that is trying to obtain the Access token from AcquireTokenAsync
In my case the order of operation is:
Logic app fires off anonymous call as a POST to web API
Web API uses Thumbprint of Cert in question via appsetting.json
Finds cert with thumbprint that is in App Registration of the web API
AcquireTokenAsync takes Azure info and Cert and returns Access Token
This will work or fail on this line of original post
var result = await Retry(() => authContext.AcquireTokenAsync(_appSettings.SqlConfig.ResourceId, certCred));

Related

What's the best/latest approach to authenticate access to Azure Key Vault using Certificate from an non-azure application

I would like to retrieve Azure Key Vault secrets from a client application(outside of Azure) using certificate authentication for Azure Key Vault through Azure AD. My initial development is done through a dotnet console application, and eventually, I would like to use similar logic within a WCF web service hosted on an IIS server outside Azure (an on-premise server). I have everything set up on cloud side: Azure Key Vault set up, client application registration, and a certificate within Azure Key Vault for client Application authentication. I also install the same certificate in my local machine current user certificate store and the certificate on the windows service server hosting my WCF web service. So that I can retrieve the certificate from the certificate by using its thumbprint value:
var certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, certificateThumbprint, onlyAllowValidCerts);
I tried a couple of approaches to access Azure Key Vault secrets, but nothing has met the expectations I set up initially. I would like to know what is the best/latest approach to achieve what I want without compromising response time and using deprecated APIs
Use AzureKeyVaultConfigurationProvider to retrieve secrets
Issues:
1.1)I have to use a few deprecated packages for this purpose.
1.2)Also, the performance (response time) to retrieve the secrets from Azure Key Vault and populate the AzureKeyVaultConfiguration is not ideal...
Code Example:
// create IConfigurationRoot to read Azure key vault
IConfigurationRoot config = new ConfigurationBuilder()
.AddAzureKeyVault(
keyVaultUrl,
CLIENT_ID,
KeyVaultUtility.AssertionCert2,
new DefaultKeyVaultSecretManager())
.Build();
Use AzureKeyVault package to create a KeyVault Utility. Then use GetSecretAsync method:
Issues: 1)Azure Key Vault package is deprecated
2)When I use this logic with WCF service, and test locally, I run into following error:
Method not found: 'Void Microsoft.Azure.KeyVault.KeyVaultClient..ctor(AuthenticationCallback, System.Net.Http.DelegatingHandler[])'.
Code Example:
var client = KeyVaultUtility.GetClient();
var secret = Task.Run(async () => await client.GetSecretAsync(keyVaultUrl, "Jon--Test")).Result.Value;
KeyVaultUtility logic:
public static KeyVaultClient GetClient()
{
if (AssertionCert == null)
{
throw new Exception("Call Initialise before calling GetClient.");
}
return new KeyVaultClient(new KeyVaultClient.AuthenticationCallback((a, r, s) => GetAccessToken(a, r, s, AssertionCert)));
}
private static async Task<string> GetAccessToken(string authority, string resource, string scope, ClientAssertionCertificate cert)
{
var context = new AuthenticationContext(authority, TokenCache.DefaultShared);
var result = await context.AcquireTokenAsync(resource, cert).ConfigureAwait(false);
return result.AccessToken;
}

Read secret stored in Key Vault from an App Service running a container

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".

Use certificate in Azure Key Vault to sign IdentityServer4

I've uploaded a pfx certificate as a secret to my Azure Portal and I now want to use this to sign the credentials in IdentityServer4.
I've got reference to the vault in the startup of the api using:
builder.AddAzureKeyVault(
$"https://{config["Vault"]}.vault.azure.net/",
config["ClientId"],
config["ClientSecret"]);
But not too sure how to get the certificate out to pass to:
services.AddIdentityServer()
.AddSigningCredential(...)
Is it possibly to be able to reference the certificate directly from the key vault, or do I need to deploy the cert to the web app the api is running on?
Thanks
You are already building the IConfiguration object, so you can reference the pfx key just like you would reference any other object from the configuration.
If the key is in the keyvault you can reference it something like:
// Name of your pfx in the keyvault.
var key = _configuration["pfx"];
var pfxBytes = Convert.FromBase64String(key);
// Create the certificate.
var cert = new X509Certificate2(pfxBytes);
services.AddIdentityServer()
.AddSigningCredential(cert);
I would recommend using the keyvault for this but you could decide to upload the pfx to the certificate store of the web app. You can do this in Azure by going to your web app -> SSL Certificates -> Upload certificate and enter the password. Go to the Application Settings and add the app setting WEBSITE_LOAD_CERTIFICATES : <thumbprint>. The last thing you would do is retrieve the certificate from the store and add it again like AddSigningCredential(cert);.

how to connect to azure (management) rest api via C# in IIS

I am trying to setup a website (local testing atm), to connect to azure rest api to see our settings. I created a cert locally (W7 machine):
makecert -sky exchange -r -n "CN=azureConnectionNew" -pe -a sha1 -len 2048 -ss My "azureConnectionNew.cer"
I can see the cert in the certs MMC snap in. (do not have a right click edit permissions option when I view the cert in here).
I have a class library that setups up the connection, the cert is passed in by getting the cert (via the thumb string), this works great for the console app, but when I try and do this in a web app it all goes wrong. I get 403 errors.
I first thought that this was due to the fact that the website is running as the ApplicationPoolIdentity so doesn't have access to the cert. So I tried passing in the cert (to the same code as the console app), by loading the actual file:
var path = #"C:\temp\azureconnection\azureConnectionNew.cer";
var cert = new X509Certificate2();
cert.Import(path);
I still get 403 errors.
I tried exporting the cer file from MMC certificates snap in as a pfx file, (with private keys included). I set the local IIS set to use this cert and navigated to the https version of my local site but still got 403.
I am not sure how to include / setup / reference the cert so that IIS can send a HttpWebRequest from the server side to Azure and get a valid response.
It is always better to use Thumbprint of the certificate to get the certificate. Please make sure you have created the certificate correctly. Also please check you have placed the certificate in Personal certificate section in Local Machine. You can check this using MMC snap in. please try below code..
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var certificate = store.Certificates
.Cast<X509Certificate2>()
.SingleOrDefault(c => string.Equals(c.Thumbprint, “CertificateThumbprint”, StringComparison.OrdinalIgnoreCase)); // please replace CertificateThumbprint with original Thumbprint
This isn't the right way to use the certificate - it needs to be stored in the personal/certificates store of the user running the code (you should update the App Pool identity to be a user who can login and into whose certificates you import the cert. Here's sample code showing you how to use the service API: http://code.msdn.microsoft.com/windowsazure/CSAzureManagementAPI-609fc31a/

Unable to load certificate instance from within Azure Worker Role

I have an Azure Worker Role that I wish to call the Management Service (e.g. REST API) and collect information regarding related services. However, when I try to load my certificate it fails to find it. Here are the steps I followed:
1. I created a certificate using MakeCert and registered it as my Management Certificate via the portal
makecert -r -pe -a sha1 -n "CN=MyCnName" -ss My -len 2048
-sp "Microsoft Enhanced RSA and AES Cryptographic Provider" -sy 24 MyCert.cer
2. Installed the cert on my local machine and everything works fine. When running the Worker Role locally I can call the Management Service with no problems.
3. Exported the cert from my machine and registered the exported certificate under the target Hosted Service via the portal
4. Deployed the Role. When the Role starts it fails to find the cert.
Here is an extract of the code I'm using to find the cert.
// Open the certificate store for the current user.
var certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); // I also tried localmachine
certStore.Open(OpenFlags.ReadOnly);
// Find the certificate with the specified subject.
X509Certificate2Collection certCollection = certStore.Certificates.Find(
X509FindType.FindBySubjectName,
_myConfiguration.SubjectName,
false);
if (certCollection == null || certCollection.Count < 1)
{
// Find the certificate with the specified thumbprint.
certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
_myConfiguration.ThumbPrint,
false);
}
// Close the certificate store.
certStore.Close();
// Check to see if a matching certificate was found.
if (certCollection.Count == 0)
{
_logger.Warn("No certificate found");
}
There is no exception, just no cert is found. Can anyone shed some light I what I need to do?
Figured out the problem... In addition to configuring the cert in the portal, I needed to add the certificate details (e.g. Name, Store, and Thumbprint) to the Azure Project Role settings under the Certificates Tab.
I have a similar problem for a web role, i have applied a workaround.
Connect with remote desktop to the VM where the service and
certificate are deployed
List item
Copy your cert or pfx on your
VM local disk (e.g C:)
Click on your pfx or .cert file and
install it on the specific certificate store "Trusted People")
Run your service, even if you are configured for search on a different
store you will find on trusted people
I don't know why my web role try to find the cert on this location if I am forcing to search on "My Store" location but the search method retrieve info from trusted people store.
The problem with this workaround is when you delete your deployment the cert and any other configuration will be wiped.
This piece of code could give you some information:
//the certificate must be in the Trusted People Store
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
try
{
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
//Commented
//Get the first available match from cert store
//X509Certificate2 cert = store.Certificates.Find(X509FindType.FindBySubjectName,
// subjectname,
// false)
// .Cast<X509Certificate2>()
// .FirstOrDefault();
X509Certificate2 cert = new X509Certificate2();
foreach (var ct in store.Certificates)
{
//Logger.TraceInformation(string.Format("Cert found: Subject {0} Tumbprt:{1}", ct.FriendlyName, ct.Thumbprint));
if (ct.SubjectName.Name.ToString().Contains("*.certnamexx.extensionxx"))
{
return new X509SecurityToken(ct);
}
}
}

Resources