6 min read

Backup & Restore Key Vault Secrets via Azure Functions

Justin Yoo

In my previous post, we used Azure Logic App to backup and restore secrets in Azure Key Vault. Logic App is really easy to achieve the goal with bare minimum codes, or even without codes. On the other hand, there are clear requirements to build an application for the same feature. Therefore, in this post, I'm going to use Azure Functions to backup and restore Azure Key Vault secrets.

The sample codes used in this post can be fount at this GitHub repository.

Activating Managed Identity against Azure Function App

For easy access to the Azure Key Vault instance from Azure Functions app, it's crucial to enable the Managed Identity feature. As I wrote another blog post about this, I'm not going to discuss further here.

Workflow to Backup Secrets from Azure Key Vault

The workflow for Key Vault backup is the same as the previous post:

  1. Get the list of secrets
  2. Run the for...each loop and backup each secret within the loop
  3. Generate an array containing the backup result
  4. Serialise the array and upload it to Azure Blob Storage

Based on the workflow descrived above, the first method is to get the list of secrets. As you can see, the method returns the list of secret names.

public async Task<List<string>> GetSecretsAsync()
{
// Declares a KeyVaultClient instance.
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var kv = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
// Gets the list of secrets.
var baseUri = "https://myu-keyvault.vault.azure.net/";
var secrets = await kv.GetSecretsAsync(baseUri)
.ConfigureAwait(false);
// Returns the list of secret names.
return secrets.Select(p => p.Identifier.Name).ToList();
}
view raw get-secrets.cs hosted with ❤ by GitHub

The next method is to backup each secret, using the list of secrets. At the time of this writing, there's no bulk backup feature supported yet. Therefore, we need to run the loop like below:

public async Task<List<BackupSecretResult>> BackupSecretsAsync(List<string> secrets)
{
// Declares a KeyVaultClient instance.
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var kv = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
// Performs the backup and add the result into the list.
var results = new List<BackupSecretResult>();
var baseUri = "https://my-keyvault.vault.azure.net/";
foreach (var name in secrets)
{
var result = await kv.BackupSecretAsync(baseUri, name)
.ConfigureAwait(false);
results.Add(result);
}
// Returns the backup results.
return results;
}

We get the backup result as a list. Serialise the list and upload it to Azure Blob Storage.

public async Task<bool> UploadAsync(List<BackupSecretResult> results)
{
// Declares the BlobClient instance.
var connectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage");
var blob = CloudStorageAccount.Parse(connectionString)
.CreateCloudBlobClient();
// Gets the Blob container.
var containerName = "backups";
var container = blob.GetContainerReference(containerName);
await container.CreateIfNotExistsAsync().ConfigureAwait(false);
// Gets the Blob.
var blobName = $"{DateTimeOffset.UtcNow.ToString("yyyyMMdd")}.json";
var blob = container.GetBlockBlobReference(blobName);
// Serialises the backup result.
var serialised = JsonConvert.SerializeObject(results);
// Uploads the backup result to Blob Storage.
await blob.UploadTextAsync(serialised).ConfigureAwait(false);
// Returns true, if everything is OK.
return true;
}
view raw upload.cs hosted with ❤ by GitHub

We got the whole working code bits. Let's put them all together in an HTTP trigger as a workflow.

[FunctionName(nameof(BackupSecrets))]
public async Task<IActionResult> BackupSecrets(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "secrets/backup")] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
// Gets the list of secrets.
var secrets = await this.GetSecretsAsync().ConfigureAwait(false);
// Performs the backup.
var results = await this.BackupSecretsAsync(secrets).ConfigureAwait(false);
// Uploads the backup data.
var uploaded = await this.UploadAsync(results).ConfigureAwait(false);
return new OkObjectResult(results);
}

Let's verify whether the function endpoint works or not. In our local development environment, to use the Managed Identity feature, we need to log in through Azure CLI first. Once logged in, run the debugging mode on the VS Code and check the result through Postman. The expected result might be:

The backup has been successfully stored into the local storage emulator, Azurite.

So far, we've walked through how to backup Azure Key Vault secrets into Azure Blob Storage, using Azure Functions.

Workflow to Restore Secrets to Azure Key Vault

The workflow described in the previous post gets the whole list of backup files and picks up the latest one, then restore it to Key Vault. This time in this post, we specify the specific backup file for restore. Here's the workflow.

  1. Get the timestamp to restore a backup file
  2. Download the backup file from Azure Blob Storage
  3. Deserialise the downloaded content
  4. Restore the content to Azure Key Vault

The timestamp has the format of yyyyMMdd, and it's passed through the endpoint URL. The code that downloads the backup file, corresponding to the timestamp looks like:

public async Task<List<BackupSecretResult>> DownloadAsync(string timestamp)
{
// Declares the BlobClient instance.
var connectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage");
var client = CloudStorageAccount.Parse(connectionString)
.CreateCloudBlobClient();
// Gets the Blob container.
var containerName = "backups";
var container = client.GetContainerReference(containerName);
// Gets the Blob.
var blobName = $"{timestamp}.json";
var blob = container.GetBlockBlobReference(blobName);
// Downloads the Blob content.
var downloaded = await blob.DownloadTextAsync().ConfigureAwait(false);
// Deserialises the contents.
var results = JsonConvert.DeserializeObject<List<BackupSecretResult>>(downloaded);
// Returns the result.
return results;
}
view raw download.cs hosted with ❤ by GitHub

After deserialising the downloaded content, the method below loops through the content, which is basically a list.

public async Task<List<string>> RestoreSecretsAsync(string key, List<BackupSecretResult> secrets)
{
// Declares a KeyVaultClient instance.
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var kv = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
// Performs the restore and add the result into the list.
var results = new List<SecretBundle>();
var baseUri = "https://my-keyvault.vault.azure.net/";
foreach (var secret in secrets)
{
var result = await kv.RestoreSecretAsync(baseUri, secret.Value)
.ConfigureAwait(false);
results.Add(result);
}
// Returns the list of secret names.
return results.Select(p => p.SecretIdentifier.Name).ToList();
}

We've got the basic restoration logic. Let's build another HTTP trigger to embrace this workflow.

[FunctionName(nameof(RestoreSecrets))]
public async Task<IActionResult> RestoreSecrets(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "secrets/restore/{timestamp}")] HttpRequest req,
string timestamp,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
// Downloads the backup of the given timestamp.
var secrets = await this._blob.DownloadAsync(timestamp).ConfigureAwait(false);
// Performs the restore from the backup.
var results = await this._secret.RestoreSecretsAsync("restore", secrets).ConfigureAwait(false);
return new OkObjectResult(results);
}

When the trigger is run in Postman, here's the result.

Those secrets are perfectly restored to the new Azure Key Vault instance.


So far, we've discussed how to backup and restore Azure Key Vault secrets, and store them into or fetch them from Azure Blob Storage. The code snippets above are working example, but many parts were omitted for better readability. As we can download the sample source code here in this repository, let's practice them with your Free Azure Account.