
Шифрование секций web.config ключом RSA: aspnet_regiis и свой контейнер ключей
Строка подключения с паролем в открытом web.config видна любому, кто получил доступ к файлам сайта. Шифрование секций RSA-ключом убирает пароль из открытого текста, а приложение читает настройки как раньше.
Почему пароль в web.config это проблема
Файл web.config IIS не отдаёт по HTTP, и многие считают, что этого достаточно. На практике конфигурация утекает другими путями: резервная копия сайта на общем диске, архив, отправленный подрядчику, исходники в системе контроля версий, доступ к серверу у администраторов соседних проектов. В каждом из этих случаев строка подключения с логином и паролем к базе читается глазами.
ASP.NET 2.0 добавил механизм защищённой конфигурации (Protected Configuration). Выбранная секция заменяется блоком EncryptedData, а при чтении через ConfigurationManager среда сама её расшифровывает. Код приложения менять не нужно: ConfigurationManager.ConnectionStrings возвращает ту же строку, что и до шифрования.
В комплекте два провайдера: DataProtectionConfigurationProvider на основе DPAPI и RsaProtectedConfigurationProvider на основе RSA. DPAPI привязан к конкретной машине, поэтому зашифрованный файл нельзя перенести на другой сервер. RSA-ключ можно экспортировать, и это главный аргумент в его пользу для веб-ферм и переноса между средами.
Шифруем секцию утилитой aspnet_regiis
Утилита aspnet_regiis.exe лежит в папке Framework нужной версии, например C:\Windows\Microsoft.NET\Framework\v2.0.50727. У неё два варианта команд: с ключом -pe работают по виртуальному пути приложения в IIS, с ключом -pef по физической папке. Второй вариант удобнее, если сайт ещё не опубликован или запущен на встроенном сервере разработки.
Без указания провайдера используется RsaProtectedConfigurationProvider и машинный контейнер ключей NetFrameworkConfigurationKey. Для одного сервера этого хватает. Расшифровка выполняется той же утилитой с ключами -pd и -pdf.
rem Переходим в папку .NET Framework 2.0 (3.5 использует ту же утилиту)
cd %WINDIR%\Microsoft.NET\Framework\v2.0.50727
rem Шифруем по виртуальному пути приложения в IIS
aspnet_regiis -pe "connectionStrings" -app "/MyShop"
rem То же по физической папке сайта
aspnet_regiis -pef "connectionStrings" "D:\Sites\MyShop"
aspnet_regiis -pef "appSettings" "D:\Sites\MyShop"
rem Обратная операция, если нужно отредактировать вручную
aspnet_regiis -pdf "connectionStrings" "D:\Sites\MyShop"Свой контейнер ключей для переноса между серверами
Стандартный контейнер NetFrameworkConfigurationKey создаётся на каждой машине свой, и ключ в нём помечен как неэкспортируемый. Поэтому для веб-фермы или для схемы «шифруем на сборочной машине, выкладываем на прод» нужен собственный контейнер с экспортируемым ключом.
Контейнер создаётся командой -pc с флагом -exp. Затем его регистрируют в web.config как отдельный провайдер в секции configProtectedData и указывают имя этого провайдера при шифровании ключом -prov. Файл с экспортированным ключом содержит закрытую часть, обращаться с ним нужно как с паролем: перенесли, импортировали, удалили.
rem 1. Создаём контейнер с экспортируемым ключом
aspnet_regiis -pc "MyShopKeys" -exp
rem 2. Шифруем секцию нашим провайдером (описан в web.config)
aspnet_regiis -pef "connectionStrings" "D:\Sites\MyShop" -prov "MyShopRsa"
rem 3. Экспортируем ключ вместе с закрытой частью
aspnet_regiis -px "MyShopKeys" "C:\secure\keys.xml" -pri
rem 4. На втором сервере импортируем ключ и удаляем файл
aspnet_regiis -pi "MyShopKeys" "C:\secure\keys.xml"
del C:\secure\keys.xml
rem 5. Даём учётной записи пула доступ на чтение ключа
aspnet_regiis -pa "MyShopKeys" "NT AUTHORITY\NETWORK SERVICE"Регистрация провайдера в web.config
Чтобы ASP.NET знал, каким ключом расшифровывать секцию, провайдер описывается в configProtectedData. Атрибут keyContainerName совпадает с именем контейнера из предыдущего шага, useMachineContainer говорит, что ключ лежит в машинном хранилище, а не в профиле пользователя.
Сама секция configProtectedData не шифруется, и это нормально: в ней нет секретов, только имя контейнера. Секретом является ключ в хранилище Windows, доступ к которому мы выдали командой -pa.
<configuration>
<configProtectedData>
<providers>
<add name="MyShopRsa"
type="System.Configuration.RsaProtectedConfigurationProvider, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
keyContainerName="MyShopKeys"
useMachineContainer="true" />
</providers>
</configProtectedData>
<!-- После шифрования секция выглядит так -->
<connectionStrings configProtectionProvider="MyShopRsa">
<EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element">
<!-- зашифрованный ключ сессии и данные в Base64 -->
</EncryptedData>
</connectionStrings>
</configuration>Шифрование из кода приложения
Иногда удобнее, чтобы приложение само шифровало свою конфигурацию при первом запуске или из служебной страницы администратора. Для этого есть класс WebConfigurationManager и свойство SectionInformation у каждой секции. Метод ProtectSection принимает имя провайдера, после чего конфигурацию нужно сохранить.
Учтите, что сохранение web.config перезапускает домен приложения: все сессии в режиме InProc будут потеряны. Делайте это при развёртывании, а не посреди рабочего дня. Кроме того, учётной записи пула нужны права на запись в файл, которые в нормальной конфигурации у неё отсутствуют, поэтому такой код обычно запускают из отдельной утилиты развёртывания.
Imports System.Configuration
Imports System.Web.Configuration
Public Module ConfigProtector
' Шифрует секцию, если она ещё открыта
Public Sub Protect(ByVal appPath As String, ByVal sectionName As String)
Dim cfg As Configuration = WebConfigurationManager.OpenWebConfiguration(appPath)
Dim section As ConfigurationSection = cfg.GetSection(sectionName)
If section Is Nothing Then Exit Sub
If Not section.SectionInformation.IsProtected Then
section.SectionInformation.ProtectSection("MyShopRsa")
section.SectionInformation.ForceSave = True
cfg.Save(ConfigurationSaveMode.Modified) ' перезапустит приложение
End If
End Sub
End Module
' Вызов: ConfigProtector.Protect("/MyShop", "connectionStrings")Что шифровать и где обычно ошибаются
Шифровать имеет смысл только секции с секретами. Защищённую секцию нельзя быстро поправить в блокноте, а каждая расшифровка стоит немного процессорного времени при старте приложения. Типичный набор выглядит так:
- connectionStrings: пароли к базам данных;
- appSettings: ключи внешних API, пароли почтового ящика, если вы храните их там;
- system.net/mailSettings/smtp: учётные данные SMTP-сервера;
- identity с атрибутами userName и password, если используется имперсонация.
Диагностика ошибок расшифровки
Самая частая ошибка после переноса звучит как «Failed to decrypt using provider. Error message from the provider: The RSA key container could not be opened». Она означает, что ключ на сервере есть, но у учётной записи пула нет прав на его чтение, либо ключа нет совсем. Проверьте, что импорт выполнен, и повторите команду -pa для нужной учётной записи. В IIS 7 с пулами ApplicationPoolIdentity это учётная запись вида IIS APPPOOL\ИмяПула.
Вторая типичная ситуация: секцию зашифровали машинным контейнером по умолчанию на рабочей станции разработчика и выложили файл как есть. На сервере такого ключа нет, и сайт падает при первом обращении к строке подключения. Правило простое: либо шифруете на целевом сервере, либо используете собственный экспортируемый контейнер.
Как это делают сегодня
Вопросы и ответы
Можно ли расшифровать секцию на другом компьютере, если украли только web.config?
Нет. Без закрытого RSA-ключа из хранилища Windows секция не расшифровывается. Поэтому файл экспорта ключа нельзя оставлять рядом с сайтом или в архиве с исходниками.
Нужно ли менять код, который читает ConfigurationManager.ConnectionStrings?
Не нужно. Расшифровка выполняется прозрачно при чтении секции, приложение получает обычную строку.
Как отредактировать зашифрованную секцию?
Расшифруйте её командой aspnet_regiis -pdf, внесите правки и снова зашифруйте. Либо используйте IIS Manager, который умеет работать с защищёнными строками подключения.