Частина посилань на цій сторінці партнерські: якщо ви зареєструєтесь через них, сайт отримає комісію, для вас ціна не змінюється. На ціни й рекомендації це не впливає.
Чотири сайти на одному старому боксі згасли. Не повільно, не 500 - згасли, у значенні «TLS-хендшейк провалювався ще до того, як виник запит». Вони лежали п’ять днів, перш ніж хтось помітив, бо ніхто не стежив за єдиною річчю, яка насправді мала значення.
Причина була буденною: оновлення сертифікатів жило всередині панелі керування, ліцензія панелі протерміналася, і її щоденний cron на оновлення помер разом із нею. Ще сім сертифікатів на тій самій машині мали прострочитися протягом наступних трьох тижнів.
Дистрибутив роками був поза підтримкою, тож certbot не встановлювався без проблем, а пакети вендора давно зникли. Ось рецепт, який усунув це за менш як тридцять хвилин без жодної правки у vhost-файлах, і моніторинг, який гарантує, що це більше не станеться тихо.
Чому acme.sh, а не certbot
certbot - це Python-застосунок із деревом залежностей. На актуальному дистрибутиві це не проблема. На тому, що вийшов з підтримки, це і є вся проблема: системний Python застарий для сучасних wheels cryptography, pip хоче компілятор, якого ви не бажаєте бачити на продакшн-боксі, а шлях через snap потребує демона, якого дистрибутив ніколи не постачав.
acme.sh - це шел-скрипт. Йому потрібен bash і або curl, або wget. Це весь список залежностей. Він спілкується з ACME API через HTTPS за допомогою curl, тож навіть древній OpenSSL не бере участі в підписанні, а bash 4.2 для цього достатньо новий.
Є й приємна іронія, яку варто знати, якщо ви втікаєте від панелі: кілька комерційних панелей самі використовують acme.sh під капотом. Ви не замінюєте механізм - ви витягуєте його з-під того, що перестало працювати.
Встановлення і виправлення CA за замовчуванням
curl https://get.acme.sh | sh -s email=you@yourdomain.dev
source ~/.bashrc
Далі одразу зробіть крок, який пропускає більшість інструкцій. Починаючи з версії 3, acme.sh за замовчуванням використовує ZeroSSL, а не Let’s Encrypt, і ZeroSSL вимагає реєстрації акаунту з EAB-credentials. Якщо вам потрібен Let’s Encrypt, скажіть про це один раз:
acme.sh --set-default-ca --server letsencrypt
acme.sh --register-account -m you@yourdomain.dev
Пропустите цей крок - і перша ж спроба випуску сертифіката впаде з помилкою реєстрації, яка виглядає як проблема мережі.
Випуск сертифіката без втручання у vhost-и
Мета - нуль змін у конфігурації веб-сервера, бо на такому боксі конфіги генерує щось, що вже не під вашим контролем, і ручна правка просто буде перезаписана.
Це можливо завдяки двом речам.
Перше: використовуйте webroot-валідацію в теку, яку сервер уже обслуговує. Спершу перевірте, чи шлях уже налаштований - панелі зазвичай лишають його по собі:
curl -s -o /dev/null -w '%{http_code}\n' \
http://yourdomain.dev/.well-known/acme-challenge/test
Будь-що інше, крім помилки з’єднання, означає, що шлях доступний, і вам лишається тільки знайти, на яку теку на диску він мапиться. Далі:
acme.sh --issue -d yourdomain.dev -d www.yourdomain.dev \
-w /var/www/letsencrypt
Друге: записуйте сертифікат у ті самі назви файлів, які вже очікує сервер. Не спрямовуйте конфіг на теку acme.sh - спрямуйте acme.sh на назви файлів із конфігу. --install-cert робить саме це, а reload-хук застосовує зміни одразу:
acme.sh --install-cert -d yourdomain.dev \
--fullchain-file /etc/ssl/sites/yourdomain.dev.crt \
--key-file /etc/ssl/sites/yourdomain.dev.key \
--reloadcmd "nginx -s reload && apachectl graceful"
Вміст файлів змінюється, шляхи - ніколи, і в конфігурації веб-сервера ніхто нічого не редагує.
Протерміновані сертифікати не блокують оновлення
Природне побоювання: кожен сайт редіректить HTTP на HTTPS, а HTTPS зламаний - то як валідація дістанеться до challenge-файлу?
Працює. Let’s Encrypt іде за редіректом на HTTPS і не перевіряє сам сертифікат під час challenge HTTP-01. Давно протермінований сертифікат, самопідписаний, невідповідність hostname - ніщо з цього не зупиняє випуск. Розкручувати редіректи назад не потрібно, і це добре, бо на згенерованому конфігу ви й не змогли б.
Автоматизуйте і перевірте, чи немає конкурента
acme.sh --install-cronjob
crontab -l | grep acme
Саме другий рядок тут важливий, і саме на ньому ми схибили. Cron панелі на оновлення був вимкнений - але другий cron тієї ж панелі, який робив ту саму роботу в інший час, сидів на чотири рядки нижче й був пропущений при першому перегляді. Два механізми оновлення, що змагаються за одні й ті самі файли, - проблема, яка виринає тижнями пізніше як сертифікат, що валідний на диску і протермінований у пам’яті сервера.
Прочитайте весь crontab цілком. Коментуйте, а не видаляйте, щоб було видно, що там було раніше:
crontab -l > /root/crontab.bak-$(date +%Y%m%d)
acme.sh за замовчуванням оновлює сертифікат на позначці 60 днів, тож у невдалого запуску є 30 днів повторних спроб, перш ніж хоч щось стане видно користувачам. Це щедрий запас - і саме тому тиха відмова може тягнутися місяцями.
Моніторте сертифікат, який реально віддається, а не файл на диску
Саме ця частина виявила б первинний простій, і майже ніхто її не налаштовує.
Перевіряти файл недостатньо: це показує, що оновлення щось записало, а не те, що працюючий процес це підхопив. Reload, який тихо провалився, лишає свіжий файл на диску і протермінований сертифікат у пам’яті. Перевіряйте сам хендшейк:
#!/usr/bin/env bash
# days until the served certificate expires, per domain
for d in "$@"; do
end=$(echo | openssl s_client -servername "$d" -connect "$d":443 2>/dev/null \
| openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -z "$end" ]; then echo "CRITICAL $d: no TLS handshake"; continue; fi
days=$(( ( $(date -d "$end" +%s) - $(date +%s) ) / 86400 ))
[ "$days" -lt 25 ] && echo "WARN $d: $days days left" || echo "OK $d: $days days"
done
Запускайте це щодня для кожного hostname, який обслуговує бокс, і сигналізуйте при менш як 25 днях. Правильність цієї перевірки тримається на двох властивостях: вона голосно провалюється, коли хендшейк не відбувається взагалі - а це і є реальний простій, - і оскільки вона читає те, що сервер віддає клієнту, reload, який не відбувся, не сховається за оновленим файлом.
Додайте до того самого завдання tail лог оновлення - і вся історія опиниться в одному алерті:
tail -n 5 /var/log/acme-renew.log
Результат
Одинадцять сертифікатів перевипущено за один прохід, усі дійсні на наступні три місяці, чотири мертві сайти знову живі, оновлення тепер належить 200-кілобайтному шел-скрипту із записом у cron, який можна прочитати. Жоден vhost-файл не був змінений - змінилися лише байти всередині файлів сертифікатів.
Ширший урок тут не про ACME-клієнти. Він про те, що провал - не в тому, що щось протерміновується, а в тиші навколо цього. Оновлення було зламане приблизно два місяці до того, як упав перший сайт, і єдина причина, чому про це хтось дізнався, - сайт згас на очах у користувачів. Перевірка хендшейка на двадцять рядків підняла б тривогу вже в перший тиждень.
Якщо ви збираєте новий бокс, а не рятуєте старий, Caddy робить усе це без жодної конфігурації взагалі - цю зв’язку ми описали в Caddy і Docker у продакшні. А цей рецепт - для машини, яку ви отримали у спадок: там, де конфіг генерується, дистрибутив заморожений, а єдина прийнятна зміна - найменша можлива.