Do functions support authorizing access to a Function by using client certificates, in a consumption plan? Something similar to the approach described here? Basically, I'm looking for the Functions runtime to immediately reject connection requests if the caller does not present a valid client certificate, without me having to implement that authorization routine in the code.
Here's the code I came up with, note: this is for Azure Functions v1, when req is an HttpRequestMessage
Caller:
X509Certificate2 clientCert = req.GetClientCertificate();
if (!IsValidClientCertificate(clientCert))
{
return req.CreateErrorResponse(HttpStatusCode.Unauthorized, "A valid client certificate is not found");
}
For Azure Functions v2, you can get the client certificate from the HttpRequest using req.HttpContext.Connection.ClientCertificate
Basic validation function:
static bool IsValidClientCertificate(X509Certificate2 clientCert)
{
// check the cert's thumbprint against expected thumbprint
if (clientCert.Thumbprint != "<expected thumprint>"
{
return false;
}
// check that we're within the cert's validity period
if (DateTime.Now > clientCert.NotAfter || DateTime.Now < clientCert.NotBefore)
{
return false;
}
// optionally check cert chaining validity
// if(!clientCert.Verify()) { return false; }
}
Based on your requirement, I created my C# HttpTrigger function to check this issue, here is the core code:
if(req.Headers.Contains("X-ARR-ClientCert"))
{
byte[] clientCertBytes = Convert.FromBase64String(req.Headers.GetValues("X-ARR-ClientCert").FirstOrDefault());
var clientCert = new X509Certificate2(clientCertBytes);
return req.CreateResponse(HttpStatusCode.OK,"Thumbprint: "+clientCert.Thumbprint);
}
return req.CreateResponse(HttpStatusCode.OK, "Hello world");
For App Service Plan, the function could work as follows:
Per my test, the function could also work as expected under the consumption plan.
You could follow How To Configure TLS Mutual Authentication for Web App or just log into Azure Portal and go to your function app, click "NETWORKIING > SSL" under Platform fetures tab, then enable Incoming client certificate option.
Yes it does. If I understand you correctly, you want to reject with a 403, any https requests without a client cert
This is how to enable it with Azure CLI
az webapp update --set clientCertEnabled=true --name <app_name> --resource-group <group_name>
Microsoft docs here
You can also do this from the Azure Portal, under Azure Function App => Configuration => General Settings
Related
We are experiencing an issue calling an On-premise API endpoint from our Azure Function and I'm at a loss to the reason.
We are getting the following error: "x509 certificate signed by unknown authority" back as a 500 status code, which is strange considering we are using the appropriate code within the HttpClientHandler code.
Specifically, we have code that calls an OAuth endpoint which we get a response for, so definitely goes through our firewall and returns a 200 OK response. This endpoint has a DigiCert certificate.
In the same function, this then calls the functional endpoint to return some user data. This comes back with a 500 Internal Server error. This endpoint has a PKI certificate.
There is some level of NSG/Azure Firewall and On Premise setup involved but wondering whether anyone has experienced this before as I'm stumped on next steps.
Sample code looks this as follows
using (HttpClientHandler httpClientHandler = new HttpClientHandler() {
CheckCertificateRevocationList = false,
ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
}) {
using (HttpClient client = new HttpClient(httpClientHandler))
{
return await GetEligibilityToken(client);
}
}
// OAuth Endpoint - This works as expected
var token = Task.Run(() => TokenRetriever.GetEligibilityToken()).GetAwaiter().GetResult();
// Call the User Endpoint - This does not work, configured by
// ConfigurePrimaryHttpMessageHandler with same
// ServerCertificateCustomValidationCallback options.
string apiEndpoint = Constants.SSOApiEndpointDomain;
string path = $#"{apiEndpoint}/v1.0/users/813229bc-3a32-4457-85d5-af7b70db85e0";
// Add the token
_httpClient.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("Bearer", token.AccessToken);
var response = await _httpClient.GetAsync(path).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
string data = response.Content.ReadAsStringAsync().Result; code here
Note - I'm aware of the suboptimal use of using HttpClient, I've tried both that and ConfigurePrimaryHttpMessageHandler options.
Any help or advice you could give would be greatly appreciated. I'm even starting to wonder whether we can call services with PKI certs via Azure Functions.
J
Edit - I should say the code works fine "locally" (albeit I appreciate it needs cleaning up) and we get the desired behaviour. I do have the appropriate CA authority certs on my own VM.
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;
}
I've created and published (to Azure) a working and fairly simple Azure Function App (HTTP Trigger) which inserts data retrieved from an API call to an Azure SQL database after authenticating via User identity (if testing locally) or Managed Identity (when running on VM).
Everything is functional, however, I now need to encrypt one of the SQL table columns which I have done using SSMS. The next step as I understand it is authenticating a provider to access the CMK via Azure Key Vault (I'm following this Microsoft guide).
I'm wondering in the following code how to InitializeAzureKeyVaultProvider without using applicationId and clientKey from Azure App registration resource, but with a user or managed identity role. Or, if there's any other way to get/use applicationId, and clientKey without creating/using an Azure App registration resource.
Is there a newer / easier way to access Azure Key Vault for Always Encrypted Columns sql queries?
static void InitializeAzureKeyVaultProvider() {
_clientCredential = new ClientCredential(applicationId, clientKey);
SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(GetToken);
Dictionary<string, SqlColumnEncryptionKeyStoreProvider> providers = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>();
providers.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(providers);
}
}
Here is the other way I've been attempting, however, upon installing and using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider, I get the error following this code sample:
private static bool EncryptionProviderInitialized = false;
private static void InitializeAzureKeyVaultProvider()
{
if (!EncryptionProviderInitialized)
{
SqlColumnEncryptionAzureKeyVaultProvider akvProvider = null;
#if DEBUG
if (Debugger.IsAttached)
{
Console.WriteLine("Debugger attached - configuring KeyVaultProvider via VisualStudioCredential");
akvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new VisualStudioCredential());
}
if (akvProvider == null)
#endif
akvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new ManagedIdentityCredential());
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders: new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>(capacity: 1, comparer: StringComparer.OrdinalIgnoreCase)
{
{ SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, akvProvider}
});
EncryptionProviderInitialized = true;
}
}
ERROR:
[2021-10-08T01:36:24.209Z] Executed 'EncryptedSQLMigration' (Failed, Id=1323dcbb-e671-4ed4-8a7c-6259447326c5, Duration=537ms)
[2021-10-08T01:36:24.209Z] System.Private.CoreLib: Exception while executing function: EncryptedSQLMigration. FreshFirstApproach: Method not found: 'Microsoft.Extensions.Primitives.StringValues Microsoft.AspNetCore.Http.IQueryCollection.get_Item(System.String)'.
Initially, I got that same error, however, with Microsoft.Extensions.Logging.Abstractions - upon removing the ILogger from my main function just for the sake of moving on as I wasn't able to solve that issue either, I now get this Microsoft.Extensions exception.
Any help with my goal of using Always Encrypted Columns with Azure Function App and Azure Key Vault is very much appreciated!
Thank you very much.
It is not possible to use applicationId and clientKey without creating or using an Azure App registration resource. There is an alternative way where you can pass clientId and clientSecret as shown below, but here also you will need application registration.
static void InitializeAzureKeyVaultProvider()
{
string clientId = ConfigurationManager.AppSettings["AuthClientId"];
string clientSecret = ConfigurationManager.AppSettings["AuthClientSecret"];
_clientCredential = new ClientCredential(clientId, clientSecret);
....
....
}
As for user or managed identity, if you check this document you won't find the Azure Key Vault in the list of services that support managed resources.
So managed service identity should be created for Azure Function instead of Azure Key Vault. Check this Retrieve Azure Key Vault Secrets using Azure Functions and Managed Service Identity document for more information.
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));
Need client certificate based or AAD token based authentication enabled web api hosted in azure app service.
I am migrating one web API from classic cloud service to azure app service.
The API supports calls with valid certificates or valid AAD token.
Code is given below:
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
ClaimsPrincipal principal;
var cert = request.GetClientCertificate();
if (cert != null)
{
//authenticate client certificate
//Set principal from client certificate
}
else
{
//get AAD token
//authenticate & set principal
}
return await base.SendAsync(request, cancellationToken);
}
The issue is in App service for certificate based calls request.GetClientCertificate() is returning null instead of X509Certificate2 object. So not able to authenticate certificate based calls.
I have tried below link as well but in that case calling without certificate is not possible as its making required SSL certificate on for whole website.
https://learn.microsoft.com/en-us/azure/app-service-web/app-service-web-configure-tls-mutual-auth
There are lot of details missing in your explanation. The shared code snippet is useless.
In Azure App Service, there is a which sits in front of the VM where the application is hosted. When you enable TLS Mutual Auth for your web app, it is enabled for the entire app. Currently there is no option to do it for specific pages or sub-folders.
When the clients accesses the site, the Front-End prompts them for the client certificate. Assuming the client provides the certificate to the Front-End, it then passes this certificate to the back end VM in the form of a host header "X-ARR-ClientCert".
I dont see this being used anywhere in the above code snippet. This is also explained in the article (Azure App Service TLS Mutual Auth) which you have linked in your question:
protected void Page_Load(object sender, EventArgs e)
{
NameValueCollection headers = base.Request.Headers;
certHeader = headers["X-ARR-ClientCert"];
if (!String.IsNullOrEmpty(certHeader))
{
try
{
byte[] clientCertBytes = Convert.FromBase64String(certHeader);
certificate = new X509Certificate2(clientCertBytes);
You need to read the contents X-ARR-ClientCert header and then convert it to a X509Certificate2 object and then run your checks against this.