Сертификаты Let's Encrypt для MS Exchange Server 2019

6cf55c2fa79c0ee2b5a1f793e073c12a.jpg

В процессе изучения и настройки Exchange Server 2019 с группой доступности из нескольких серверов встал вопрос с установкой валидных сертификатов. Где брать,  было очевидно. Мы планировали использовать Let«s Encrypt — не с самоподписанным сертификатом же в свет выходить. Вопрос заключался в том, как устанавливать сертификат минимум на два сервера. Можно, конечно, и руками… каждые три месяца. Но если серверов станет больше?

В этой статье распишем шаги с подготовкой, запросом и установкой сертификата, которые потом можно собрать в скрипт для регулярного обновления и установки на все сервера Exchange.

Вся работа по выпуску и распространению сертификата будет выполняться на первом сервере mbx1 в DAG«е. Для получения сертификата будем использовать ACME клиент win-acme. Это ACMEv2 клиент для Windows, предназначенный для автоматизации получения и установки SSL/TLS сертификатов, таких как Let’s Encrypt. 

Основные возможности:

  • поддержка сценариев установки для ПО (в т. ч. Apache, Exchange);

  • поддержка wildcards, IDN;

  • методы валидации: DNS-01, HTTP-01 и TLS-ALPN-01;

  • интеграция с популярными DNS-провайдерами;

  • поддержка хранилищ сертификатов (Windows Certificate Store, IIS Central Certificate Store, .pem файлы, .pfx файлы, Azure KeyVault);

  • полностью автоматизированная работа из командной строки;

  • поддержка создания заданий в Task Scheduler для автоматического обновления сертификатов.

Подготовка

Клиент положим в C:\win‑acme, скрипты в C:\scripts, а в C:\cert будем сохранять файлы сертификатов. Сертификат для Mailbox и Edge серверов будет отдельный, с различающимися Thumbprint. Немного позже объясню, почему так.

Отметим, что с win‑acme поставляются скрипты для установки сертификатов под различные цели, в том числе скрипт ImportExchange.v2.ps1 подходит для установки сертификата только на локальный Exchange Server. Напишем своё решение, которое будет устанавливать сертификат на Mailbox и Edge серверы в сети.

Пароль на сертификаты будем хранить в локальном хранилище SecretManagement.

Этот модуль позволяет безопасно хранить и управлять паролями и другими секретами. Он поддерживает различные хранилища, в том числе SecretStore, KeePass, LastPass, HashiCorp Vault, Azure Key Vault.

Get-PSRepository
Register-PSRepository -Default
Install-Module -Name PowerShellGet -Force -AllowClobber
Install-Module -Name PackageManagement -Force -AllowClobber

# Установим модуль SecretManagement и хранилище SecretStore
Install-Module -Name Microsoft.PowerShell.SecretManagement
Install-Module -Name Microsoft.PowerShell.SecretStore

# Регистрация хранилища
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
Register-SecretVault -Name "MyVault" -ModuleName Microsoft.PowerShell.SecretStore

# Для использования пароля для сертификата в скриптах уберем запрос пароля с хранилища:
Set-SecretStoreConfiguration -Authentication None -Confirm:$false
Set-Secret -Name CertPass -Secret (ConvertTo-SecureString "pa$$w0rd" -AsPlainText -Force) -Vault "MyVault"

# Так же сохраним пользователя сервера Edge:
$credential = Get-Credential
Set-Secret -Name EdgeCredential -Secret $credential

Основная часть

Приступаем к шагам для выполнения по скрипту:

# определяем переменные
$securePassword = Get-Secret -Name CertPass
$password = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword))
$credentialEdge = Get-Secret -Name EdgeCredential
$servers = @("mbx1", "mbxN")
$edges = @("edge1", "edgeN")

# возможность использовать командлеты Exchange
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://mbx1.example.ru/PowerShell/ -Authentication Kerberos
Import-PSSession $Session -DisableNameChecking
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn

Валидация DNS-01

Валидация с помощью DNS-01 занимает больше времени и требует манипуляций с системным DNS во время валидации, но позволяет получить wildcard сертификат. 

Для увеличения количества и продолжительности попыток валидации TXT записей есть возможность поменять параметры »PreValidateDnsRetryCount» и »PreValidateDnsRetryInterval» в блоке «Validation» файла settings.json.

Временно поменяем адреса DNS для сетевой карты (этот шаг необязателен и применяется индивидуально). Во-первых, так можно быстрее увидеть изменения,   потому что win-acme попытается разрешить имя зоны через IP-адреса NS серверов для зон. Но практика  показывает, что у REG.RU не все адреса могут отдать результат, поэтому скрипт перейдёт на использование DNS адресов системы.

