Шифрование секций web.config ключом RSA: aspnet_regiis и свой контейнер ключей

РазделASP.NETУровеньСредний Чтение9 минОпубликовано ТегиASP.NETБезопасностьweb.config

Строка подключения с паролем в открытом web.config видна любому, кто получил доступ к файлам сайта. Шифрование секций RSA-ключом убирает пароль из открытого текста, а приложение читает настройки как раньше.

Главное. Защищённая конфигурация убирает пароли из открытого текста 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.

encrypt.cmdКомандная строка
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. Файл с экспортированным ключом содержит закрытую часть, обращаться с ним нужно как с паролем: перенесли, импортировали, удалили.

keys.cmdКомандная строка
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.

web.configXML
<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 будут потеряны. Делайте это при развёртывании, а не посреди рабочего дня. Кроме того, учётной записи пула нужны права на запись в файл, которые в нормальной конфигурации у неё отсутствуют, поэтому такой код обычно запускают из отдельной утилиты развёртывания.

ConfigProtector.vbVB.NET
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, если используется имперсонация.
Не шифруются секции, которые IIS читает раньше ASP.NET: например system.webServer и processModel. Для них protected configuration не работает.

Диагностика ошибок расшифровки

Самая частая ошибка после переноса звучит как «Failed to decrypt using provider. Error message from the provider: The RSA key container could not be opened». Она означает, что ключ на сервере есть, но у учётной записи пула нет прав на его чтение, либо ключа нет совсем. Проверьте, что импорт выполнен, и повторите команду -pa для нужной учётной записи. В IIS 7 с пулами ApplicationPoolIdentity это учётная запись вида IIS APPPOOL\ИмяПула.

Вторая типичная ситуация: секцию зашифровали машинным контейнером по умолчанию на рабочей станции разработчика и выложили файл как есть. На сервере такого ключа нет, и сайт падает при первом обращении к строке подключения. Правило простое: либо шифруете на целевом сервере, либо используете собственный экспортируемый контейнер.

Как это делают сегодня

В ASP.NET Core файла web.config с секретами больше нет: настройки собираются из appsettings.json, переменных окружения и защищённых хранилищ. Во время разработки пароли держат в User Secrets, а на сервере в Azure Key Vault, HashiCorp Vault или переменных окружения контейнера. Для своих данных, которые нужно шифровать в самом приложении, используется Data Protection API с ротацией ключей.

Вопросы и ответы

Можно ли расшифровать секцию на другом компьютере, если украли только web.config?

Нет. Без закрытого RSA-ключа из хранилища Windows секция не расшифровывается. Поэтому файл экспорта ключа нельзя оставлять рядом с сайтом или в архиве с исходниками.

Нужно ли менять код, который читает ConfigurationManager.ConnectionStrings?

Не нужно. Расшифровка выполняется прозрачно при чтении секции, приложение получает обычную строку.

Как отредактировать зашифрованную секцию?

Расшифруйте её командой aspnet_regiis -pdf, внесите правки и снова зашифруйте. Либо используйте IIS Manager, который умеет работать с защищёнными строками подключения.

Читайте также