Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom JsonConfigurationProvider - ASP.NET Core uses wrong implementation

I tried to implement a custom JsonConfigurationProvider called 'CryptographyConfigProvider' that decrypts JSON from a stream if it is encrypted.

I inherited from the JsonConfigurationProvider and implemented a custom Load-method.

I use it like this in the Program.cs:

  var configuration = new ConfigurationBuilder()
                .AddEncryptedJsonFile($"appsettings.{enviromentValue}.json", optional: true, reloadOnChange: false)
                .Build();

This call executes my custom implementation. (see below)

But after this ASP.NET Core tries to access the appsettings file again here:

 webHostBuilder.Build().Run();

The exception indicates that the normal JsonConfigurationProvider is called and not my inherited class CryptographyConfigProvider.

System.FormatException: Could not parse the JSON file. Error on line number '0': 'EncryptedString'. 
---> Newtonsoft.Json.JsonReaderException: Unexpected character encountered while parsing value: S. Path '', line 0, position 0.
at Newtonsoft.Json.JsonTextReader.ParseValue()
at Newtonsoft.Json.Linq.JObject.Load(JsonReader reader, JsonLoadSettings settings)
at Microsoft.Extensions.Configuration.Json.JsonConfigurationFileParser.Parse(Stream input)
at Microsoft.Extensions.Configuration.Json.JsonConfigurationProvider.Load(Stream stream)
--- End of inner exception stack trace ---
at Microsoft.Extensions.Configuration.FileConfigurationProvider.Load(Boolean reload)
at Microsoft.Extensions.Configuration.FileConfigurationProvider.Load()
at Microsoft.Extensions.Configuration.ConfigurationRoot..ctor(IList`1 providers)
at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
at Microsoft.AspNetCore.Hosting.WebHostBuilder.BuildCommonServices(AggregateException& hostingStartupErrors)
at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
at Main(String[] args) in Program.cs

Has someone an idea why ASP.NET Core is using the normal JsonConfigurationProvider?

Here is my implementation:

public static class DecryptionConfigProviderExtension
{
    public static IConfigurationBuilder AddEncryptedJsonFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange)
    {
        return builder.AddJsonFile(s =>
        {
            s.FileProvider = null;
            s.Path = path;
            s.Optional = optional;
            s.ReloadOnChange = reloadOnChange;
            s.ResolveFileProvider();
        });
    }

    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<CryptographyConfigurationSource> configureSource) => builder.Add(configureSource);
}

public class CryptographyConfigurationSource : JsonConfigurationSource, IConfigurationSource
{
    public override IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        EnsureDefaults(builder);
        return new CryptographyConfigProvider(this);
    }
}

public class CryptographyConfigProvider : JsonConfigurationProvider
{
    private const string EncryptionKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456";

    private AesCryptography _aesCryptography;

    public CryptographyConfigProvider(CryptographyConfigurationSource cryptographyConfigurationSource) : base(cryptographyConfigurationSource)
    {
        _aesCryptography = new AesCryptography();
    }

    public override void Load(Stream stream)
    {
        Data = UnencryptConfiguration(stream);
    }

    private IDictionary<string, string> UnencryptConfiguration(Stream stream)
    {
        var reader = new StreamReader(stream);
        var text = reader.ReadToEnd();
        var jsonString = DecryptIfEncrypted(text);

        using (MemoryStream jsonStream = new MemoryStream())
        {
            var parser = new JsonConfigurationFileParser();
            StreamWriter writer = new StreamWriter(jsonStream);
            writer.Write(jsonString);
            writer.Flush();
            jsonStream.Position = 0;
            return parser.Parse(jsonStream);
        };
    }

    private string DecryptIfEncrypted(string text)
    {
        var jsonString = string.Empty;

        try
        {
            jsonString = _aesCryptography.DecryptString(text, EncryptionKey);
        }
        catch
        {
            jsonString = text;
        }

        return jsonString;
    }
}
like image 329
Shamshiel Avatar asked Jun 14 '18 13:06

Shamshiel


People also ask

Does .NET Core use app config?

Application configuration in ASP.NET Core is performed using one or more configuration providers. Configuration providers read configuration data from key-value pairs using a variety of configuration sources: Settings files, such as appsettings. json.

How do you inject IConfiguration in NET Core 6?

public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } private IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { // TODO: Service configuration code here... } public void Configure(IApplicationBuilder app, ...

What is Appsetting json in ASP.NET Core?

The appsettings. json file is generally used to store the application configuration settings such as database connection strings, any application scope global variables, and much other information.


2 Answers

As of .NET Core 2.0, appsettings.{env.EnvironmentName}.json is loaded automatically for you. If you have encrypted it, then the framework will probably have an issue parsing it.

.ConfigureAppConfiguration((hostingContext, config) =>
{
    ...

    config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
          .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);

    ...

MetaPackages/src/Microsoft.AspNetCore/WebHost.cs

I would try to name your file something else.

An alternative solution that my team recently implemented was to move secrets to app.config and use protected configuration to encrypt it. A custom configuration provider reads the application settings (e.g. Azure:ApiKey) and supplies them to the Core framework.

like image 87
Dan Wilson Avatar answered Sep 29 '22 19:09

Dan Wilson


Having to create custom providers and use old-school XML config files to handle encrypted settings is crazy. This should be handled by the framework, IMO.

In the mean time, my answer to this question is a pretty simple and straight-forward way to encrypt values in your settings files. It uses the existing JSON provider, preferred .Net Core encryption techniques, and is DI friendly.

Hope it helps!

like image 22
Scott Roberts Avatar answered Sep 29 '22 21:09

Scott Roberts