Keyvault and Azure Functions

We want to manage secrets from within a portal developed azure function in c#. In the following we develop a function, which is able to save / update a secret into a keyvault

This post was inspired by https://richardswinbank.net/adf/access_google_analytics_with_azure_data_factory

Prerequisits

We first create a resource group (here keyvaulttest) and within this 3 resources
– a keyvault named kvfunctest001 with one secret („here named „Token“)
– a new Azure function here named kvfunctest001, consuption tier, runtime stack .net, version 6 (LTS)

To authenticate the azure function to the keyvault we want to use a managed identity to avoid any credential handling. This can be enabled in the azure function, area „Settings“, Entry „Identity“. Here we enable it under „System assigned“. We copy the Object (principal) Id and go to the keyvault. Under Access Policies we create a new policy with the required permissions (e.g. Secret permissions: Get, Let, Set, Delete). In the following principal selection we use our above copied Object Id and save this new policy. With this the function has access to the keyvault.

Create the azure function

Within our azure function app we create a new http Triggerd function named SaveKeyvaultSecret and go to Code + Test

In order to access a keyvault via managed identity we need to add two nuget packages.
– Azure.Identity (Version 1.8.2): This is required to get the authentication from the managed identity
– Azure.Security.KeyVault.Secrets (Version 4.4.0): This is required to access the keyvault functionality
A research shows various working and not working ways to configure this. According to the microsoft documentation we have the following structure:
Azure Functions C# script developer reference | Microsoft Learn

FunctionsProject
 | - MyFirstFunction
 | | - run.csx
 | | - function.json
 | | - function.proj
 | - MySecondFunction
 | | - run.csx
 | | - function.json
 | | - function.proj
 | - host.json
 | - extensions.csproj
 | - bin

Nuget packages can be added by adding it to a self created function.proj file in the individual function directory or in the overall self created extensions.csproj. This can be done by uploading a new file in the editor or by using the app service editor.

We create a file function.proj (the extensions.csproj was not correctly working) in our new function with the following content:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Azure.Identity" Version="1.8.2" />
        <PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.4.0" />
    </ItemGroup>
</Project>

Once this file is created we can see that the function app is generating a new NuGet folder as well as a project.assets.json file:

Now we code the logic we require. We want to keep the references by configuration and add two new config settings in the function app under Settings -> Configuration:
– KeyVaultSecretName : Name of the secret we want to manage, in our sample we use „token“.
– KeyVaultUrl: Url, which can be found in the keyvault overview

We need to add these new references to the azure function:

using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

To login to the keyvault we use the object Azure.Identity.DefaultAzureCredential, which retrieves the token for our managed identiy. The access to the keyvault works with the object Azure.Security.KeyVault.Secrets.SecretClient, which takes the DefaultAzureCredential as parameter. This client is than responsible to manage the keyvault entries. In summary here a very simple Azure function:

#r "Newtonsoft.Json"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
    var keyVaultUri = GetEnvironmentVariable("KeyVaultUrl");
    var keyVaultSecretName = GetEnvironmentVariable("KeyVaultSecretName");

    log.LogInformation("Keyvault: " + keyVaultUri);
    log.LogInformation("SecretName: " + keyVaultSecretName);

    string token = req.Query["token"];

    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    dynamic data = JsonConvert.DeserializeObject(requestBody);
    token = token ?? data?.token;

    var client = new SecretClient(vaultUri: new Uri(keyVaultUri), credential: new DefaultAzureCredential());   

    if (!string.IsNullOrEmpty(token))
    {        
        try
        {
            client.SetSecret(keyVaultSecretName, token);
            return new OkObjectResult("Function executed");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            
            return new ObjectResult(ex.Message)
            {
                StatusCode = 500
            };
        }
    }

    return new BadRequestObjectResult("No Token given");
}

public static string GetEnvironmentVariable(string name)
{
    return 
        System.Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process);
}

The complete code can be found here:

AzureFunctionsSamples/SaveKeyvaultSecret