МНОГО ДЕТАЛЕЙОчень удобно, когда благодаря правильно выбранным умолчаниям все работает само и «из коробки» и не нужно ничего настраивать. Эта история о том, что выбранные умолчания должны быть работоспособными всегда, в противном случае есть риск непредвиденного отказа после многих лет беспроблемной работы.

Мы столкнулись с недокументированным поведением Windows Server в web-ролях Microsoft Azure, которое долгие годы маскировало неправильную настройку нашего сервиса Cloud OCR SDK, пока в один не самый прекрасный момент не привело к серьезным проблемам у отдельных пользователей.

В феврале 2016 года – к этому времени сервис работал уже несколько лет – отдельные пользователи стали сообщать о проблемах при попытке установить защищенное соединение. Программы этих пользователей выдавали сообщения о каких-то проблемах с сертификатом сервиса. Поскольку «до этого работало, а теперь сломалось», первым делом пользователи решили, что у сертификата истек срок действия, и вполне предсказуемо были Очень НедовольныTM. На самом деле срок действия истекал только несколько месяцев спустя.

Попытки воспроизвести проблему чаще всего были неудачными – даже на точно тех платформах, которые указывали пользователи. При помощи сторонних средств проверки (один и два) иногда удавалось получить сообщение о неправильной установке промежуточных сертификатов.

С этого момента нужны технические подробности. Выпуск сертификатов осуществляют специальные организации – сертификационные центры, которым все доверяют. Каждый выпущенный сертификат подписан цифровой подписью, которую программы-клиенты проверяют.

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

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

Для проверки подписей программам-клиентам достаточно сертификатов без закрытых ключей – закрытые ключи хранятся в секрете сертификационным центром (корневые и промежуточные сертификаты) и владельцем сервиса (закрытый ключ сертификата сервиса).

У программы-клиента есть несколько способов получить промежуточные сертификаты.

Первый вариант – промежуточные сертификаты могут быть заренее установлены на машине, где работает программа. Это неудобно, рассчитывать, что пользователи будут устанавливать промежуточные сертификаты, не стоит.

Второй вариант – программа может попытаться скачать их по сети с серверов сертификационного центра. Это удобно, но поддерживается не всеми реализациями клиентов SSL. Также это ненадежно – нужно, чтобы в момент установления соединения был доступ также и к серверам сертификационного центра. Этот способ замедляет установление первого соединения.

Третий способ – самый надежный и универсальный. Сервер сам отправляет клиенту все промежуточные сертификаты одновременно с отправкой своего сертификата в начале установления защищенного соединения. Примерно: «вот мой сертификат, он подписан вот этими сертификатами, из которых последний подписан корневым, который должен у вас, ценнейшая программа-клиент, быть помечен как заслуживающий доверия, извольте все подписи проверить, убедиться и давайте уже соединение установим».

Сторонние средства проверки иногда показывали, что наш сервис отдает промежуточный сертификат при установлении соединения, а иногда – что не отдает. После проверки оказалось, что, следуя доступным примерам, мы неправильно настроили процесс развертывания сервиса.

Вот так выглядит типичное определение сервиса с web-ролью:
    <?xml version="1.0" encoding="utf-8"?>
    <ServiceDefinition name="CoolCloudService"
                   xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition"
                   schemaVersion="2014-06.2.4"
    >
   <WebRole name="CoolRole">
    <Sites>
      <Site name="Web" >
        <Bindings>
          <Binding name="HttpIn" endpointName="HttpIn" />
          <Binding name="HttpsIn" endpointName="HttpsIn" />
        </Bindings>
      </Site>
    </Sites>
    <Endpoints>
      <InputEndpoint name="HttpIn" protocol="http" port="80" />
      <InputEndpoint name="HttpsIn" protocol="https" port="443" certificate="ProductionCert"/>
    </Endpoints>
    <Certificates>
      <!--НЕПРАВИЛЬНО!!! Не надо так!!!-->
      <Certificate name="ProductionCert" storeLocation="LocalMachine" storeName="My"/>
    </Certificates>
  </WebRole>
  </ServiceDefinition>


