Setting up Azure Key Vault in Kentico Xperience 13
23/10/2023Azure Key Vault is a cloud-based service designed to secure sensitive information, keys, and secrets that should not be hardcoded into your applications.
It proves particularly valuable when you need to store and manage items like database connection strings, API keys, or any other confidential information you wish to protect. Letβs take a look at how to configure Kentico Xperience 13 to work with Azure Key Vault.
Presentation website β ASP.NET Core
Considering the substantial emphasis on security in modern times, it's unsurprising to discover a huge number of online resources related to Azure Key Vault and ASP.NET Core. There are plenty of online tutorials and code samples across several platforms and programming languages.
When implementing Azure Key Vault for ASP.NET Core websites, I typically follow this example provided by Microsoft. This guide explains how to configure an ASP.NET Core application to use managed identities to authenticate and access secrets from Azure Key Vault. This is perfect for setting up Azure Key Vault with Kentico Xperience 13 presentation websites. The implementation focuses around installing the following NuGet packages and using the extension method to register the Key Vault instance.
- Azure.Extensions.AspNetCore.Configuration.Secrets
- Azure.Identity
- Azure.Security.KeyVault.Secrets
An example implementation I have used recently (not using the new minimal hosting model) is below:
.ConfigureAppConfiguration((context, config) =>
{
if (context.HostingEnvironment.IsProduction() || context.HostingEnvironment.IsUAT())
{
var builtConfig = config.Build();
var keyVaultUrl = $"https://{builtConfig["KeyVaultName"]}.vault.azure.net/";
var secretClient = new SecretClient(
new Uri(keyVaultUrl),
new DefaultAzureCredential());
config.AddAzureKeyVault(secretClient, new KeyVaultSecretManager());
}
})
Administration website - ASP.NET Web Forms
A slightly more challenging task when setting up Azure Key Vault for Kentico Xperience 13 is configuring the administration portal. The challenge stems from the admin portal being built on older ASP.NET Web Forms technology, making it less straightforward to find relevant resources to set up the configuration.
I found that the most straightforward approach of connecting the admin site with Azure Key Vault was through creating a custom implementation of the IConnectionStringService and IAppSettingsService interfaces.
In my custom implementations I offer a fall-back option to Kentico, by checking for the appropriate connection string or app setting in Azure Key Vault. This only happens if a value isn't found within the usual locations, I would then perform a lookup in Azure Key Vault and try to retrieve the necessary secret.
Let's take a look at some of the code! π
Both of these implementations are based on the default Kentico services, and both use the following methods for accessing Key Vault secrets:
private static async Task<SecretBundle> GetSecretFromKeyVaultAsync(KeyVaultClient keyVaultClient, string secretName)
{
var keyVaultName = ConfigurationManager.AppSettings["KeyVaultName"];
var keyVaultUrl = $"https://{keyVaultName}.vault.azure.net/";
try
{
var secretIdentifier = $"{keyVaultUrl}secrets/{secretName}";
return await keyVaultClient.GetSecretAsync(secretIdentifier);
}
catch (KeyVaultErrorException ex)
{
// Handle any exception occurred during the secret retrieval process.
Console.WriteLine($"Error retrieving secret: {ex.Message}");
return null;
}
}
private static async Task<string> GetAccessTokenAsync(string authority, string resource, string scope)
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
return await azureServiceTokenProvider.GetAccessTokenAsync(resource);
}
GetSecretFromKeyVaultAsync provides the custom services a simple method for retrieving secrets from the key vault and returning the SecretBundle to the caller, which contains the secret value inside.
GetAccessTokenAsync is another simple method that generates an access token for the given resource, this is important as it is required to authenticate the admin site with the Azure Key Vault instance.
Next we'll look at how I'm using these methods to look up a connection string in the custom IConnectionStringService implementation.
/// <summary>
/// Gets the specific connection string from the app config
/// </summary>
public string this[string name]
{
get
{
// Get connection string
var connString = SettingsHelper.ConnectionStrings[name];
if (connString == null)
{
if (!name.Equals("CMSConnectionString", StringComparison.InvariantCultureIgnoreCase))
{
return null;
}
return GetAzureKeyVaultConnectionString();
}
return connString.ConnectionString;
}
set
{
if (String.IsNullOrEmpty(name))
{
throw new ArgumentException(nameof(name));
}
SettingsHelper.ConnectionStrings[name] = new ConnectionStringSettings(name, value);
}
}
private string GetAzureKeyVaultConnectionString()
{
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetAccessTokenAsync));
var connectionString = Task.Run(() => GetSecretFromKeyVaultAsync(keyVaultClient, "ConnectionStrings--CMSConnectionString")).GetAwaiter().GetResult();
var connectionStringValue = connectionString?.Value;
if (!string.IsNullOrWhiteSpace(connectionStringValue))
{
return connectionStringValue;
}
return null;
}
I start by amending Kentico's default indexer property, if there isn't a CMSConnectionString already added, then we call the new method GetAzureKeyVaultConnectionString. This method calls the previous methods to authenticate and retrieve the ConnectionStrings--CMSConnectionString secret from Azure Key Vault which contains our database connection string.
Now we'll look at how I customised the AppSettingsService.
// Added a whitelist of app settings so we're only looking up keys we want to.
private static readonly HashSet<string> WhiteListedAppSettings = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"CMSAzureAccountName",
"CMSAzureSharedKey",
};
/// <summary>
/// Gets the specific settings from the application configuration file or from a memory.
/// Sets a specific settings with a value into a memory.
/// </summary>
public string this[string key]
{
get
{
var appSetting = SettingsHelper.AppSettings[key];
if (appSetting == null)
{
if (!WhiteListedAppSettings.Contains(key))
{
return null;
}
return GetAzureKeyVaultAppSetting(key);
}
return SettingsHelper.AppSettings[key];
}
set
{
SettingsHelper.AppSettings[key] = value;
}
}
private string GetAzureKeyVaultAppSetting(string key)
{
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetAccessTokenAsync));
var appSetting = Task.Run(() => GetSecretFromKeyVaultAsync(keyVaultClient, key)).GetAwaiter().GetResult();
var appSettingValue = appSetting?.Value;
if (!string.IsNullOrWhiteSpace(appSettingValue))
{
return appSettingValue;
}
return null;
}
Similar to the connection string example, I am changing Kentico's default indexer property, but this time I'm restricting the keys that we want to look up - to those that are stored within the white list. GetAzureKeyVaultAppSetting only really differs to GetAzureKeyVaultConnectionString in that it accepts a key as a parameter and then uses that to do the look up in Azure Key Vault.
You can find the full example code for both custom service implementations in this gist.
The future β Xperience by Kentico
The challenges mentioned here for Kentico Xperience 13 are a thing of the past with Xperience by Kentico. Xperience by Kentico is developed using the latest ASP.NET Core versions and seamlessly integrates the administration portal with the presentation website (added through a NuGet package). Now we can follow the many tutorials and code samples that are available for ASP.NET Core for both our administration portal and the presentation website!
You might also be interested in...
Umbraco Tips for Newcomers: Insights from an Outsider
19/12/2024I have published my first Umbraco related article in the calendar, 24 Days in Umbraco. It provides insights for newcomers, some tips on how to get started. Read more about the article.
How to Easily Extend Xperience by Kentico UI Pages with Page Extenders
13/09/2024UI page extenders in Xperience by Kentico allow you to customise existing UI pages without modifying their source code. In this post, we’ll walk through a simple example of reordering columns in a listing layout to better suit your needs.
Enable Quick Page Editing from Your Xperience by Kentico Website Channels
23/08/2024Simplify content management with this new package for Xperience by Kentico. It adds an "Edit Page" button to your website channels, allowing editors to quickly access the Kentico administration interface and make updates effortlessly. Enhance your workflow and keep your content fresh with ease.