Во-вторых, в AD DNS для Exchange внутренняя зона скорее всего совпадает по имени с внешней, но хосты имеют внутренние адреса. Либо отдельно создана зона с именем внешней зоны, которая содержит внутренние адреса.

# Получение активного сетевого адаптера по id (или имени)
$adapter = Get-NetAdapter | Where-Object { $_.InterfaceIndex -eq 6 }  
# сохранение текущих настроек DNS
$originalDns = Get-DnsClientServerAddress -InterfaceAlias $adapter.Name  
# установка нового DNS сервера
Set-DnsClientServerAddress -InterfaceAlias $adapter.Name -ServerAddresses "77.88.8.8,8.8.8.8"
# очистим DNS кэш
Clear-DnsClientCache

Записи эксплуатируемой зоны хранятся на NS серверах REG.RU. REG — непопулярный DNS-провайдер, поэтому нужен скрипт для создания и удаления записей для валидации доменных имен, для которых будет выпускаться сертификат. 

Предварительно необходимо настроить доступ к API REG.RU. В разделе профиля пользователя «Настройка» — «Настройка API» указать как минимум альтернативный пароль и разрешённый IP адрес доступа.

Руководствуясь документацией, создаём два простых файла:

Скрипт для добавления TXT записи C:\scripts\dns-01-reg2ru-addtxt.ps1

param(
	[Parameter(Position=0,Mandatory=$true)]
	[string]
	$zoneName,
	[Parameter(Position=1,Mandatory=$true)]
	[string]
	$nodeName,
	[Parameter(Position=2,Mandatory=$false)]
	[string]
	$token	
)

Invoke-RestMethod -Uri "https://api.reg.ru/api/regru2/zone/add_txt" -Method Post -Body @{
username = "regru-account@example.com"
password = "pa$$w0rd"
domain_name = $zoneName
subdomain = $nodeName
content = $token
output_format = "json"
}

И скрипт для удаления TXT записи C:\scripts\dns-01-reg2ru-remove_record.ps1

param(
	[Parameter(Position=0,Mandatory=$true)]
	[string]
	$zoneName,
	[Parameter(Position=1,Mandatory=$true)]
	[string]
	$nodeName,
	[Parameter(Position=2,Mandatory=$false)]
	[string]
	$token	
)

Invoke-RestMethod -Uri "https://api.reg.ru/api/regru2/zone/remove_record" -Method Post -Body @{
username = "regru-account@example.com"
password = "pa$$w0rd"
domain_name = $zoneName
subdomain = $nodeName
record_type = "TXT"
content = $token
output_format = "json"
}

Пользуйтесь ключами --test и --nocache в win-acme. Первый включает режим тестирования, при котором используется тестовый сервер ACME, что позволяет избежать ограничения по количеству запросов на реальном сервере. При использовании второго ключа запросы выполняются без использования ранее сохраненных данных, что позволяет выполнить полный цикл запроса сертификата.

Судя по документации, win-acme позволяет одним запуском клиента сделать запрос на два сертификата. Но при использовании DNS валидации для первого хоста в списке (wildcard) не создавалась TXT запись в зоне, хотя по логу всё работало штатно. 

Разделим на запрос wildcard сертификата для серверов Mailbox:

C:\win-acme\wacs.exe --source manual --host *.example.ru --emailaddress admin@example.ru --accepttos --validationmode dns-01 --validation script --dnscreatescript "c:\scripts\dns-01-reg2ru-addtxt.ps1" --dnsdeletescript "c:\scripts\dns-01-reg2ru-remove_record.ps1" --dnscreatescriptarguments "'{ZoneName}' '{NodeName}' '{Token}'" --dnsdeletescriptarguments "'{ZoneName}' '{NodeName}' '{Token}'" --store pfxfile --pfxpassword $password --pfxfilepath "C:\cert" --pfxfilename "_wildcard.example.ru"

И запрос сертификата для Edge сервера

C:\win-acme\wacs.exe --source manual --host edge.example.ru --emailaddress admin@example.ru --accepttos --validationmode dns-01 --validation script --dnscreatescript "c:\scripts\dns-01-reg2ru-addtxt.ps1" --dnsdeletescript "c:\scripts\dns-01-reg2ru-remove_record.ps1" --dnscreatescriptarguments "'{ZoneName}' '{NodeName}' '{Token}'" --dnsdeletescriptarguments "'{ZoneName}' '{NodeName}' '{Token}'" --store pfxfile --pfxpassword $password --pfxfilepath "C:\cert" --pfxfilename "edge.example.ru"

Восстановление оригинальных настроек DNS:

Set-DnsClientServerAddress -InterfaceAlias $adapter.Name -ServerAddresses $originalDns.ServerAddresses
Clear-DnsClientCache

Валидация HTTP-01

