Различные ключи шифрования в зависимости от того, как запускается приложение ASP. NET Core? IDataProtector не может снять защиту с определенных ключей - PullRequest
0 голосов
/ 02 марта 2020

Я использую защиту данных для защиты секретов своего приложения в отдельном файле json. Каждому моему приложению я назначаю идентификатор Guid, который я использую в качестве appName для защиты данных. Каждое приложение имеет параметр cli для запуска сбора параметров, затем он передает объект параметра в этот метод общей библиотеки, который создает поставщик защиты данных, а затем защищает каждое свойство собранных параметров для шифрования

    public static void WriteSecrets<T>(T secrets, string appName, string secretsFileName = "secrets.json") where T: class
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddDataProtection().SetApplicationName(appName);
        var services = serviceCollection.BuildServiceProvider();
        var dataProtectionProvider = services.GetService<IDataProtectionProvider>();
        var protector = dataProtectionProvider.CreateProtector(appName);
        var props = secrets.BasicProperties();
        foreach (var prop in props)
        {
            var plainText = secrets.ObjectStringValue(prop);
            if (!string.IsNullOrEmpty(plainText))
                secrets.SetObjectValue(prop, protector.Protect(plainText));
        }
        var json = JsonSerializer.Serialize(secrets);
        var path = Path.Combine(CurrentDirectory, secretsFileName);
        File.WriteAllText(path, json);
        Console.WriteLine($"Writing secret to '{path}' completed successfully.");
    }

I затем используйте IO C, чтобы получить IDataProtectionProvider в нужных ему классах (например, мой класс, который обрабатывает генерацию токена JWT). Кажется, что IDataProtectionProvider регистрируется по умолчанию, я могу получить его через IApplicationBuilder.ApplicationServices.GetService в методе Configure моего Startup.cs.

Этот подход прекрасно работал для двух ASP. NET Core 3.1 Приложения, которые я создал.

Теперь я столкнулся с проблемой. Приложение, на которое я сейчас нацеливаюсь, дважды использует компоновщик хостов приложения c. Это сервисное приложение, размещающее веб-API. Приложение запускается с помощью средства запуска * generic c, созданного на основе узла службы generi c.

Это мой подход к размещению служб

public class ServiceLauncher<T> where T : class, IGenericService
{

    private readonly IHostBuilder builder;
    private readonly T service;

    public ServiceLauncher(string[] args)
    {
        builder = CreateHostBuilder(args);
    }

    public ServiceLauncher(T service, string[] args): this(args)
    {
        this.service = service;
    }

    public void StartService()
    {
        if (service != null) // a service was already provided, register it as singleton
            builder.ConfigureServices((hostContext, services) => 
            {
                services.AddSingleton<IGenericService>(service);
            });
        else // register 
            builder.ConfigureServices((hostContext, services) =>
            {
                services.AddSingleton<IGenericService, T>();
            });
        var host = builder.Build();
        host.Run();
    }

    private static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseWindowsService() // enables running as a windows service
            .ConfigureLogging(loggerFactory =>
            {
                loggerFactory.AddFile(options => // for file system logging
                {
                    options.LogDirectory = @"c:\temp\";
                    options.FileName = "wrappedservice";
                });
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<GenericServiceWorker>();
                services.Configure<HostOptions>(options => // sett custom shutdown timeout
                {
                    options.ShutdownTimeout = TimeSpan.FromSeconds(30);
                });
            });

}

, где GenericServiceWorker - это BackgroundService, который переопределяет StartAsync / StopAsync / ExecuteAsyn c для запуска приложения / остановки / работы. Затем само приложение запускается следующим образом:

var service = new PresenceManager()
var launcher = new ServiceLauncher<PresenceManager>(service, args);
launcher.StartService();

Когда запускается BackgroundService, запускается хостинг моего веб-API, поэтому создается новый хост и настраивается все так, как обычно, когда у меня есть обычный не обслуживаемый хостинг ASP. NET Базовое приложение. Сборка хоста довольно стандартна и выглядит следующим образом:

var builder = Host.CreateDefaultBuilder()
.UseServiceProviderFactory(new DryIocServiceProviderFactory(container)) // register to use DryIoc
.ConfigureAppConfiguration((hostingContext, config) =>
{
    config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
    config.AddJsonFile("secrets.json", optional: true, reloadOnChange: true);
})
.ConfigureWebHostDefaults(webBuilder =>
{
    webBuilder.CaptureStartupErrors(true);
    webBuilder.UseStartup<Startup>();
    webBuilder.UseUrls(apiUrl);
});
host = builder.Build();
await host.StartAsync(ServerShutDownSource.Token).ConfigureAwait(false);

Обратите внимание, что это точно такой же способ, которым я запускаю свои не обслуживаемые хост-приложения ASP. NET ядра, а также практически тот же самый Автозагрузка. cs (различия относятся к ведению журнала и IO C, так как приложение с оболочкой использует DryIo c). Теперь мое приложение запускается, а затем я выполняю первую операцию, которая требует защиты данных и снятия защиты с одной из строк в секретах. json.

И теперь средство защиты данных выдает:

CryptographicException при вызове Unprotect говорит мне 'Полезная нагрузка была недействительной' .

Глядя на зашифрованную строку, она совпадает с тем, что находится в secrets.json, и когда я вызываю метод в моей вспомогательной библиотеке lib который выполнил защиту от CLI, он может правильно расшифровать ту же самую строку. имя приложения, которое я использую для создания своего DataProtector, такое же, как и учетная запись, под которой выполняется все.

Таким образом, я озадачен - почему на моем хосте службы не работает сценарий хоста web api, почему не работает ?

@ edit: в качестве эксперимента я переписал код, который записывает секреты в secrets.json - теперь я инициализирую хост Web API, в Startup.Configure, получаю экземпляр IDataProtectionProvider и устанавливаю его как единичный экземпляр для DryIo c. И тогда я защищаю свои секреты с этим IDataProtectionProvider. Сгенерированные зашифрованные строки отличаются от ранее, и теперь я могу расшифровать строки в моих контроллерах (которые вводят IDataProtectionProvider).

Так что я был на правильном пути, но почему я получаю разные зашифрованные строки в зависимости от того, откуда я получаю IDataProtectionProvider?

...