Azure Key Vault is a pretty handy way of centrally managing access to secrets and logging what process has requested access to them. The best way to use it is for Azure hosted resources such as Web Applications or VMs for which you can assign a managed identity to the resource and grant this identity access to the vault. However, if you want to access vault secrets from a console application running on a local server you’ll need to do it via a service principle.

A service principle can be created in the Azure Active Directory blade in the portal, this is done by registering an app (it doesn’t need an endpoint) and noting down the Application (client) ID and Directory (tenant) ID as well as the name you gave it. This service principle then needs to be granted rights to access the key vault, this can be done in the Access policies blade in the Key Vault where rights can be granted separately for keys, secrets and certificates. Annoyingly it’s not possible to assign access to only specific keys in the vault to a service principle with the service principle having access to everything there provided you’ve granted it the relevant permissions.

The below code is an example of how to access Key Vault keys in a console application that will run from a local server, I have granted my service principle AzureResourceReport get permissions on both keys and secrets. In a real application the client ID and secret obviously shouldn’t be hard coded in the code!

Program.cs

using AzureResourceReport.Models;
using Microsoft.Azure.KeyVault;
using Microsoft.Azure.Management.ResourceManager.Fluent;
using Microsoft.Azure.Management.ResourceManager.Fluent.Authentication;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace AzureResourceReport
{
    class Program
    {
        static void Main(string[] args)
        {
            // Authenticate
            string clientId = "MYCLIENTID";
            string clientSecret = "MYCLIENTSECRET";

            AzureCredentials credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(clientId, clientSecret, tenantId, AzureEnvironment.AzureGlobalCloud).WithDefaultSubscription(subscriptionId);

            var keyClient = new KeyVaultClient(async (authority, resource, scope) =>
            {
                var adCredential = new ClientCredential(clientId, clientSecret);
                var authenticationContext = new AuthenticationContext(authority, null);
                return (await authenticationContext.AcquireTokenAsync(resource, adCredential)).AccessToken;
            });

            KeyVaultCache keyVaultCache = new KeyVaultCache("https://bitscryvault.vault.azure.net/secrets/", clientId, clientSecret);
            var cacheSecret = keyVaultCache.GetCachedSecret("BlogConnection");

            string connectionString = cacheSecret.Result;
        }
    }
}

KeyVaultCache.cs

using Microsoft.Azure.KeyVault;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace AzureResourceReport.Models
{
    public class KeyVaultCache
    {
        public KeyVaultCache(string baseUri, string clientId, string clientSecret)
        {
            BaseUri = baseUri;
            ClientId = clientId;
            ClientSecret = clientSecret;
        }

        public static string BaseUri { get; set; }

        public static string ClientId { get; set; }

        public static string ClientSecret { get; set; }

        private static KeyVaultClient _KeyVaultClient = null;

        private static Dictionary<string, string> SecretsCache = new Dictionary<string, string>();

        public async Task<string> GetCachedSecret(string secretName)
        {
            if (!SecretsCache.ContainsKey(secretName))
            {
                if (_KeyVaultClient is null)
                {
                    _KeyVaultClient = new KeyVaultClient(async (authority, resource, scope) =>
                    {
                        var adCredential = new ClientCredential(ClientId, ClientSecret);
                        var authenticationContext = new AuthenticationContext(authority, null);
                        return (await authenticationContext.AcquireTokenAsync(resource, adCredential)).AccessToken;
                    });
                }

                var secretBundle = await _KeyVaultClient.GetSecretAsync($"{BaseUri}{secretName}").ConfigureAwait(false);
                SecretsCache.Add(secretName, secretBundle.Value);
            }

            return SecretsCache.ContainsKey(secretName) ? SecretsCache[secretName] : string.Empty;
        }
    }
}

The KeyVaultCache is used to cache previously retrieved secrets to improve performance as there is some overhead with fetching them from the Key Vault. The above code is based on that from this post.


Leave a Reply

Your email address will not be published. Required fields are marked *