Обычно внутри элемента Certificates указывают только сертификат сервиса. Это неправильно. Правильно там же указать и все промежуточные сертификаты в цепочке. Не имеет значения, что они не упоминаются в разделе Endpoints.

Так правильно:
     <Certificates>
     <!--Так правильно! -->
      <Certificate name="IntermediateForProductionCert" storeLocation="LocalMachine" storeName="CA"/>
       <!—Если в цепочке более одного промежуточного сертификата, перечислить нужно все-->
      <Certificate name="ProductionCert" storeLocation="LocalMachine" storeName="My"/>
    </Certificates>

Перечисление всех промежуточных сертификатов приводит к их установке в хранилища на каждом экземпляре роли во время инициализации экземпляра. После этого IIS сможет отправлять промежуточные сертификаты программам-клиентам при установлении защищенного соединения.

WIN? Нет, не так быстро.

После исправления настроек и публикации изменений мы связались с пользователем, который удачно был в близком часовом поясе, и он ответил, что нет, не помогло, ВСЕ ПЛОХО, ничего не работает.

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

Сервис временно перевели на предыдущий образ… и проблема была решена.

WIN? Нет, это даже близко не WIN.

Во-первых, было непонятно, какое из двух изменений решило проблему. Оставаться вечно на более раннем образе нельзя – через пару месяцев инфраструктура Azure принудительно переведет сервис на более новый образ, и тогда проблемы могут повториться. Вариант перевести сервис на более новый образ операционной системы и проверить на пользователях нам как-то не подошел.

Во-вторых, было непонятно, почему проблема повторилась именно в это время, а не раньше – настройки не менялись, но «раньше работало, а потом сломалось».
На тестовом сервисе с неправильными настройками «до» – с указанием только одного сертификата – все сторонние средства диагностики показывали, что сертификаты установлены правильно, возвращаются сервисом в ответ на запрос программы-клиента. Добавленный в тестовый сервис код просмотра установленных сертификатов при этом показывал, что промежуточного сертификата в хранилищах нет.

Сертификата в хранилищах нет, но сервис отдает его программам пользователям. Телепортация, видимо. Обычное дело.

Еще у нас были сообщения пользователей. Среди них было примерно такое описание: «вот кусок кода на PHP, мы выполняем его в цикле, он когда-то отрабатывает успешно, а когда-то – со сбоем, по мере того, как эта ваша проблема ухудшалась, он все чаще отрабатывал со сбоем».

Еще у нас есть код, который периодически проверяет срок действия нашего сертификата и на всякий случай проверяет его цепочку доверия методом X509Chain.Build(). Раньше он отрабатывал нормально, а в период времени, когда пользователи сталкивались с проблемой, — этот метод иногда не отрабатывал, выдавая такой набор сообщений:
  • PartialChain A certificate chain could not be built to a trusted root authority.
  • RevocationStatusUnknown The revocation function was unable to check revocation for the certificate.
  • OfflineRevocation The revocation function was unable to check revocation because the revocation server was offline.

Подозрительно похоже на невозможность обратиться к серверам сертификационного центра.

А что если экземпляр роли умеет сам получать у сертификационного центра недостающие промежуточные сертификаты и заботливо припрятывать их, чтобы IIS мог отдавать их программам-клиентам? Это было бы КРАЙНЕ НЕОЖИДАННО, высказывать такое предположение без весомых доказательств очень легкомысленно, с таким же успехом можно высказать предположение, что Windows ест котят.

Нужно было проверить. Попытки настроить межсетевой экран или подпилить разрешение имен правкой файла hosts на экземпляре роли результата не дали. Это было ожидаемо – до момента, когда впервые появляется возможность выполнить эти действия, экземпляр роли работает несколько минут, по окончании которых может быть слишком поздно – сертификат будет скачан и заботливо припрятан.

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

