Сертификаты Zimbra: zmcertmgr, цепочка и автопродление LE

У клиента-бухгалтерской фирмы на Авиамоторной сертификат истёк в субботу утром. Легла веб-почта, отвалились телефоны, а вдобавок перестал отвечать zmcontrol status: LDAP отказался поднимать TLS. Автопродление Let's Encrypt при этом честно отработало месяцем раньше и положило на диск безупречные файлы. Разбираю, почему свежий сертификат может лежать на диске и не работать.

Суббота, 10:20: не открывается ни у кого

Аварии с сертификатами устроены подло. Никто ничего не менял. Не было ни обновления, ни перезагрузки, ни грозы. Просто наступила дата — и сервер, четыре года не доставлявший хлопот, перестал пускать всех сразу: и веб-клиент, и телефоны, и Outlook на бухгалтерских машинах.

Если вы ставили сертификат на Zimbra один раз, руками, а потом настроили автопродление и выдохнули — дальше про вас. Причём самое неприятное: автопродление у вас, скорее всего, живое. Файлы обновляются по расписанию, дата в них свежая, certbot рапортует об успехе. А сервер продолжает отдавать наружу прошлогодний сертификат, потому что Zimbra читает его совсем не из того каталога, куда пишет certbot.

Второй сюрприз — про масштаб. Все ждут, что просроченный сертификат — это жёлтая плашка в браузере и ворчание пользователей. У Zimbra от него отваливается LDAP: внутреннее соединение к каталогу тоже идёт через TLS и тоже проверяет сертификат. А без каталога не стартует ничего, включая ту самую команду, которой вы собрались смотреть, что случилось.

Клиент, сервер и первые пять минут

Июнь 2023 года. Бухгалтерская фирма на Авиамоторной, 25 рабочих мест, обслуживает примерно полторы сотни небольших ООО и ИП. Пик у них — отчётные недели, и суббота перед 25-м числом для них полноценный рабочий день. Zimbra 8.8.15 Open Source, развёрнута в 2019-м на своей виртуалке, Let’s Encrypt поставили в 2022-м, тогда же прописали certbot в крон. Штатного админа у фирмы нет: приходил свой человек, раз в квартал руками передеплоивал свежий сертификат внутрь Zimbra и уходил. В апреле 2023-го он сменил работу. Про эту часть ритуала не знал никто, включая директора, который платил за «сопровождение сервера».

Меня подняли в 10:20. Первое, что я сделал, — зашёл под пользователем zimbra и попросил статус служб. Получил не статус:

$ su - zimbra
$ zmcontrol status
ERROR: Unable to start TLS: SSL connect attempt failed
 error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed
 when connecting to ldap master.

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

Дальше я посмотрел на сертификат снаружи, не изнутри сервера:

$ echo | openssl s_client -connect mail.example.ru:443 -servername mail.example.ru 2>/dev/null \
  | openssl x509 -noout -subject -dates
subject=CN = mail.example.ru
notBefore=Mar 19 03:14:52 2023 GMT
notAfter=Jun 17 03:14:51 2023 GMT

Девять утра субботы, 17 июня. Сертификат умер этой же ночью, в 03:14 по Гринвичу — 06:14 по Москве. Ровно поэтому никто ничего не заметил вчера: вчера он ещё был живой.

Ложная версия: «значит, certbot сломался»

Мысль напрашивалась сама. Автопродление отвалилось, крон не отработал, LE выкатил новую версию клиента, у сервера кончилось место — вариантов масса, и все они правдоподобны. Я полез проверять именно это и потратил впустую минут сорок.

# ls -l /etc/letsencrypt/live/mail.example.ru/
lrwxrwxrwx 1 root root  41 May 18 03:14 cert.pem -> ../../archive/mail.example.ru/cert13.pem
lrwxrwxrwx 1 root root  42 May 18 03:14 chain.pem -> ../../archive/mail.example.ru/chain13.pem
lrwxrwxrwx 1 root root  46 May 18 03:14 fullchain.pem -> ../../archive/mail.example.ru/fullchain13.pem