В варианте с валидацией HTTP-01 можно использовать валидацию self-hosting plugin с публикацией, т.к. клиента запускаем с сервера IIS с подготовленными сайтами для проверки. Wildcard сертификат в таком варианте не получить. Приведу пример.

Подтвердить владение доменным именем можно, опубликовав на IIS сервера mbx1 ресурсы mail и autodiscover на порту tcp/80 — на роутере настроено dnat правило. Поставим туда страницу-заглушку в виде серой страницы и отключим эти сайты — поднимать будем только во время выпуска сертификата.

New-Website -Name "mail-plug" -Port 80 -PhysicalPath "C:\inetpub\wwwroot\mail-plug" -ApplicationPool "DefaultAppPool" -HostHeader "mail.example.ru"
New-Website -Name "autodiscover-plug" -Port 80 -PhysicalPath "C:\inetpub\wwwroot\autodiscover-plug" -ApplicationPool "DefaultAppPool" -HostHeader "autodiscover.example.ru"

Stop-Website -Name mail-plug
Stop-Website -Name autodiscover-plug

IIS не обрабатывает файлы без расширения, что приведёт к ошибке 404 при попытке верификации сайта. Необходимо создать директорию .well-known/acme-challenge и положить в неё файл web.config с содержимым:



     
         
             
         
     

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

Страница-заглушка:

index.html





Серая страница
















Приступаем к запросу сертификата:

# включаем сайты заглушки
Start-Website -Name mail-plug
Start-Website -Name autodiscover-plug

C:\win-acme\wacs.exe --source manual --host mail.example.ru,autodiscover.example.ru --emailaddress admin@example.ru --accepttos --store pfxfile --pfxpassword $password --pfxfilepath "C:\cert"

# выключаем сайты заглушки
Stop-Website -Name mail-plug
Stop-Website -Name autodiscover-plug

Полученный файл сертификата будет содержать имена хостов в Subject Alternative Names. Основным именем и именем файла будет имя первого хоста в перечне — mail.example.ru. Для Edge сервера нужно запросить отдельный сертификат. Если он должен покрывать то же имя хоста, то второй запрос можно сделать с ключем –nocache и сохранением в отдельный файл. Thumbprint у двух сертификатов будет отличаться.

Типовые проблемы с валидацией win-acme можно посмотреть здесь.

Установка сертификатов

Для себя выбрал валидацию DNS-01, чтобы получить wildcard сертификат и отдельный сертификат для серверов Edge.

Приступаем к импорту и установке сертификата на Mailbox сервера, назначаем на службы POP/IMAP и перезапускаем службы. Предварительно снимаем сертификат с коннектора отправки, чтобы избежать ошибки:

Set-SendConnector "Internet" -TlsCertificateName $none
foreach ($server in $servers) {
Import-ExchangeCertificate -FileData ([System.IO.File]::ReadAllBytes('C:\\cert\\_wildcard.example.ru.pfx')) -Password $securePassword -Server $server
$newCert = Get-ExchangeCertificate | Where-Object {$_.Subject -eq "CN=*.example.ru"} | Sort-Object NotAfter -Descending | Select-Object -First 1 -ExpandProperty Thumbprint
# для wildcard
Enable-ExchangeCertificate -Thumbprint $newCert -Services IIS,SMTP -Server $server -Force
# не wildcard
Enable-ExchangeCertificate -Thumbprint $newCert -Services POP,IMAP,IIS,SMTP -Server $server -Force
Set-PopSettings  -Server $server -X509CertificateName mail.bormoglot.ru -ExternalConnectionSettings 'mail.bormoglot.ru:995:SSL'
Set-ImapSettings -Server $server -X509CertificateName mail.bormoglot.ru -ExternalConnectionSettings 'mail.bormoglot.ru:993:SSL'
Invoke-Command -ComputerName $server -ScriptBlock {
Restart-Service MSExchangePOP3
Restart-Service MSExchangePOP3BE
Restart-Service MSExchangeIMAP4
Restart-Service MSExchangeIMAP4BE
}
# ищем старую версию сертификата и удаляем её
$oldCert = Get-ExchangeCertificate -Server $server | Where-Object {$_.Subject -eq "CN=*.example.ru"} | Sort-Object NotAfter | Select-Object -First 1 -ExpandProperty Thumbprint
if ($newCert -ne $oldCert) {
Remove-ExchangeCertificate -Thumbprint $oldCert -Server $server -Confirm:$false
}
}

# Для коннекторов приема:
$cert = Get-ExchangeCertificate -Thumbprint $newCert
$tlscertificatename = "$($cert.Issuer)$($cert.Subject)"
foreach ($connector in Get-ReceiveConnector "*\Default Frontend*") {
Set-ReceiveConnector $connector.Id -TlsCertificateName $tlscertificatename
}

# Для коннектора отправки серверов Mailbox:
Set-SendConnector "Internet" -TlsCertificateName $tlscertificatename