Нам хватило «виртуальной сети» (virtual network), в которой мы создали подсеть (subnet) с «группой сетевой безопасности» (network security group), далее в настройках сервиса добавили элемент NetworkConfiguration, чтобы сервис публиковался в эту виртуальную сеть. Это было несложно.

В «группе сетевой безопасности» настраиваются ограничения доступа к сети. Там добавили правило, запрещающее исходящие запросы к диапазону адресов, по которым размещена инфраструктура сертификационного центра.

Если после этого опубликовать тестовый сервис, заявленная пользователями проблема начинает воспроизводиться независимо от свежести образа операционной системы. IIS перестает отдавать промежуточный сертификат. Если в «группе безопасности» изменить правило так, чтобы запросы не ограничивались, и опубликовать сервис еще раз, проблема перестает воспроизводиться.

После основательной проверки этого черного ящика мы установили, что попытка получить промежуточный сертификат производится при изначальной инициализации экземпляра роли, при ее перезагрузке, при ее повторной инициализации с образа операционной системы и при повторной публикации пакета сервиса. Перезапуск пула приложений в IIS не приводит к попытке получения недостающего сертификата. Таким образом, попытка получения промежуточного сертификата связана с моментом развертывания сайта в IIS – предположительно, она происходит в момент установки сертификата сайта. В этой статье (How Certificate Revocation Works) упоминается некий дисковый кеш сертификатов CryptoAPI. CryptoAPI – часть Windows Server.

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

Нужно добавить сюда масштабирование и балансировщик нагрузки, распределяющий входящие запросы между экземплярами роли. Разные экземпляры роли могут запускаться в разное время и потому при разной доступности инфраструктуры сертификационного центра, в результате одни экземпляры оказываются в лучшей форме, чем другие. Балансировщик нагрузки по своему усмотрению отправляет разные запросы в разные экземпляры, поэтому разные запросы могут приводить к разному набору сертификатов в ответе. Малоактивные пользователи видят, что «то работает, то не работает», а очень активные – что некоторая доля запросов завершается успешно, а остальные – со сбоем.

Головоломка сложилась. Вот теперь WIN.

Определенно, это не самое удачное поведение по умолчанию.

Теперь у нас есть код, который при проверке нашего сертификата заодно проверяет, что все сертификаты в цепочке лежат в соответствующих хранилищах экземпляра роли. Благодаря силе свежих пул-запросов примеры в документации к Microsoft Azure скоро исправят.

И если у вас есть сервис с web-ролями, очень может быть, что он все еще настроен неправильно.

Дмитрий Мещеряков,
департамент продуктов для разработчиков

