Sander.KeyVaultCache 1.1.0

dotnet add package Sander.KeyVaultCache --version 1.1.0
NuGet\Install-Package Sander.KeyVaultCache -Version 1.1.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Sander.KeyVaultCache" Version="1.1.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Sander.KeyVaultCache --version 1.1.0
#r "nuget: Sander.KeyVaultCache, 1.1.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Sander.KeyVaultCache as a Cake Addin
#addin nuget:?package=Sander.KeyVaultCache&version=1.1.0

// Install Sander.KeyVaultCache as a Cake Tool
#tool nuget:?package=Sander.KeyVaultCache&version=1.1.0

GitHub license NetStandard 2.0 NuGet v1.1.0

Introduction

KeyVaultCache is read-only cache as system-of-record pattern implementation for Azure Key Vault.

What this means in humanese is that KeyVaultCache takes care of fetching and caching of the values from the Azure Key Vault.

Since secrets and certificates change very rarely, it is useful to cache them, as fetching is a relatively slow operation - if the Key Vault is in the same data center, it takes 100...300ms, but fetching value from geographically distant Azure data center can take a second or even more. With the caching, getting the value becomes a sub-millisecond operation.

KeyVaultCache simplifies the fetching and caching of the values from the Key Vault. It is fully thread-safe, has on-demand re-fetching and supports cache expiration (entries expire after specified time).

Features

  • Easy to use
  • On-demand re-fetching of the values from Key Vault (for example, if accessing blob storage fails, first step would be to re-fetch the secret and try again)
  • Supports Key Vault secrets, certificates and keys
  • Methods to return either bundle (e.g. SecretBundle, CertificateBundle, KeyBundle) or values directly (string for secret, byte array for certificate, JsonWebKey (JWK) for key).
  • Optional cache expiration (absolute time - entries expire after specified time and will be re-fetched when requested).
  • Fully thread-safe (one thread can request re-fetch of the secret, other threads will wait until fetching completes and use the new value).
  • .NET Standard 2.0, meaning KeyVaultCache can be used with .NET Framework 4.6.1+, .NET Core 2.0 and more - see here for detailed information.
Dependencies

Use & examples

KeyVaultCache is meant to be used as a single instance per Key Vault. You can store the KeyVaultCache instance in a static variable or configure your IoC/DI framework accordingly.

Simple use

Shared.KeyVaultCache = new KeyVaultCache(new KeyVaultClient("your-authentication-token"));
...
...
var storageAccountConnectionString =
	$"DefaultEndpointsProtocol=https;AccountName={BlobStorageName};AccountKey={await Shared.KeyVaultCache.GetSecret("https://your-key-vault.vault.azure.net/secrets/storageaccesskey")};EndpointSuffix=core.windows.net"; 

First line will create the KeyVaultCache instance where values never expire.
After that we'll use that instance to fetch the Azure Storage access key to be used in a connection string.

Example with expiration and retry

Shared.KeyVaultCache = new KeyVaultCache(new KeyVaultClient("your-authentication-token"),  TimeSpan.FromMinutes(15));
...
...
public async Task<string> GetUsername(long userId, bool isRetry = false)
{
	try
	{
		var certificateBytes = Shared.KeyVaultCache.GetCertificate("https://your-key-vault.vault.azure.net/certificates/yourcertificate", isRetry);
		//this can write certificate to Windows temp folder, so don't do it!
		var accessCertificate = new X509Certificate2(certificateBytes);
		//fetch username
...
		return username;

	}
	catch(Exception ex)
	{
		Trace.Writeline(ex);
		if (isRetry)
			throw;

		return await GetUsername(userId, true);
	}
}

In this example we create an instance of the KeyVaultCache where all cached values will expire in 15 minutes after initial fetching, so next request for value will re-fetch it from Azure and cache again for 15 minutes.

The method GetUsername() has parameter isRetry with a default value of false. This allows us to implement a very simple retry mechanism - if the initial (isRetry == false) username request fails with exception, we'll try once more. And the second attempt with isRetry == true also re-requests the certificate from the Azure Key Vault - maybe a new certificate was added to the data store and Key Vault.

This allows very simple key rotation without any downtime for the service, as is often required by various govermental and other entities.

For example, if our resource is Azure Storage account, we have primary accesskey in the Key Vault which has been fetched and cached by our application. We'll regenerate the secondary access key and store it to KeyVault instead of the primary key - and regenerate the primary key, invalidating our cached value. This means that our next attempt to access storage account fails, but we will go and fetch the new valid value from the Key Vault - and can re-attempt the operation without any downtime.

FAQ

  • How to use KeyVaultCache methods from non-async method?

var secret = Shared.KeyVaultCache.GetSecret("url-here").GetAwaiter().GetResult();

  • How to manually remove value from cache?

keyVaultCache.Remove("url-here"); will remove just the specified value.
keyVaultCache.Clear(); removes all cached entries. Both operations are thread-safe.

Changelog

  • 1.0.0 Initial release
  • 1.1.0
    • Using IKeyVaultClient in constructor to simplify unit testing by consumer projects
    • Minor code optimizations to simplify locking logic
    • Clarify descriptions for both NuGet and readme.md
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.1.0 901 9/17/2018
1.0.0 739 9/10/2018