Moedim.AzureVault

Azure Vault Library for prefixes support.


Keywords
dotnet, azure, vault
License
MIT
Install
Install-Package Moedim.AzureVault -Version 1.0.2

Documentation

Moedim.AzureVault

GitHub license master workflowNuGet Nuget feedz.io

This is a Hebrew word that translates "feast" or "appointed time." "Appointed times" refers to HaSham's festivals in Vayikra/Leviticus 23rd. The feasts are "signals and signs" to help us know what is on the heart of HaShem.

Note: Pre-release packages are distributed via feedz.io.

This goal of this repo is to provide with a reusable Azure Vault Key functionality for developing Microservice.

On application start, the secrets are loaded from Azure Key Vault based on hosting environment or other prefixes i.e. local--mykey.

Hire me

Please send email if you consider to hire me.

buymeacoffee

Give a Star!

If you like or are using this project to learn or start your solution, please give it a star. Thanks!

Install

    dotnet add package Moedim.AzureVault

Usage

Azure Key Vault Secrets can be something like:

    dev--db--connectionString = "some secret connection"
    prod--db--connectionString = "some secret connection"

If Azure AD App credentials are used than TenantId, ClientId and ClientSecret are also required values.

For Azure AD DefaultCredential provider to be used only BaseUrl is required. The extension method validates BaseUrl value.

"AzureVault": {
    "BaseUrl": "https://kdcllc.vault.azure.net/",
    "TenantId" : "",
    "ClientId": "",
    "ClientSecret": ""
  }

We configure our host to use Azure Key Vault with reload based on hosting environment and 10 seconds interval for reloading the secrets.

     Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((hostingContext, configBuilder) =>
        {
            // based on environment Development = dev; Production = prod prefix in Azure Vault.
            var envName = hostingContext.HostingEnvironment.EnvironmentName;
            var configuration = configBuilder.AddAzureKeyVault(
                envName,
                reloadInterval: TimeSpan.FromSeconds(10));

            // helpful to see what was retrieved from all of the configuration providers.
            if (hostingContext.HostingEnvironment.IsDevelopment())
            {
                configuration.DebugConfigurations();
            }
        })
        .ConfigureServices((hostContext, services) =>
        {
            services.AddOptions<SampleOptions>().Bind(hostContext.Configuration.GetSection("Sample"));

            services.AddHostedService<Worker>();
        });

Then

    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;
        private SampleOptions _options;

        public Worker(ILogger<Worker> logger, IOptionsMonitor<SampleOptions> options)
        {
            _logger = logger;

            _options = options.CurrentValue;

            // makes the update to the options based on 10 seconds.
            options.OnChange((opt) =>
            {
                _options = opt;
            });
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("Worker running at: {time} - {name}", DateTimeOffset.Now, _options.Name);

                await Task.Delay(1000, stoppingToken);
            }
        }
    }

References

        private void CheckConfiguration(IApplicationBuilder app, IServiceCollection services)
        {
            var optionsServiceDescriptors = services.Where(s => s.ServiceType.Name.Contains("IOptionsChangeTokenSource"));

            foreach (var service in optionsServiceDescriptors)
            {
                var genericTypes = service.ServiceType.GenericTypeArguments;

                if (genericTypes.Length > 0)
                {
                    var optionsType = genericTypes[0];
                    var genericOptions = typeof(IOptions<>).MakeGenericType(optionsType);

                    dynamic instance = app.ApplicationServices.GetService(genericOptions);
                    var options = instance.Value;
                    var results = new List<ValidationResult>();

                    var isValid = Validator.TryValidateObject(options, new ValidationContext(options), results, true);
                    if (!isValid)
                    {
                        var messages = new List<string> { "Configuration issues" };
                        messages.AddRange(results.Select(r => r.ErrorMessage));
                        throw new Exception(string.Join("\n", messages));
                    }
                }
            }
        }