In addition to use Azure App Service or docker containers, you can also securely store your app secrets in production using IDataProtector
:
- App secrets are entered with running a -config switch of the application, for example:
dotnet helloworld -config
; InProgram.Main
, detects this switch to let user to enter the secrets and store in a separate .json file, encrypted:
public class Program
{
private const string APP_NAME = "5E71EE95-49BD-40A9-81CD-B1DFD873EEA8";
private const string SECRET_CONFIG_FILE_NAME = "appsettings.secret.json";
public static void Main(string[] args)
{
if (args != null && args.Length == 1 && args[0].ToLowerInvariant() == "-config")
{
ConfigAppSettingsSecret();
return;
}
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((builder, options) =>
{
options.AddJsonFile(ConfigFileFullPath, optional: true, reloadOnChange: false);
})
.UseStartup<Startup>();
internal static IDataProtector GetDataProtector()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddDataProtection()
.SetApplicationName(APP_ID)
.PersistKeysToFileSystem(new DirectoryInfo(SecretsDirectory));
var services = serviceCollection.BuildServiceProvider();
var dataProtectionProvider = services.GetService<IDataProtectionProvider>();
return dataProtectionProvider.CreateProtector(APP_ID);
}
private static void ConfigAppSettingsSecret()
{
var protector = GetDataProtector();
string dbPassword = protector.Protect("DbPassword", ReadPasswordFromConsole());
... // other secrets
string json = ...; // Serialize encrypted secrets to JSON
var path = ConfigFileFullPath;
File.WriteAllText(path, json);
Console.WriteLine($"Writing app settings secret to '${path}' completed successfully.");
}
private static string CurrentDirectory
{
get { return Directory.GetParent(typeof(Program).Assembly.Location).FullName; }
}
private static string ConfigFileFullPath
{
get { return Path.Combine(CurrentDirectory, SECRET_CONFIG_FILE_NAME); }
}
}
- In Startup.cs, read and decrypt the secret:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
if (env.IsProduction())
{
var protector = Program.GetDataProtector();
var builder = new SqlConnectionStringBuilder();
builder.Password = protector.Unprotect(configuration["DbPassword"]);
...
}
}
BTW, appsettings.production.json
or environment variable is really not an option. Secrets, as its name suggests, should never be stored in plain text.