# certbot certificates
  Certificate Name: mail.example.ru
    Expiry Date: 2023-08-16 03:14:51+00:00 (VALID: 59 days)

Продление отработало 18 мая в 03:14, за месяц до аварии. Файлы на диске были не просто свежие — они были безупречные, и до августа им ничего не грозило.

Здесь я и завис. Сертификат обновлён, а сервер отдаёт старый. Пока в голове держится картинка «сертификат — это файл», объяснения нет. Картинка неверная. У Zimbra сертификат живёт в пяти местах сразу, и файл на диске — только исходник.

Где на самом деле лежит сертификат Zimbra

Zimbra не читает pem-файл при каждом подключении. При деплое утилита zmcertmgr раскладывает сертификат по потребителям: nginx-прокси, mailboxd, LDAP, MTA, admin-консоль. У части из них — свои форматы хранения.

  • /opt/zimbra/ssl/zimbra/commercial/commercial.crt и commercial.key — то, что вы отдали утилите;
  • /opt/zimbra/ssl/zimbra/jetty.pkcs12 — промежуточный контейнер для Java;
  • /opt/zimbra/mailboxd/etc/keystore — хранилище, из которого читает mailboxd;
  • копии для nginx, postfix и slapd в подкаталогах /opt/zimbra/ssl/.

Пока не выполнен деплой, все они содержат прошлую версию. Именно поэтому на сервере может одновременно лежать валидный файл от 12 июня и работать сертификат, истёкший 12 июня же. Никакого противоречия.

Посмотреть, что реально развёрнуто, можно одной командой — и она берёт данные из хранилищ, а не с диска:

$ /opt/zimbra/bin/zmcertmgr viewdeployedcrt
** Verifying ldap
notBefore=Mar 19 03:14:52 2023 GMT
notAfter=Jun 17 03:14:51 2023 GMT
** Verifying mailboxd
notBefore=Mar 19 03:14:52 2023 GMT
notAfter=Jun 17 03:14:51 2023 GMT
** Verifying proxy
notBefore=Mar 19 03:14:52 2023 GMT
notAfter=Jun 17 03:14:51 2023 GMT

Три службы, три одинаковые даты, все три в прошлом. И мартовская дата слева — это отпечаток последнего ручного деплоя: тот самый человек в марте прогнал zmcertmgr, в апреле уволился, а майское продление осталось лежать файлами. Дальше уже дело техники.

Проверьте у себя за две минуты

Две команды. Первая показывает, что развёрнуто внутри, вторая — что видит внешний мир. Расхождение между ними и есть та самая мина.

su - zimbra
/opt/zimbra/bin/zmcertmgr viewdeployedcrt | grep -E 'Verifying|notAfter'

for p in 443 993 7071; do
  echo -n "порт $p: "
  echo | openssl s_client -connect $(zmhostname):$p 2>/dev/null | openssl x509 -noout -enddate
done

Как читать результат:

Что увиделиЧто это значитКогда действовать
Все даты одинаковы, до конца больше 25 днейДеплой проходил целиком, запас естьНичего не трогать
Даты одинаковы, осталось меньше 14 днейПродление либо не настроено, либо не доходит до деплояНа этой неделе
У ldap одна дата, у proxy другаяПрошлый деплой оборвался на середине, часть служб живёт на старом сертификатеВ ближайшее окно
Дата в прошлом, но файл в /etc/letsencrypt свежийПродление работает, передеплоя нет — авария отложена до истеченияСегодня
Команда вообще не отвечает, вместо неё error:14090086Сертификат уже просрочен и уронил TLS к каталогуНемедленно

Проверку на портах делайте именно снаружи: с рабочей станции, а не с самого сервера. Локально вы можете попасть в другой listener и увидеть благополучную картину, которой у пользователей нет.

