我试图弄清楚哪里可以最好地存储ASP.NET Core应用程序的应用程序生产机密。有两个类似的问题 Where should I store the connection string for the production environment of my asp.net-5 app? 和 How to deploy ASP.NET Core UserSecrets to production 这两者都建议使用环境变量。
我的问题是我想运行具有不同数据库和不同数据库凭据的Web应用程序的多个实例。所以应该有一些包含秘密的每个实例配置。
如何以安全的方式实现这一目标?
请注意,应用程序应该能够自我托管并在IIS下托管! (稍后我们还计划在Linux上运行它,如果这对问题有任何重要性的话)
更新
这个问题与尝试在生产中使用ASP.NET用户机密无关! UserSecrets被排除用于生产。
答案 0 :(得分:6)
正如他们所述,用户机密仅用于开发(以避免意外地将凭据提交到SCM中)而不是用于生产。您应该为每个数据库使用一个连接字符串,即ConnectionStrings:CmsDatabaseProduction
,ConnectionStrings:CmsDatabaseDevelopment
等。
或者使用docker容器(当您不使用Azure App Service时),然后您可以按容器设置它。
或者,您也可以使用基于环境的appsetting文件。 appsettings.production.json
,但它们不得包含在源代码管理(Git,CSV,TFS)中!
在启动时只需:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
这样,您可以从appsettings.production.json
加载特定内容,并且仍然可以通过环境变量覆盖它。
答案 1 :(得分:2)
没有Azure Vault或任何第三方提供商,有一种简单的方法可以安全地在生产中存储应用程序秘密:
dotnet helloworld -config
;在Program.Main
中,检测到此开关以允许用户输入机密并将其存储在单独的加密的.json文件中: 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>();
private static void ConfigAppSettingsSecret()
{
var serviceCollection = new ServiceCollection();
AddDataProtection(serviceCollection);
var services = serviceCollection.BuildServiceProvider();
var dataProtectionProvider = services.GetService<IDataProtectionProvider>();
var protector = CreateProtector(dataProtectionProvider);
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); }
}
internal static void AddDataProtection(IServiceCollection serviceCollection)
{
serviceCollection.AddDataProtection()
.SetApplicationName(APP_NAME)
.DisableAutomaticKeyGeneration();
}
internal static IDataProtector CreateProtector(IDataProtectionProvider dataProtectionProvider)
{
return dataProtectionProvider.CreateProtector(APP_NAME);
}
}
public void ConfigureServices(IServiceCollection services)
{
Program.AddDataProtection(services);
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
if (env.IsProduction())
{
var dataProtectionProvider = app.ApplicationServices.GetService<IDataProtectionProvider>();
var protector = Program.CreateProtector(dataProtectionProvider);
var builder = new SqlConnectionStringBuilder();
builder.Password = protector.Unprotect(configuration["DbPassword"]);
...
}
}
就是这样!