在ASP.NET Core中存储生产秘密

Nic*_*asR 22 asp.net-core

我试图找出哪个最佳存储ASP.NET核心应用程序的应用程序生产机密.有两个类似的问题 我应该在哪里存储我的asp.net-5应用程序的生产环境的连接字符串? 以及 如何将ASP.NET Core UserSecrets部署到生产中 ,它们都建议使用环境变量.

我的问题是我想运行具有不同数据库和不同数据库凭据的Web应用程序的多个实例.所以应该有一些包含秘密的每个实例配置.

如何以安全的方式实现这一目标?

请注意,应用程序应该能够自我托管并在IIS下托管!(稍后我们还计划在Linux上运行它,如果这对问题有任何重要性)

更新

这个问题不是试图在生产中使用ASP.NET用户机密!UserSecrets被排除用于生产.

Tse*_*eng 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();
    }
Run Code Online (Sandbox Code Playgroud)

这样,您可以从中加载特定的东西,appsettings.production.json并且仍然可以通过环境变量覆盖它.

  • @Tseng 如果您不将 appsettings.{whatever}.json 添加到存储库,那么您如何期望任何 CI/CD 管道在部署应用程序时了解每个环境的特定设置。那根本行不通。appsettings 文件不是存放秘密的地方,而是存放设置的地方。它们必须添加到 Git(或您使用的其他任何东西)中才能被您的应用程序使用。 (3认同)
  • 我在开发期间意识到用户机密的问题。但这不是这里的问题,我只是谈论生产时间!环境设置文件没有帮助,因为它对于生产中的每个实例仍然是相同的,并且覆盖环境变量对于所有实例仍然是相同的!我现在对 Docker 还不太了解,但也许这个想法可以有所帮助。 (2认同)
  • @NicolasR:不一定。每次安装都会有一个 application.production.json。如果您的应用程序位于 3 个不同的文件夹中,您可以将每个文件夹的“applciation.production.json”设置为所需的值。您只需要确保从源代码管理中排除“applciation.production.json”,因为它现在包含敏感数据。 (2认同)
  • @NicolasR:嗯,据我所知,没有像传统ASP.NET应用程序那样的"安全字符串",你可以将加密的字符串放入web.config并在运行时解密,所以除非你写你自己的解密方法(它会读取加密的字符串并通过私钥/公钥解密),没有办法存储它.但那是如此巨大的影响.您的应用需要访问私钥才能解密字符串,并且需要此私钥的密码,因此您仍然需要通过应用访问它 (2认同)
  • 如果有人入侵您的服务器,他还可以访问您的私钥和密码以及您的应用程序(如果密码是内部硬编码的话).编译为CIL的C#代码完全没有安全性.因此,有权访问您的服务器的人,无论如何都可以通过这种方式访问​​您的密码.即使他们能够获得密码,您的数据库也不应该面向互联网,只允许来自您的网络/ IP /本地的连接.安全性这比加密连接字符串提供了更多的安全性(因为你必须解密它们) (2认同)

Wei*_*Luo 6

没有Azure Vault或任何第三方提供者,有一种简单的方法可以安全地在生产中存储应用程序秘密:

  1. 通过运行应用程序的-config开关输入应​​用程序密码,例如: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);
        }
    }
Run Code Online (Sandbox Code Playgroud)
  1. 在Startup.cs中,读取并解密机密:
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"]);
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

而已!

  • 您的代码中仍然有“protector.Protect("DbPassword", ReadPasswordFromConsole())”。你又回到了原点。黑客可以从您的程序二进制文件中提取密码。 (4认同)
  • 这是否意味着每次应用程序启动时都需要手动输入密码?如果托管的 IIS 在半夜自动重新启动,会发生什么情况,该应用程序将不会运行,直到用户干预。 (3认同)
  • 我真正不明白的是为什么人们不使用可信连接字符串。在本地系统或 Active Directory 上创建单独的用户帐户。将应用程序池配置为在该帐户下运行。将数据库配置为使用混合模式或 Windows 身份验证。将帐户添加到数据库,以便它可以连接并定义帐户对数据库的权限。将连接字符串更改为其中不包含用户名或密码的受信任连接字符串。我已经为Oracle和SQL Server数据库做过了。 (3认同)
  • 当然不是,你只需要使用 `-config` 开关运行你的程序一次。 (2认同)