Комментарии (10)


  1. zedalert
    07.04.2016 12:19
    +2

    Чем больше администрируешь виндовые сервера, тем больше замечаешь такие не очевидные и глупые вещи. Особенно доставляет, когда поиск ошибки приводит к описанию в KB вроде «появление этой ошибки нормальное явление, не обращайте внимание на это». Или когда зачем-то информационное сообщение в системный журнал записывается как ошибка.


    1. EndUser
      07.04.2016 14:26
      +2

      Напомнило историю о том, что электронные письма не уходили далее 600 миль.
      А там была не винда.


      1. Bozaro
        07.04.2016 15:19
        +3

        К у меня Windows основная претензия не в том, что там происходят не всегда понятные штуки, а в том, что в ситуации когда что-то пошло не так, не понятно, куда копать.
        Т.е. вроде все нормально, но ничего не работает:

        • в логах тишина,
        • диагностику добавлять некуда;
        • исходный код подсмотреть нельзя.


        1. centur
          07.04.2016 15:56
          +1

          Там у большого количества утилит есть выдача в разные Trace и TraceSource, но собирать их надо заранее да и разбираться в них то еще удовольствие =(.
          Но в целом — это все обычно есть, просто нет сильно удобных инструментов сбора и анализа.


    1. VitalKoshalew
      08.04.2016 17:00
      -2

      В данном случае — проблема не в Windows Server. У них нет своего Windows Server. Есть сервис на его базе, который кто-то как-то настраивает на основе пожеланий заказчика, записанных в некоей мета-конфигурации. Сервер сам куда-то лезет в интернет, сам что-то откуда-то скачивает. Такое решение хромает на обе ноги в плане безопасности, а уж стабильности от такого самообновляющегося решения ожидать совсем странно.
      В остальном: тривиальная ошибка конфигурации, на которую открытым текстом указали все возможные диагностические системы. Вместо того, чтобы первым делом проверить единственное, над чем в такой системе есть контроль — конфигурационный файл, стали проверять текущее состояние отдельных самообновляющихся экземпляров.
      Исправив, наконец, конфигурацию, я так понимаю (этот момент не совсем понятен из текста), забыли, что необходимо перевыпустить все экземпляры, и с испугу откатили обновления.

      Никаких диагностических сообщений в журналах в такой ситуации быть не могло — на сервер установили действительный сертификат. Какова цепочка доверия между корневыми сертификатами, установленными у каждого отдельно взятого *клиента* и этим сертификатом сервер знать не может. Может предположить на основе своих корневых сертификатов (которые, например, могут вообще быть все удалены в некоторых конфигурациях), но не более того.


  1. centur
    07.04.2016 16:06

    Дим, а поделиться этим легендарным кодом который теперь проверяет «все правильно и надежно» слабо? А то ты его в статье упоминаешь несколько раз, так что вполне ожидаемо что в конце будет — «А вот вам правильный надежный кода». А там только «Теперь у нас есть код...»
    Кстати так и непонятно, почему решение с установкой всех промежуточных сертификатов вручную не работает — по логике все эти загрузки и крипто-кэши дергаются только тогда, когда нету сертификата с определенным thumbprint в хранилищах. А если ты его устанавливаешь при развертывании роли — какие могут быть проблемы? Можешь развернуто рассказать что там в деталях ломается, если вы разобрались так глубоко.

    По логике — сертификаты из csdef устанавливаются и развертываются до развертывания кода на IIS, иначе он бы ругался на попытку binding к неустановленному сертификату — зачем бы IIS вообще ходить к центру сертификации если все есть локально ( если только не за проверкой, не отозваны ли какие-либо сертификаты в цепочке.


    1. DmitryMe
      07.04.2016 16:40
      +1

      Код проверки, что сертификаты правильно разложены по хранилищам, вызывает X509Chain.Build(), затем проходит по коллекции X509Chain.ChainElements и проверяет, что каждый из сертификатов лежит в соответствующем хранилище (вызывает X509Store.Open(), затем проверяет, что сертификат присутствует в X509Store.Certificates, затем вызывает X509Store.Close()). Я не стал приводить этот код, чтобы не перегружать пост.

      «Решение с установкой вручную» очень хорошо работает, нужно просто перечислить промежуточные сертификаты в определении сервиса.


  1. hdfan2
    07.04.2016 16:27
    +2

    Большой вам респект и уважуха за то, что не бросили расследование на первом же WINе. Очень хорошо представляю себе, какой был соблазн. Получился настоящий детектив для гиков, где до самого конца неясно, кто уби… в смысле где грабли зарыты.


  1. Mixim333
    08.04.2016 16:47

    Я правильно понял, что если сертификат CertN нам выдал центр №N, которому, в свою очередь, сертификат Cert(N-1) выдал центр №(N-1),..., Cert1-№1, то в конфиге должны быть все эти N-центров? Т.е. получается целая «портянка»?


    1. DmitryMe
      08.04.2016 18:23
      +1

      Все сертификаты цепочки обычно выпущены одним и тем же центром — и корневой (он «самоподписанный»), и промежуточные (один или несколько), и сертификат сервиса. Сервис должен отдавать клиентам все сертификаты кроме корневого в этой цепочке.