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.


2 Comments

james · 24th March 2019 at 1:59 pm

Can you elaborate on how to get tenant id and subscription id? and if you can’t put your client id and client secret in this app and you don’t yet have access to key vault, what’s the solution for storing these particular secrets?

Shinigami · 24th March 2019 at 2:21 pm

The easiest way I’ve found to get subscription ID and tenant ID is from the portal. Subscription ID is visible in the “Subscriptions” blade and tenant ID is available from the “Directory + Subscription” filter on the top right.

If you can’t use key vault then you could use an appsettings file.

https://blog.bitscry.com/2017/05/30/appsettings-json-in-net-core-console-app/

Though the secrets here are obviously in the clear so it’ll depend where you’re app is running as to if this is a good idea or not.

Leave a Reply

Your e-mail address will not be published. Required fields are marked *