Деплой: три отказа zmcertmgr и как их обходят

Дальше всё упирается в одну команду, но отказывать она умеет тремя способами. За тот день я собрал два из трёх.

Отказ первый — проверка цепочки. Перед деплоем сертификат положено проверить:

$ /opt/zimbra/bin/zmcertmgr verifycrtchain /tmp/ca_chain.crt /tmp/commercial.crt
ERROR: Unable to validate certificate chain: /tmp/commercial.crt: OK
error 2 at 1 depth lookup:unable to get issuer certificate

Это самый частый отказ, и почти всегда он про неполную цепочку. У коммерческих CA не хватает свежего промежуточного сертификата. У Let's Encrypt в chain.pem лежит промежуточный, а корня нет: предполагается, что он есть в системе. Утилита думает иначе, и корень дописывают в файл цепочки руками.

Отказ второй — права. Классика для всех, кто копировал файлы под root:

$ /opt/zimbra/bin/zmcertmgr deploycrt comm /tmp/commercial.crt /tmp/ca_chain.crt
** install commercial certificate failed: Permission denied

# лечится до запуска, не после
chown zimbra.zimbra /tmp/*.crt
chmod 644 /tmp/*.crt

Ещё одна свежая ловушка, на которой я потом обжёгся у другого клиента: certbot начиная с версии 2.0 по умолчанию выпускает ключ ECDSA, а zmcertmgr до 8.8.15-P45 (в девятке — до 9.0.0-P38, в десятке — до 10.0.6) такой ключ не переваривает и отказывается деплоить. Лечится флагом --key-type rsa при выпуске.

Отказ третий — зависший предыдущий деплой. Если попытка оборвалась в середине, следующая может встать намертво на записи в хранилище. Тогда старые контейнеры убираются в сторону и деплой повторяется с нуля:

mv /opt/zimbra/ssl/zimbra/jetty.pkcs12 /tmp/jetty.pkcs12.bak
mv /opt/zimbra/mailboxd/etc/keystore /tmp/keystore.bak
/opt/zimbra/bin/zmcertmgr deploycrt comm /tmp/commercial.crt /tmp/ca_chain.crt
zmcontrol restart

Мой личный прокол того дня. Я начал с деплоя, не остановив TLS к каталогу, и утилита честно отказалась работать, потому что не смогла достучаться до LDAP — по той самой причине, из-за которой я всё и затеял. Круг замкнулся. Развязывается он временным отключением TLS в клиентской части:

zmlocalconfig -e ldap_starttls_supported=0
zmlocalconfig -e ldap_starttls_required=false
zmcontrol restart

После деплоя оба значения возвращаются на место. Я вернул. Проверьте, что вернули и вы: с отключённым TLS каталог работает как ни в чём не бывало, и никто не заметит, что внутренний трафик ходит открытым.

Автопродление, которое действительно продлевает

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

#!/bin/bash
# /etc/letsencrypt/renewal-hooks/deploy/zimbra.sh
D=/etc/letsencrypt/live/mail.example.ru
Z=/opt/zimbra/ssl/zimbra/commercial
# корень ISRG качается один раз:
# wget -O /opt/zimbra/ssl/isrgrootx1.pem https://letsencrypt.org/certs/isrgrootx1.pem

cp $D/privkey.pem   $Z/commercial.key
cp $D/cert.pem      $Z/commercial.crt
cat $D/chain.pem /opt/zimbra/ssl/isrgrootx1.pem > $Z/commercial_ca.crt
chown -R zimbra:zimbra $Z

su - zimbra -c "/opt/zimbra/bin/zmcertmgr verifycrtchain $Z/commercial_ca.crt $Z/commercial.crt" || exit 1
su - zimbra -c "/opt/zimbra/bin/zmcertmgr deploycrt comm $Z/commercial.crt $Z/commercial_ca.crt"
su - zimbra -c "zmcontrol restart"

Три детали, ради которых это всё и пишется.

  • Хук лежит в каталоге deploy, то есть выполняется только после реального продления, а не при каждом холостом прогоне. Иначе почта будет перезапускаться дважды в день.
  • Проверка цепочки стоит до деплоя, и при её провале скрипт выходит с ошибкой. Так вы получите письмо от крона, а не тихо сломанный keystore.
  • Перезапуск обязателен. Без него mailboxd продолжит держать в памяти прежний сертификат до ближайшей перезагрузки, и вы окажетесь ровно там, откуда начали.

Ещё один момент, который у Let's Encrypt ловят через раз: продление по HTTP-01 требует, чтобы 80-й порт вёл на этот же сервер. Zimbra любит держать редирект с 80 на 443, и валидация упирается в него. Либо аккуратный alias в конфиге прокси, либо DNS-01 через API вашего регистратора.

Самоподписанный сертификат: что тихо отвалится

Соблазн понятен. Пересоздать свой CA — минута, предупреждение в браузере пользователи научатся прожимать за день, и авария как бы закрыта:

/opt/zimbra/bin/zmcertmgr createca -new
/opt/zimbra/bin/zmcertmgr deploycrt self
zmcontrol restart

Работает. Почта ходит. А потом в течение месяца приходят три заявки, которые никто не связывает с субботним инцидентом.

  • Перестаёт открываться общая адресная книга у части клиентов.
  • В Outlook пропадает информация о занятости коллег — переговорки бронируются вслепую.
  • Мобильные клиенты по-разному реагируют на неизвестный корень: одни ругаются, другие молча перестают синхронизироваться, и человек неделю ходит с ощущением, что ему просто никто не пишет.

Проблема не в том, что самоподписанный сертификат плох сам по себе. Проблема в отложенности: связь между «в июне мы поставили свой сертификат» и «в июле не бронируются переговорки» в голове не возникает ни у кого. Как временная мера на выходные — нормально. Как постоянное решение — вы платите чужим временем.

Чем кончилось и сколько это стоило

Разделю две цифры, их часто путают. Меня подняли в 10:20, отдал я работающий сервер в 12:45 — моей работы 2 часа 25 минут, из них сорок минут ушло на проверку версии с certbot, которая оказалась пустой. А клиент простоял дольше: с 09:05, когда пришла первая жалоба, до тех же 12:45. Это 3 часа 40 минут субботы отчётной недели.

В ту субботу в офисе сидели 18 человек из 25 — сдавали отчётность. Стоимость часа работы бухгалтера с налогами и рабочим местом фирма считает по 630 рублей. 18 человек × 3 ч 40 мин = 66 человеко-часов, то есть 41 580 рублей оплаченного времени, потраченного на разглядывание ошибки сертификата. Плюс мой аварийный выезд в выходной: обычная ставка 2 900 ₽/час, в выходной двойная, 4 часа к оплате — 23 200 рублей.

СтатьяЦифра
Простой почты3 ч 40 мин, суббота перед сдачей отчётности
Оплаченное время сотрудников впустую66 человеко-часов × 630 ₽ = 41 580 ₽
Аварийный выезд в выходной4 часа по двойному тарифу = 23 200 ₽
Сорванных встреч с клиентами фирмы2, обе перенесены на понедельник
Стоимость правильной настройки заранее1 час в будний день = 2 900 ₽: хук, тест продления, мониторинг даты

Итого 64 780 рублей против 2 900. Двадцать два к одному — и это без двух перенесённых встреч и без нервов восемнадцати человек в отчётную субботу. Разница не в квалификации, а в том, что кто-то один раз проверил, доходит ли обновление до keystore.

Что мы сделали после: поставили хук с передеплоем, вернули TLS для каталога, добавили в мониторинг проверку даты не по файлу, а по порту 443 с внешнего хоста, с алертом за 21 день. За два с половиной года — ни одного повтора. Одно продление в декабре 2024-го споткнулось о занятый 80-й порт, но мы узнали об этом за три недели, а не постфактум.

Если сомневаетесь, где вы находитесь: прогоните две команды из раздела про двухминутную проверку и пришлите мне вывод. Отвечу, что у вас — нормальный запас, тихо разъехавшиеся службы или мина с известной датой срабатывания. Это одно сообщение, и оно ничего не стоит.

Частые вопросы

У нас сертификат обновляется сам уже год. Значит, всё в порядке?

Не обязательно. Обновление файла и деплой в хранилища Zimbra — две разные операции, и вторая по умолчанию не происходит. Проверяется это за минуту: сравните дату из zmcertmgr viewdeployedcrt с датой файла в /etc/letsencrypt/live/. Если файл свежий, а развёрнутый сертификат старый — у вас работает половина схемы, и авария назначена на дату из второй колонки. Я видел серверы, где такое расхождение жило одиннадцать месяцев и разрешилось ровно в тот день, когда истёк последний развёрнутый сертификат.

Почему из-за сертификата отваливается LDAP, он же внутренний?

Потому что внутреннее соединение к каталогу у Zimbra тоже идёт через TLS и тоже проверяет сертификат — тот же самый. Когда он просрочен, клиентская часть отказывается устанавливать соединение и вы получаете error:14090086 при попытке узнать статус служб. Служба каталога при этом жива, процессы на месте, но обвязка её не видит. Расшить круг помогает временное отключение TLS через ldap_starttls_supported=0, после успешного деплоя значение обязательно вернуть обратно.

verifycrtchain падает, хотя сертификат точно правильный. Что не так?

Почти всегда дело в файле цепочки, а не в сертификате. Утилита хочет видеть путь до корня целиком. У коммерческих центров сертификации часто не хватает обновлённого промежуточного звена — его нужно скачать у самого CA и дописать. У Let's Encrypt в chain.pem лежит промежуточный, а корневой не лежит вовсе, потому что он предполагается в системном хранилище; для проверки его приходится подклеивать вручную. Порядок в файле — от промежуточного к корневому, сверху вниз.

Можно ли деплоить сертификат без перезапуска почты?

Частично можно: прокси и MTA подхватывают новые файлы относительно безболезненно. А вот mailboxd держит сертификат в своём хранилище и читает его при старте — до перезапуска он будет отдавать прежний. Практический вывод: планируйте деплой на окно, когда пятиминутная пауза никого не убьёт, и не делайте этого в понедельник утром. Перезапуск на сервере среднего размера занимает от двух до шести минут, дольше всего поднимается mailboxd.

Мы поставили самоподписанный сертификат, чтобы не платить. Чем рискуем?

Деньгами тут не рискуете, Let's Encrypt бесплатен. Рискуете отложенными заявками: у части клиентов перестаёт открываться общая адресная книга, в Outlook пропадает свободно-занято, мобильные клиенты ведут себя непредсказуемо — от предупреждений до молчаливой остановки синхронизации. Связь между причиной и следствием разнесена на недели, поэтому чинить это дороже, чем один раз выпустить нормальный сертификат. Как затычка на выходные — приемлемо, как постоянная схема — нет.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#Zimbra#сертификаты#Let's Encrypt#zmcertmgr#эксплуатация
Комментарии 0

Оставить комментарий

загрузка...

Подпишитесь на рассылку ITfresh

Раз в неделю — практические гайды для руководителя IT и сисадмина: безопасность, 1С, миграции, резервные копии, лайфхаки из реальных проектов.

Реквизиты оператора персональных данных

ООО «АЙТИ-ФРЕШ», ИНН 7719418495, КПП 771901001. Юридический адрес: 105523, г. Москва, Щёлковское шоссе, д. 92, корп. 7. Контакт: info@itfresh.ru, +7 903 729-62-41. Оператор обрабатывает e-mail подписчика в целях рассылки информационных и рекламных материалов до момента отзыва согласия.