Если присутствует Edge сервер, то при смене сертификата будут появляться уведомления о необходимости обновить подписку. На сам Edge также можно установить сертификат для SMTP, но нельзя использовать тот же сертификат, что стоит на Mailbox серверах.

Ничего не мешает импортировать и установить его, после чего Edge попросит обновить подписку. Но когда вы сгенерируете файл подписки на Edge, скопируете его на HubTransport сервер и попытаетесь применить, то получите ошибку:

The subscription file failed to load for the following reason: The direct trust certificate of the subscribed Edge Transport server with thumbprint
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX is a duplicate of the certificate of one of the HubTransport servers. Sharing the same certificate between Edge and
Hub Transport servers is not allowed.

Импортируем отдельный сертификат для Edge:

# добавляем edge1 в доверенные хосты и открываем сессию. Помним о Firewall
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "edge1" -Force
$session = New-PSSession -ComputerName edge1 -Credential $credentialEdge

Copy-Item -Path "C:\cert\edge.example.ru.pfx" -Destination "C:\cert\edge.example.ru.pfx" -ToSession $session
# снимем сертификаты с коннекторов отправки Edge
foreach ($connector in Get-SendConnector) {
if ($connector.Name -like "*Edge*") {Set-SendConnector $connector.Name -TlsCertificateName $none}
}
Invoke-Command -Session $session -ScriptBlock {
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn
Import-ExchangeCertificate -FileData ([System.IO.File]::ReadAllBytes('C:\cert\edge.example.ru.pfx')) -Password $using:securePassword -Server edge1
$newCert = Get-ExchangeCertificate | Where-Object {$_.Subject -eq "CN=edge.example.ru"} | Sort-Object NotAfter -Descending | Select-Object -First 1 -ExpandProperty Thumbprint
Enable-ExchangeCertificate -Thumbprint $newCert -Services SMTP -Server edge1 -Force
$oldCert = Get-ExchangeCertificate -Server $server | Where-Object {$_.Subject -eq "CN=edge.example.ru"} | Sort-Object NotAfter | Select-Object -First 1 -ExpandProperty Thumbprint
if ($newCert -ne $oldCert) {
Remove-ExchangeCertificate -Thumbprint $oldCert -Server edge1 -Confirm:$false
}
}

# Назначим сертификат на коннекторы отправки Edge:
Import-ExchangeCertificate -FileData ([System.IO.File]::ReadAllBytes('C:\\cert\\edge.example.ru.pfx')) -Password $securePassword
$newCertEdge = Get-ExchangeCertificate | Where-Object {$_.Subject -eq "CN=edge.example.ru"} | Sort-Object NotAfter -Descending | Select-Object -First 1
$tlscertificatenameEdge = "$($newCertEdge.Issuer)$($newCertEdge.Subject)"
foreach ($connector in Get-SendConnector) {
if ($connector.Name -like "*Edge*") {Set-SendConnector $connector.Name -TlsCertificateName $tlscertificatenameEdge}
}

# Теперь обновим подписку:
Invoke-Command -Session $session -ScriptBlock {
Remove-Item -Path "C:\Edge.xml"
New-EdgeSubscription -FileName "C:\Edge.xml" -Force
}
Remove-Item -Path "C:\Edge.xml"
Copy-Item -Path "C:\Edge.xml" -Destination "C:\Edge.xml" -FromSession $session

New-EdgeSubscription -FileData ([byte[]]$(Get-Content -Path "C:\Edge.xml" -Encoding Byte -ReadCount 0)) -Site "Default-First-Site-Name"

Invoke-Command -Session $session -ScriptBlock {
Restart-Service ADAM_MSExchange -Force
}
Restart-Service MSExchangeEdgeSync
Start-EdgeSynchronization
Test-EdgeSynchronization -FullCompareMode

# Закроем сессию и очистим список доверенных хостов:
Remove-PSSession -Session $session
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "" -Force

Перевыпуск

По умолчанию win-acme создает задание на перевыпуск сертификата в Task Scheduler. Вместо него нужно собрать свой отлаженный скрипт и создать задачу с выполнением раз в 60 дней,   отключив автопродление в wacs:

Get-ExchangeCertificate | Select-Object FriendlyName, Thumbprint wacs.exe --cancel --friendlyname "Имя вашего сертификата" 
"ScheduledTask": false

 в файле settings.json.

Заключение

В результате проделанной работы все необходимые службы MS Exchange Server обеспечены сертификатом от Let«s Encrypt. Имеется всё необходимое для выполнения автоматического обновления и установки сертификата каждые два месяца, но задачу лучше контролировать, проверять результат выполнения, чтобы избежать ошибок в работе служб.

Спасибо за внимание! Макс, системный администратор Cloud4Y.

© Habrahabr.ru