Предыстория
Мне поступила задача по настройке CI. Было принято решение использовать трансформацию конфигурационных файлов и конфиденциальные данные хранить в зашифрованном виде. Зашифровать и расшифровать их можно с использованием Key Container.
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
Комментарии (12)
NYMEZIDE
04.08.2019 10:21+1для чего вы все это делаете? где будет хостится сервис?
1. если у вас — то зачем это все??? чтобы не украли? ну это по другому решается.
2. если у кого-то — то это вам не поможет, т.к. этот кто-то наверняка будет иметь физический доступ к машине.
Mikluho
04.08.2019 12:59Цель подобной защиты — уменьшение потенциального вреда от уязвимостей. Грубо говоря, если через дырку в другом софте на вашем сервере запустится вредоносный код, то ему будет существенно сложнее получить доступ к вашей БД, потому что строка подключения зашифрована, а для расшифровки надо залогиниться под нужной учёткой.
Это не гарантия и не панацея, но чем больше преград на пути потенциального злоумышленника — там меньше шансов, что он таки сумеет нанести существенный ущерб.x67
04.08.2019 15:42для расшифровки надо залогиниться под нужной учёткой
Но ведь эту задачу решает правильная настройка прав доступа.Mikluho
05.08.2019 00:10эту задачу решает правильная настройка прав доступа
тут ещё и защита от случайного взгляда…
Хотя лично я предпочитаю вместо много хитростей использовать встроенную аутентификацию при доступе к другим серверам или ресурсам, ведь гораздо проще сохранить секрет, если его не нужно нигде хранить.
elena864 Автор
05.08.2019 15:27Для того чтобы при автоматической заливке данные передавались в зашифрованном виде. Т.к. тут два выхода, либо вы правите конфиг руками на сервере, либо он идёт в пакете с заливкой.
NYMEZIDE
05.08.2019 15:40какие еще пакеты с заливкой? заливка нового кода, сборки — не должна зависеть от каких-то критически-важных конфигурационных данных.
все через Environment решается. Покажите DevOps'ам своим эту статью, пусть поржут.
Imbecile
Для .NET Core это работает?
elena864 Автор
На .net core поддержка шифрования с RsaProtectedConfigurationProvider есть.
Но в .net core ушли от web.config и теперь настройки хранятся в appsetttings.json.
Поэтому лучше использовать в core, то что microsoft предлагает Safe storage или Azure Key Vault
Вот тут можно почитать подробнее.
Imbecile
1. Safe Storage только для разработки. В Прод его нельзя.
2. Второй работает только при доступе сервера к Azure.
Оба мимо. :(