...

суббота, 3 августа 2019 г.

[Из песочницы] Шифрование конфигурационных файлов

Предыстория


Мне поступила задача по настройке CI. Было принято решение использовать трансформацию конфигурационных файлов и конфиденциальные данные хранить в зашифрованном виде.
Изучив документацию по шифрованию, вот что было сделано.

Key Container


В каждой ОС Windows есть наборы сгенерированных ключей. Ключ генерируется либо на учетную запись, либо на машину. Ключи сгенерированные на машину можно посмотреть по этому пути C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys. Сюда и отправиться ключ который мы создадим далее.

Создание ключа


Запускаем cmd от администратора и переключаемся в директорию с aspnet_regiis, у меня это C:\Windows\Microsoft.NET\Framework64\v4.0.30319

Выполняем команду

aspnet_regiis -pc "TestKey" -exp

exp — добавляется чтобы можно было экспортировать ключ в дальнейшем
TestKey — название нашего Key Container

Экспорт ключа


Команда
aspnet_regiis -px "TestKey" С:\TestKey.xml -pri

TestKey — название Key Container
С:\TestKey.xml — путь куда будет экспортирован файл
pri — добавить в экспорт приватный ключ

Импорт ключа


Команда
aspnet_regiis -pi "TestKey" С:\TestKey.xml

TestKey — название Key Container
С:\TestKey.xml — путь откуда будет экспортирован файл

Настройка прав


Чтобы ваше приложение или IIS могли работать с key container нужно настроить им права.

Делается это командой

aspnet_regiis -pa  "TestKey" "NT AUTHORITY\NETWORK SERVICE"

TestKey — название Key Container
NT AUTHORITY\NETWORK SERVICE — кому будет выдан доступ до ключа

По умолчанию в IIS стоит ApplicationPoolIdentity для пула.

В документации Microsoft (см. ссылка 2) ApplicationPoolIdentity описан как:

  • ApplicationPoolIdentity: When a new application pool is created, IIS creates a virtual account that has the name of the new application pool and that runs the application pool worker process under this account. This is also a least-privileged account.

Поэтому чтобы IIS смог расшифровать конфиг, обязательно должна быть настроена Identity у пула на учетную запись или можно выбрать «NETWORK SERVICE» и для него дать права.

Добавление секции в config

<configProtectedData defaultProvider="RsaProtectedConfigurationProvider">
<providers>
<add name="CustomRsaProtectedConfigurationProvider" 
type="System.Configuration.RsaProtectedConfigurationProvider,System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" 
description="Uses RsaCryptoServiceProvider to encrypt and decrypt" 
keyContainerName="TestKey" 
cspProviderName="" 
useMachineContainer="true" 
useOAEP="false"/>
</providers>
</configProtectedData>


Также key container и RsaProtectedConfigurationProvider определены глобально в файлах

C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\machine.config, C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config

<configProtectedData defaultProvider="RsaProtectedConfigurationProvider">
    <providers>
        <add name="RsaProtectedConfigurationProvider" type="System.Configuration.RsaProtectedConfigurationProvider,System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" description="Uses RsaCryptoServiceProvider to encrypt and decrypt" keyContainerName="NetFrameworkConfigurationKey" cspProviderName="" useMachineContainer="true" useOAEP="false"/>
 
        <add name="DataProtectionConfigurationProvider" type="System.Configuration.DpapiProtectedConfigurationProvider,System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" description="Uses CryptProtectData and CryptUnProtectData Windows APIs to encrypt and decrypt" useMachineProtection="true" keyEntropy=""/>
    </providers>
</configProtectedData>


Шифрование


Само шифрование можно сделать тремя способами

Шифрование через командную строку

aspnet_regiis.exe -pef connectionStrings С:\Site  -prov "CustomRsaProtectedConfigurationProvider"

С:\Site — путь до файла с конфигом.

CustomRsaProtectedConfigurationProvider — наш провайдер указанный в конфиге с названием key container.

Шифрование через написанное приложение

private static string provider = "CustomRsaProtectedConfigurationProvider";
 
public static void ProtectConfiguration()
{
    Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
 
    ConfigurationSection connStrings = config.ConnectionStrings;
 
    if (connStrings != null && !connStrings.SectionInformation.IsProtected && !connStrings.ElementInformation.IsLocked)
    {
        connStrings.SectionInformation.ProtectSection(provider);
 
        connStrings.SectionInformation.ForceSave = true;
        config.Save(ConfigurationSaveMode.Full);
    }
}
 
public static void UnProtectConfiguration(string path)
{
    Configuration config = ConfigurationManager.OpenExeConfiguration(path);
 
    ConfigurationSection connStrings = config.ConnectionStrings;
 
    if (connStrings != null && connStrings.SectionInformation.IsProtected && !connStrings.ElementInformation.IsLocked)
    {
        connStrings.SectionInformation.UnprotectSection();
    }
}


Велосипед


Когда есть трансформация файлов и нужно зашифровать секции отдельно от всего конфига, то подойдет только самописный вариант. Мы создаем экземпляр класса RsaProtectedConfigurationProvider, берем узел из xml и шифруем его отдельно, затем заменяем в исходном xml узел на наш зашифрованный и сохраняем результат.
public void Protect(string filePath, string sectionName = null)
{
    XmlDocument xmlDocument = new XmlDocument { PreserveWhitespace = true };
    xmlDocument.Load(filePath);

    if (xmlDocument.DocumentElement == null)
    {
        throw new InvalidXmlException($"Invalid Xml document");
    }

    sectionName = !string.IsNullOrEmpty(sectionName) ? sectionName : xmlDocument.DocumentElement.Name;

    var xmlElement = xmlDocument.GetElementsByTagName(sectionName)[0] as XmlElement;

    var config = new NameValueCollection
                     {
                         { "keyContainerName", _settings.KeyContainerName },
                         { "useMachineContainer",  _settings.UseMachineContainer ? "true" : "false" }
                     };
    var rsaProvider = new RsaProtectedConfigurationProvider();
    rsaProvider.Initialize(_encryptionProviderSettings.ProviderName, config);
    var encryptedData = rsaProvider.Encrypt(xmlElement);

    encryptedData = xmlDocument.ImportNode(encryptedData, true);

    var createdXmlElement = xmlDocument.CreateElement(sectionName);
    var xmlAttribute = xmlDocument.CreateAttribute("configProtectionProvider");
    xmlAttribute.Value = _encryptionProviderSettings.ProviderName;
    createdXmlElement.Attributes.Append(xmlAttribute);
    createdXmlElement.AppendChild(encryptedData);

    if (createdXmlElement.ParentNode == null
        || createdXmlElement.ParentNode.NodeType == XmlNodeType.Document
        || xmlDocument.DocumentElement == null)
    {
        XmlDocument docNew = new XmlDocument
                                 {
                                     InnerXml = createdXmlElement.OuterXml
                                 };
        docNew.Save(filePath);
    }
    else
    {
        xmlDocument.DocumentElement.ReplaceChild(createdXmlElement, xmlElement);
        xmlDocument.Save(filePath);
    }
}


Ссылки


1. docs.microsoft.com/en-us/previous-versions/53tyfkaw
2. support.microsoft.com/en-za/help/4466942/understanding-identities-in-iis

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий