Saturday, 19 April 2025

Building a KeyVaultService in .NET for Secure Secret Management

////Declare Interface
public interface IKeyVaultService
{
    public string GetSecret(string secretName, bool appendPrefix = true);

}
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
public class KeyVaultService : IKeyVaultService
{
    private const string _PREFIX = "XXX";
    private readonly string _keyVaultBaseUrl;
    protected  SecretClient _keyVaultClient;
    private static readonly ConcurrentDictionary<string, string> _keyVaultSecrets =
                         new ConcurrentDictionary<string, string>();
    private readonly KeyVaultConfigAppSetting _keyVaultLocalConfigAppSetting;
    private readonly ILogger _logger;
   
    /// <summary>
    /// initialize
    /// </summary>
    /// <param name="keyVaultLocalOptions"></param>
    /// <exception cref="Exception"></exception>
    public KeyVaultService(
        IOptions<KeyVaultConfigAppSetting> keyVaultLocalOptions
        ,ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<KeyVaultService>();
        _keyVaultLocalConfigAppSetting = keyVaultLocalOptions.Value;
        _keyVaultBaseUrl = _keyVaultLocalConfigAppSetting.BaseUrl;
        _keyVaultBaseUrl = _keyVaultLocalConfigAppSetting.BaseUrl
            ?? throw new InvalidOperationException
            ("KeyVaultBaseUrl is null or not configured");

        EnsureConfigured();

    }

    /// <summary>
    /// get secret value from keyvault
    /// </summary>
    /// <param name="secretName"></param>
    /// <param name="appendPrefix"></param>
    /// <returns></returns>
    public string GetSecret(string secretName, bool appendPrefix = true)
    {
        try
        {
            EnsureConfigured();
       
            if (appendPrefix && !secretName.StartsWith(_PREFIX))
                secretName = $"{_PREFIX}{secretName}";
            _keyVaultSecrets.TryGetValue(secretName, out var secretValue);

            if (string.IsNullOrEmpty(secretValue))
            {
                var secretBundle = _keyVaultClient.GetSecret(secretName);
                secretValue = secretBundle.Value?.Value;

                if (!string.IsNullOrEmpty(secretValue))
                    _keyVaultSecrets.TryAdd(secretName, secretValue);
            }

            return secretValue;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Keyvalut Error:{0} {1}", GetType()?.Name, ex.Message);
            throw new InvalidOperationException
            ("An error occurred while accessing the Keyvault.", ex);
        }

    }

    private void EnsureConfigured()
    {
        if (_keyVaultClient == null)
#if DEBUG
//Create azure key-vault instance using Application client id and client secret.
            ConfigureWithClientCredentials();
#else
            ConfigureAzureCredentials();
#endif
    }

    private void ConfigureWithClientCredentials()
    {


        var tenantId = _keyVaultLocalConfigAppSetting.TenantId;
        var sharedSecretKey = _keyVaultLocalConfigAppSetting.SharedSecretKey;
        var sharedSecretAppId = _keyVaultLocalConfigAppSetting.AppId;

        // If Shared Secret is present and not "Disabled",
        // use the shared secret (typically for local development).
        if (!string.IsNullOrWhiteSpace(sharedSecretKey)
        && sharedSecretKey != "Disabled")
        {
           
                ClientSecretCredential clientCredential =
                            new ClientSecretCredential
                            (tenantId, sharedSecretAppId, sharedSecretKey);
                _keyVaultClient = new SecretClient
                    (vaultUri: new Uri(_keyVaultBaseUrl), credential: clientCredential);
           
        }
        else
        {
            //fallback to the default credential authentication
            _keyVaultClient = new SecretClient(
                vaultUri: new Uri(_keyVaultBaseUrl)
                , credential: new DefaultAzureCredential());
        }

    }

    private void ConfigureAzureCredentials()
    {
        try
        {
            _keyVaultClient = new SecretClient(
                vaultUri: new Uri(_keyVaultBaseUrl)
                , credential: new DefaultAzureCredential());
        }
        catch (Exception ex)
        {
            _logger.LogError
            (ex,"Keyvalut Error: {0}  - {1}", GetType()?.Name, ex.Message);
            throw;
        }
    }
}

No comments:

Post a Comment