.NET Health Checks: Azure Key Vault

There isn’t much documentation on how to use the Azure Key Vault Health Checks so I thought I would demonstrate and teach what I’ve learned using C#. I typically use Azure App Configuration and reference Key Vault entries with microservices. It’s not very intuitive but the way this works is your Key Vault Health Check is capable of checking to see if Secrets, Certificates, and Keys exist. I find this very useful, especially if the environment you are using is in a CI/CD pipeline that creates and destroys itself rather frequently. It’s also a very practical thing to check at startup. This is a one-off article but I will write a much larger article covering everything I’ve learned about Health Checks with .NET Microservices.

GitHub

I will include a GitHub sample project with the larger Health Checks project.

Configuration

I recommend creating a configuration class of some sort to keep track of which Key Vault references need to be monitored and the URI.

AppHealthChecksConfiguration.cs

The two important values here are KeyVaultUri and the list of strings AzureKeyVaultSecrets.

Samples values may look like:

KeyVaultUri = “https://YOUR_KEY_VAULT_NAME.vault.azure.net/”

AzureKeyVaultSecrets = new () { “secret-test” }

public class AppHealthChecksConfiguration
{
    public const string Position = "AppHealthChecksConfiguration";
        
    public string KeyVaultUri { get; set; }
        
    public List<string> AzureKeyVaultSecrets { get; set; }
}

Health Check Extension

Static Tags Class

I’m also creating a nested static Tags class that will provide string references to the tags associated with the health checks. If you’re not aware, tags can associate each health check with a certain type of health check. In Kubernetes, there are health checks for Startup, Readiness, and Liveness.

public static class CustomAzureKeyVaultExtension {

    public static class Tags
    {
        public const string Startup = "startup";

        public const string Readiness = "ready";

        public const string Liveness = "live";
    }
CustomAzureKeyVaultExtension.cs

Using a list of strings as a configuration property, we can iterate through the secrets we want to check. This is also beneficial for adding and removing key vault checks without redeploying code. This is read from Azure App Config or the AppSettings.json file.

Important Note: (DefaultAzureCredential) Typically, I do this in a microservice running in Docker so the appropriate values for the AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET are set as environment variables so Azure Identity can authorize. This is how Azure Key Vault would authorize in a containerized microservice. The article below is for Express.js but it covers the general idea of how it works.

https://docs.microsoft.com/en-us/azure/developer/javascript/how-to/with-web-app/use-secret-environment-variables#configure-expressjs-required-environment-variables-to-use-azure-identity

services
    .AddHealthChecks()
    .AddAzureKeyVault(new Uri(healthCheckConfig.KeyVaultUri), new DefaultAzureCredential(),
        options =>
        {
            // secrets
            foreach (string secret in healthCheckConfig?.AzureKeyVaultSecrets)
            {
                options.AddSecret(secret);
            }
        }, "azurekeyvault", HealthStatus.Unhealthy, new [] { Tags.Startup });

Map Endpoints

This code will create two endpoints that listen on /hc/startup and /hc/live to monitor health checks. This can easily be integrated with Kubernetes.

UIResponseWriter.WriteHealthCheckUIResponse

This line isn’t necessary but it sets the ResponseWriter to an implementation that will output a detailed response that can be ready by the Health Check UI. This is also compatible with Kubernetes Health Checks. This will also include an error if the health check fails. It will likely be a large piece of text containing HTML explaining that either the URI was invalid or the health check failed.

Startup.cs
.UseEndpoints(endpoints =>
{
    endpoints.MapHealthChecks("/hc/startup", new HealthCheckOptions()
    {
        Predicate = (check) => check.Tags.Contains(HealthCheckExtension.Tags.Startup),
        ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
    });
    endpoints.MapHealthChecks("/hc/live", new HealthCheckOptions()
    {
        Predicate = (check) => check.Tags.Contains(HealthCheckExtension.Tags.Liveness),
        ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
    });
});

Further Reading

https://docs.microsoft.com/en-us/dotnet/api/overview/azure/security.keyvault.secrets-readme-pre

https://azuresdkdocs.blob.core.windows.net/$web/dotnet/Azure.Security.KeyVault.Secrets/4.0.0/api/index.html