Skip to content

[API Proposal]: Support of secured value decryption configuration options #95899

Open
@HHobeck

Description

@HHobeck

Background and motivation

Dear community,

I came up with the idea to extend the Microsoft.Extensions.Options model to support a secured value decryption of dedicated configuration properties marked with SecuredValueAttribute. Handling with sensitive information in application settings is very difficult and error prone. Because every API developer and third party library authors knows what sensitive information are it is very easy to mark it with deserving protection.

The idea is to encrypt the sensitive information in the application settings with e.g. IDataProtector on design time (via IDE would be awesome) and decrypt it when the application started. This has the advantage that you can check in you configuration without to be afraid that the sensitive information are in the repository.

The idea would be to have a provider based approach and supporting other security provider for AWS, MS Azure and so on.

Thank you for reading.

API Proposal

using System;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Microsoft.Extensions.Options;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
public class SecuredValueAttribute : Attribute
{
}

public static class OptionsWithSecuredValueDecryptionComposition
{
    public static void Register(IServiceCollection serviceCollection)
    {
        if (serviceCollection is null) throw new ArgumentNullException(nameof(serviceCollection));

        serviceCollection.AddSingleton(typeof(IPostConfigureOptions<>), typeof(SecuredValueDecryptionConfigureOptions<>));
    }
}

public sealed class SecuredValueDecryptionConfigureOptions<TOptions> : IPostConfigureOptions<TOptions> where TOptions : class
{
    private readonly IHostEnvironment _hostEnvironment;
    private readonly Lazy<IDataProtector> _dataProtectorLazy;

    public SecuredValueDecryptionConfigureOptions(IHostEnvironment hostEnvironment, IServiceProvider serviceProvider)
    {
        _hostEnvironment = hostEnvironment ?? throw new ArgumentNullException(nameof(hostEnvironment));
        if (serviceProvider is null) throw new ArgumentNullException(nameof(serviceProvider));

        _dataProtectorLazy = new Lazy<IDataProtector>(() =>
            serviceProvider.GetRequiredService<IDataProtectionProvider>().CreateProtector("SecuredValueDecryption")
        );
    }

    public void PostConfigure(string? name, TOptions options)
    {
        if (_hostEnvironment.IsDevelopment()) return;

        // Detect encrypted configuration properties and decrypt them here if the environment is not Development.
    }
}

API Usage

internal class Program
{
    static void Main(string[] arguments)
    {
        IHostBuilder hostBuilder = Host.CreateDefaultBuilder(arguments);
        hostBuilder.ConfigureServices((hostBuilderContext, serviceCollection) =>
        {
            OptionsWithSecuredValueDecryptionComposition.Register(serviceCollection);
            serviceCollection.Configure<ServiceSettings>(
                hostBuilderContext.Configuration.GetSection(typeof(ServiceSettings).FullName!)
            );
        });

        using IHost host = hostBuilder.Build();
        host.Run();
    }
}

[SecuredValue]
public class ServiceSettings
{
    public string UserName { get; set; } = string.Empty;

    [SecuredValue]
    public string Password { get; set; } = string.Empty;
}

Alternative Designs

No response

Risks

This is additive and every developer can decide to use it or not.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions