Acme.sh - скрипт, позволяющий без особых проблем получать let's encrypt сертификаты очень разными способами. В данной статье я разберу как получать сертификаты через DNS api, но этим уже никого не удивишь, поэтому расскажу про метод DNS alias, он свежий (всего 3 года) и интересный. А так же про автоматизацию на Ansible и немного про мониторинг сертификатов.
Видеоверсия
Режимы acme.sh получения сертификатов прямо на целевом сервере
acme.sh --issue --dns -d *.itdog.info --yes-I-know-dns-manual-mode-enough-go-ahead-please
koala@x220:~$ acme.sh --issue --dns -d *.itdog.info --yes-I-know-dns-manual-mode-enough-go-ahead-please
[Ср мая 5 14:52:29 MSK 2021] Using CA: https://acme-v02.api.letsencrypt.org/directory
[Ср мая 5 14:52:29 MSK 2021] Creating domain key
[Ср мая 5 14:52:29 MSK 2021] The domain key is here: /home/koala/.acme.sh/*.itdog.info/*.itdog.info.key
[Ср мая 5 14:52:29 MSK 2021] Single domain='*.itdog.info'
[Ср мая 5 14:52:29 MSK 2021] Getting domain auth token for each domain
[Ср мая 5 14:52:32 MSK 2021] Getting webroot for domain='*.itdog.info'
[Ср мая 5 14:52:32 MSK 2021] Add the following TXT record:
[Ср мая 5 14:52:32 MSK 2021] Domain: '_acme-challenge.itdog.info'
[Ср мая 5 14:52:32 MSK 2021] TXT value: 'QXRgFOfVOZGOBC1qxAToMNOf7Xsv9gjM8hYG6akRoJ8'
[Ср мая 5 14:52:32 MSK 2021] Please be aware that you prepend _acme-challenge. before your domain
[Ср мая 5 14:52:32 MSK 2021] so the resulting subdomain will be: _acme-challenge.itdog.info
[Ср мая 5 14:52:32 MSK 2021] Please add the TXT records to the domains, and re-run with --renew.
[Ср мая 5 14:52:32 MSK 2021] Please add '--debug' or '--log' to check more details.
[Ср мая 5 14:52:32 MSK 2021] See: https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh
--issue - запрос на получение
--dns без аргумента - режим ручного DNS
--yes-I-know-dns-manual-mode-enough-go-ahead-please - интересное решение проблемы, когда люди не понимают что такое ручной режим
Он выдаёт TXT запись, которую нам необходимо добавить в наш dns хостинг, в данном случае это _acme-challenge.itdog.info TXT QXRgFOfVOZGOBC1qxAToMNOf7Xsv9gjM8hYG6akRoJ8
Ручной он потому что, мы вручную добавляем эту запись.
Анимация manual mode
После этого добавления нужно подождать какое-то время, что бы запись зарезолвилась и выполнить такую же команду, только с --renew
После добавления записи проверяем начала ли она резолвится на гугловом dns
koala@x220:~$ dig -t txt _acme-challenge.itdog.info @8.8.8.8
; <<>> DiG 9.11.3-1ubuntu1.15-Ubuntu <<>> -t txt _acme-challenge.itdog.info @8.8.8.8
;; ANSWER SECTION:
_acme-challenge.itdog.info. 1798 IN TXT "QXRgFOfVOZGOBC1qxAToMNOf7Xsv9gjM8hYG6akRoJ8"
Она резолвится, а значит можно получать сертификат
koala@x220:~$ acme.sh --renew --dns -d *.itdog.info --yes-I-know-dns-manual-mode-enough-go-ahead-please
[Ср мая 5 14:58:08 MSK 2021] Renew: '*.itdog.info'
[Ср мая 5 14:58:09 MSK 2021] Using CA: https://acme-v02.api.letsencrypt.org/directory
[Ср мая 5 14:58:09 MSK 2021] Single domain='*.itdog.info'
[Ср мая 5 14:58:09 MSK 2021] Getting domain auth token for each domain
[Ср мая 5 14:58:09 MSK 2021] Verifying: *.itdog.info
[Ср мая 5 14:58:13 MSK 2021] Success
[Ср мая 5 14:58:13 MSK 2021] Verify finished, start to sign.
[Ср мая 5 14:58:13 MSK 2021] Lets finalize the order.
[Ср мая 5 14:58:13 MSK 2021] Le_OrderFinalize='https://acme-v02.api.letsencrypt.org/acme/finalize/121...'
[Ср мая 5 14:58:15 MSK 2021] Downloading cert.
[Ср мая 5 14:58:15 MSK 2021] Le_LinkCert='https://acme-v02.api.letsencrypt.org/acme/cert/042...'
[Ср мая 5 14:58:16 MSK 2021] Cert success.
-----BEGIN CERTIFICATE-----
certificate
-----END CERTIFICATE-----
[Ср мая 5 14:58:16 MSK 2021] Your cert is in /home/koala/.acme.sh/*.itdog.info/*.itdog.info.cer
[Ср мая 5 14:58:16 MSK 2021] Your cert key is in /home/koala/.acme.sh/*.itdog.info/*.itdog.info.key
[Ср мая 5 14:58:16 MSK 2021] The intermediate CA cert is in /home/koala/.acme.sh/*.itdog.info/ca.cer
[Ср мая 5 14:58:16 MSK 2021] And the full chain certs is there: /home/koala/.acme.sh/*.itdog.info/fullchain.cer
После этого TXT запись можно удалить.
Теперь есть ключ и сертификат, который будет действителен 3 месяца. Обновить сертификат можно будет через 2 месяца, let's enctypt даёт запас времени и если у вас вдруг что-то сломается, будет целый месяц чтобы починить и обновить сертификат.
И да, обновляется только сертификат, ключ остаётся таким, какой был выдан в первый раз. Обратите внимание на даты создания файлов, особенно *.example.com.key
# ls -l --time-style=+%Y-%m-%d \*.example.com/
total 28
-rw-r--r-- 1 root root 1587 2021-04-15 ca.cer
-rw-r--r-- 1 root root 3433 2021-04-15 fullchain.cer
-rw-r--r-- 1 root root 1846 2021-04-15 *.example.com.cer
-rw-r--r-- 1 root root 719 2021-04-15 *.example.com.conf
-rw-r--r-- 1 root root 980 2021-04-15 *.example.com.csr
-rw-r--r-- 1 root root 211 2021-04-15 *.example.com.csr.conf
-rw-r--r-- 1 root root 1675 2019-04-10 *.example.com.key
Хороший режим для одного раза или для понимания как работает подтверждение, но на постоянке каждые 2 месяца вручную обновлять TXT записи не серьёзно, поэтому рассматриваем следующий режим.
Анимация API mode
Под DNS хостингом и DNS провайдером я буду иметь в виду сервис, в который вносятся DNS записи. Это может быть и DNS хостинг, который есть почти у каждой компании, торгующей доменами (namecheap, beget итд) или как отдельный сервис за деньги (Amazon Route 53, ClouDNS итд), или же ваш собственный сервис развернутый с помощью BIND, PowerDNS итд.
У каждого DNS провайдера свой не стандартизированный API и их поддержка в acme.sh реализована отдельными скриптами. Список всех поддерживаемых провайдеров находится тут https://github.com/acmesh-official/acme.sh/wiki/dnsapi
Для каждого написано как пользоваться и пример. Если вашего провайдера или сервиса нет в списке, можно написать свой скрипт, но не спешите открывать vim, дождитесь третьего способа.
Работу этого режима покажу на примере хостинга DNS у namecheap.
Для каждого DNS провайдера свои настройки, где-то нужно просто включить API и сгенерировать токен, где-то бывает посложнее, например для namecheap нужно ещё внести IP в allow list. Включаем API и сразу генерируется token, добавляем IP в список.
Теперь на локальной машине нужно настроить доступ к API
export NAMECHEAP_USERNAME="USERNAME"
export NAMECHEAP_API_KEY="TOKEN"
export NAMECHEAP_SOURCEIP="MY-IP"
acme.sh --issue --dns dns_namecheap -d *.itdog.info --test
Здесь после --dns мы добавляем имя провайдера.
Запускаем acme.sh
Раскрыть
В логе прям видно, что acme.sh добавляет TXT запись, ждёт немного, проверяет запись через доверенные DNS сервера, удаляет запись и скачивает сертификаты с ключом.
После первого запуска через API, acme.sh заносит env переменные c доступами к API себе в файл ~/.acme.sh/account.conf и вам не нужно каждый раз их экспортировать.
Отлично, получение автоматизировали, всё вроде классно. Но у этого метода есть свои недостатки:
Идея такая: Есть технический домен, через добавления TXT записей на котором мы подтверждаем владение основным доменом. т.е. acme.sh смотрит CNAME запись у основного домена, видит "перенаправление" на технический домен и идёт к нему проверять TXT запись. А дальше всё как в режиме DNS API.
Анимация alias mode
Разберём последовательно. Для демонстрации я купил домен tech-domain.club, он выступает в качестве технического домена. В моём примере основной домен itdog.info располагается на namecheap, а техничский tech-domain.club я делегирую на Hetzner DNS, таким образом операции с записями будут производиться через API Hetzner'a.
В записях основного домена мы добавляем CNAME запись, которая указывает на технический домен
_acme-challenge CNAME _acme-challenge.tech-domain.club
Для провайдера с техническим доменом мы настраиваем доступ к API.
Экспортируем токен Hetzner export HETZNER_Token="TOKEN"
Команда выглядит так (-f и --test опять же для примера)
acme.sh --issue -d *.itdog.info --challenge-alias tech-domain.club --dns dns_hetzner -f --test
Раскрыть
CNAME запись удалять не нужно, она будет использоваться при каждом обновлении сертификата. Таким образом, вы можете иметь несколько доменов с настроенной CNAME записью на один технический домен и с его помощью получать сертификаты для всех.
Кстати, если вам нужно в одном файле иметь несколько сертификатов, например и itdog.info и wildcard *.itdog.info, то просто перечислите их с -d, например
acme.sh --issue --challenge-alias tech-domain.club --dns hetzner -d *.itdog.info -d itdog.info
Это правило действует и для других методов.
И так, что даёт нам этот режим:
Мой сервер с ansible, уже имеет доступ ко всем необходимым серверам, на нём установлен acme.sh и реализовано два плейбука, на получение и распространение. Кстати, не забудьте закомментировать acme.sh в crontab, что бы не было лишних запросов и путаницы.
Домены, которые должны подходить как для обычного, так и для wildcard идут по втором taks, тоже с помощью loop. Если вам нужно например wilcard вида *.*.itdog.info, то просто добавьте ещё один -d и ещё один subkey в item. Опция ignore_errors необходима, потому что exit code 0 будет только 6 раз за год при обновлении сертификата, в остальное время будут сообщения о том, что сертификат не нужно обновлять, для ansible это ошибка на которой он будет останавливаться.
Три типа серверов из моей инфраструктуры:
Во втором случае всё плюс-минус так же, но уже нужно сделать docker exec project-nginx -s reolad, т.е. уже другой handler.
В третьем случае у нас помимо handler в контейнере, ещё нужно дать сертификату с ключом определённое имя, потому что оно прописано в их конфигурации, которую лучше не трогать, чтоб не было проблем при обновлении.
В моём случае доменов много, пути, по которым сертификаты с ключами хранятся, различаются. Так же есть случаи когда у одного сервера несколько доменов. Что бы была возможность настроить для каждого хоста свой путь и необходимый домен, в hosts для каждого хоста заданы переменные пути и домена.
nginx.itdog.info tls_path=/etc/letsencrypt/*.itdog.info/ DOMAIN=*.itdog.info
Для случаев, когда доменов на сервере несколько, делается два хоста с разными именами и одинаковым ansible_host (Совет, как сделать лучше, приветствуется).
nginx.example.com-1 ansible_host=nginx.example.com tls_path=/etc/letsencrypt/*.example.com/ DOMAIN=example.com
nginx.example.com-2 ansible_host=nginx.example.com tls_path=/etc/letsencrypt/*.example.org/ DOMAIN=example.org
Для каждого типа серверов создана своя группа в hosts. Для каждой группы свои немного отличающиеся друг от друга tasks. Для tls-hosts-docker так же добавлена переменная с именем контейнера nginx. А для tls-hosts-docker-rename добавлена переменная, в которой задаётся конечное имя сертификата и ключа.
docker-zabbix.itdog.info tls_path=/root/docker-zabbix/zbx_env/etc/ssl/nginx/ DOMAIN=*.itdog.info CONTAINER=docker-zabbix_zabbix-web-nginx-pgsql_1 cert_name=ssl.crt key_name=ssl.key
Для nginx нужен fullchain и domain.key - копируются только они. Если файлы различаются, происходит копирование и срабатывает handler nginx -s reload. Так же есть проверка, перед тем как зарелоудить nginx, что это файл. У меня один раз был случай, в самом начале пользования acme.sh, скрипт вместо файла с сертификатом создал директорию. Прямо как traefik 1.7 создаёт acme.json директорию, вместо файла. Поэтому я сделал простую проверку. В идеале нужно делать проверку, что сертификат валидный и не просроченный, но для этого требуется иметь на каждом хосте python-pyOpenSSL.
23 4 * * * /usr/bin/ansible-playbook /etc/ansible/playbook-copy-tls.yml -v >> /var/log/copy-tls.log
Можно без проблем вызывать их каждый день, let's encrypt будет вежливо говорить, что пока не нужно обновляться. А когда придёт срок, сертификаты будут обновлены.
Я использую zabbix и скрипт от @selivanov_pavel
Проверим с его помощью мой домен локально
koala@x220 ~/t/acme.sh-test> ./ssl_cert_check.sh expire itdog.info 443
41
41 день сертификат на itdog.info будет актуален. Сертификат в let's encrypt обновляется за 30 дней до протухания. А значит, например, если ему осталось жить 10 дней, значит что-то пошло не так и надо идти смотреть.
Темплейт состоит из одного item и одного trigger. Теплейт есть так же на github
ssl_cert_check.sh["expire","{HOST.NAME}","{$TLS_PORT}"]
В item две переменных, первая HOST.NAME берёт имя хоста, предполагается что у нас хост назван по доменному имени. Переменная $TLS_PORT по дефолту 443, но если нужно проверять на нестандартном порту, то записываем значение порта в macros.
Триггер тоже супер простой
{Check tls expire:ssl_cert_check.sh["expire","{HOST.NAME}","{$TLS_PORT}"].last()}<=10
Если полученное значение меньше 10ти - аллерт. Таким образом, мы узнаем если у нас начнут протухать сертификаты и будет 10 дней на починку.
Да, в идеале, ansible должен проверять перед копированием, что сертификат валидный и не просроченный. А система мониторинга проверять, помимо expire, валидность сертификатов.
Источник статьи: https://habr.com/ru/post/562026/
Видеоверсия
- Webroot
- Nginx\Apache
- Stanalone
- DNS manual
- DNS API
- DNS alias
DNS manual mode
Manual режим работает супер просто. Запускаем acme.sh с флагом --dnsacme.sh --issue --dns -d *.itdog.info --yes-I-know-dns-manual-mode-enough-go-ahead-please
koala@x220:~$ acme.sh --issue --dns -d *.itdog.info --yes-I-know-dns-manual-mode-enough-go-ahead-please
[Ср мая 5 14:52:29 MSK 2021] Using CA: https://acme-v02.api.letsencrypt.org/directory
[Ср мая 5 14:52:29 MSK 2021] Creating domain key
[Ср мая 5 14:52:29 MSK 2021] The domain key is here: /home/koala/.acme.sh/*.itdog.info/*.itdog.info.key
[Ср мая 5 14:52:29 MSK 2021] Single domain='*.itdog.info'
[Ср мая 5 14:52:29 MSK 2021] Getting domain auth token for each domain
[Ср мая 5 14:52:32 MSK 2021] Getting webroot for domain='*.itdog.info'
[Ср мая 5 14:52:32 MSK 2021] Add the following TXT record:
[Ср мая 5 14:52:32 MSK 2021] Domain: '_acme-challenge.itdog.info'
[Ср мая 5 14:52:32 MSK 2021] TXT value: 'QXRgFOfVOZGOBC1qxAToMNOf7Xsv9gjM8hYG6akRoJ8'
[Ср мая 5 14:52:32 MSK 2021] Please be aware that you prepend _acme-challenge. before your domain
[Ср мая 5 14:52:32 MSK 2021] so the resulting subdomain will be: _acme-challenge.itdog.info
[Ср мая 5 14:52:32 MSK 2021] Please add the TXT records to the domains, and re-run with --renew.
[Ср мая 5 14:52:32 MSK 2021] Please add '--debug' or '--log' to check more details.
[Ср мая 5 14:52:32 MSK 2021] See: https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh
--issue - запрос на получение
--dns без аргумента - режим ручного DNS
--yes-I-know-dns-manual-mode-enough-go-ahead-please - интересное решение проблемы, когда люди не понимают что такое ручной режим
Он выдаёт TXT запись, которую нам необходимо добавить в наш dns хостинг, в данном случае это _acme-challenge.itdog.info TXT QXRgFOfVOZGOBC1qxAToMNOf7Xsv9gjM8hYG6akRoJ8
Ручной он потому что, мы вручную добавляем эту запись.
После этого добавления нужно подождать какое-то время, что бы запись зарезолвилась и выполнить такую же команду, только с --renew
После добавления записи проверяем начала ли она резолвится на гугловом dns
koala@x220:~$ dig -t txt _acme-challenge.itdog.info @8.8.8.8
; <<>> DiG 9.11.3-1ubuntu1.15-Ubuntu <<>> -t txt _acme-challenge.itdog.info @8.8.8.8
;; ANSWER SECTION:
_acme-challenge.itdog.info. 1798 IN TXT "QXRgFOfVOZGOBC1qxAToMNOf7Xsv9gjM8hYG6akRoJ8"
Она резолвится, а значит можно получать сертификат
koala@x220:~$ acme.sh --renew --dns -d *.itdog.info --yes-I-know-dns-manual-mode-enough-go-ahead-please
[Ср мая 5 14:58:08 MSK 2021] Renew: '*.itdog.info'
[Ср мая 5 14:58:09 MSK 2021] Using CA: https://acme-v02.api.letsencrypt.org/directory
[Ср мая 5 14:58:09 MSK 2021] Single domain='*.itdog.info'
[Ср мая 5 14:58:09 MSK 2021] Getting domain auth token for each domain
[Ср мая 5 14:58:09 MSK 2021] Verifying: *.itdog.info
[Ср мая 5 14:58:13 MSK 2021] Success
[Ср мая 5 14:58:13 MSK 2021] Verify finished, start to sign.
[Ср мая 5 14:58:13 MSK 2021] Lets finalize the order.
[Ср мая 5 14:58:13 MSK 2021] Le_OrderFinalize='https://acme-v02.api.letsencrypt.org/acme/finalize/121...'
[Ср мая 5 14:58:15 MSK 2021] Downloading cert.
[Ср мая 5 14:58:15 MSK 2021] Le_LinkCert='https://acme-v02.api.letsencrypt.org/acme/cert/042...'
[Ср мая 5 14:58:16 MSK 2021] Cert success.
-----BEGIN CERTIFICATE-----
certificate
-----END CERTIFICATE-----
[Ср мая 5 14:58:16 MSK 2021] Your cert is in /home/koala/.acme.sh/*.itdog.info/*.itdog.info.cer
[Ср мая 5 14:58:16 MSK 2021] Your cert key is in /home/koala/.acme.sh/*.itdog.info/*.itdog.info.key
[Ср мая 5 14:58:16 MSK 2021] The intermediate CA cert is in /home/koala/.acme.sh/*.itdog.info/ca.cer
[Ср мая 5 14:58:16 MSK 2021] And the full chain certs is there: /home/koala/.acme.sh/*.itdog.info/fullchain.cer
После этого TXT запись можно удалить.
Теперь есть ключ и сертификат, который будет действителен 3 месяца. Обновить сертификат можно будет через 2 месяца, let's enctypt даёт запас времени и если у вас вдруг что-то сломается, будет целый месяц чтобы починить и обновить сертификат.
И да, обновляется только сертификат, ключ остаётся таким, какой был выдан в первый раз. Обратите внимание на даты создания файлов, особенно *.example.com.key
# ls -l --time-style=+%Y-%m-%d \*.example.com/
total 28
-rw-r--r-- 1 root root 1587 2021-04-15 ca.cer
-rw-r--r-- 1 root root 3433 2021-04-15 fullchain.cer
-rw-r--r-- 1 root root 1846 2021-04-15 *.example.com.cer
-rw-r--r-- 1 root root 719 2021-04-15 *.example.com.conf
-rw-r--r-- 1 root root 980 2021-04-15 *.example.com.csr
-rw-r--r-- 1 root root 211 2021-04-15 *.example.com.csr.conf
-rw-r--r-- 1 root root 1675 2019-04-10 *.example.com.key
Хороший режим для одного раза или для понимания как работает подтверждение, но на постоянке каждые 2 месяца вручную обновлять TXT записи не серьёзно, поэтому рассматриваем следующий режим.
DNS API mode
Как это работает? Принцип действия тот же самый что у manual, только acme.sh сам вносит и удаляет TXT запись с помощью API вашего dns провайдера.Под DNS хостингом и DNS провайдером я буду иметь в виду сервис, в который вносятся DNS записи. Это может быть и DNS хостинг, который есть почти у каждой компании, торгующей доменами (namecheap, beget итд) или как отдельный сервис за деньги (Amazon Route 53, ClouDNS итд), или же ваш собственный сервис развернутый с помощью BIND, PowerDNS итд.
У каждого DNS провайдера свой не стандартизированный API и их поддержка в acme.sh реализована отдельными скриптами. Список всех поддерживаемых провайдеров находится тут https://github.com/acmesh-official/acme.sh/wiki/dnsapi
Для каждого написано как пользоваться и пример. Если вашего провайдера или сервиса нет в списке, можно написать свой скрипт, но не спешите открывать vim, дождитесь третьего способа.
Работу этого режима покажу на примере хостинга DNS у namecheap.
Для каждого DNS провайдера свои настройки, где-то нужно просто включить API и сгенерировать токен, где-то бывает посложнее, например для namecheap нужно ещё внести IP в allow list. Включаем API и сразу генерируется token, добавляем IP в список.
Теперь на локальной машине нужно настроить доступ к API
export NAMECHEAP_USERNAME="USERNAME"
export NAMECHEAP_API_KEY="TOKEN"
export NAMECHEAP_SOURCEIP="MY-IP"
Команда получения через API выглядит таким образомОтступление про дополнительные флаги force и test. Будем использовать флаг -f (--force), что бы наши сертификаты генерировались заново, т.к. acme.sh видит уже сгенерированные сертификаты при их наличии не будет заново получать. Можно конечно просто сделать rm -rf ~/.acme.sh/domain/ вместо этого. Так же будем использовать флаг --test, что бы лишний раз не нагружать продакшн сервера let's encrypt. Вот такое сообщение мы получим, если после подтверждения в manual режиме попробуем другой режим.
[Ср мая 5 16:39:31 MSK 2021] *.itdog.info is already verified, skip dns-01.
acme.sh --issue --dns dns_namecheap -d *.itdog.info --test
Здесь после --dns мы добавляем имя провайдера.
Запускаем acme.sh
Раскрыть
В логе прям видно, что acme.sh добавляет TXT запись, ждёт немного, проверяет запись через доверенные DNS сервера, удаляет запись и скачивает сертификаты с ключом.
После первого запуска через API, acme.sh заносит env переменные c доступами к API себе в файл ~/.acme.sh/account.conf и вам не нужно каждый раз их экспортировать.
Отлично, получение автоматизировали, всё вроде классно. Но у этого метода есть свои недостатки:
- Если никто не написал скрипта для вашего провайдера\сервиса, то нужно либо писать, либо переезжать на другой провайдер
- А есть ли у вашего провайдера API?
- Поразмышляем немного об безопасности. Вот у меня в "открытую" лежит полный доступ к редактированию моего домена, если он каким-то образом попадёт в чужие руки, эти руки могут сделать что угодно. Эту проблему можно решить ограничим доступа в API, например по токену можно только добавлять\удалять txt записи _acme-challenge. Есть ли такая возможность у вашего провайдера? Я не встречал такого, наверное есть у какого-нибудь AWS конечно. Обычно уже хорошо если есть API, а токен один и даёт полный доступ
- У вас несколько доменов на разных провайдерах (сочувствую). Тут конечно можно настроить каждое API и сделать для каждого провайдера отдельный запуск acme.sh со своими переменными, но мне кажется это не очень удобным. Тем более если у одного из них отсутствует API или скрипт
- Кто-то просто не любит, что бы в DNS постоянно лазил какой-то скрипт и что-то добавлял\удалял
DNS aliase mode
Это модернизированный режим DNS API.Идея такая: Есть технический домен, через добавления TXT записей на котором мы подтверждаем владение основным доменом. т.е. acme.sh смотрит CNAME запись у основного домена, видит "перенаправление" на технический домен и идёт к нему проверять TXT запись. А дальше всё как в режиме DNS API.
Разберём последовательно. Для демонстрации я купил домен tech-domain.club, он выступает в качестве технического домена. В моём примере основной домен itdog.info располагается на namecheap, а техничский tech-domain.club я делегирую на Hetzner DNS, таким образом операции с записями будут производиться через API Hetzner'a.
В записях основного домена мы добавляем CNAME запись, которая указывает на технический домен
_acme-challenge CNAME _acme-challenge.tech-domain.club
Для провайдера с техническим доменом мы настраиваем доступ к API.
Экспортируем токен Hetzner export HETZNER_Token="TOKEN"
Команда выглядит так (-f и --test опять же для примера)
acme.sh --issue -d *.itdog.info --challenge-alias tech-domain.club --dns dns_hetzner -f --test
Раскрыть
CNAME запись удалять не нужно, она будет использоваться при каждом обновлении сертификата. Таким образом, вы можете иметь несколько доменов с настроенной CNAME записью на один технический домен и с его помощью получать сертификаты для всех.
Кстати, если вам нужно в одном файле иметь несколько сертификатов, например и itdog.info и wildcard *.itdog.info, то просто перечислите их с -d, например
acme.sh --issue --challenge-alias tech-domain.club --dns hetzner -d *.itdog.info -d itdog.info
Это правило действует и для других методов.
И так, что даёт нам этот режим:
- Если у вашего провайдера нет API или лень писать скрипт, возьмите технический домен, делегируйте его на сервис, который поддерживает acme.sh
- Если у вашего провайдера нет настройки прав для доступа через API, то технический домен тоже выручает. В случае, если наш token утечёт, у злоумышленника будет доступ только к вашему техническому домену, и если вы его используете только для acme.sh, то максимум что сможет сделать злоумышленник - получить ключ и сертификат для вашего домена. Это тоже неприятно и можно использовать, но это совершенно другой уровень угрозы, по сравнению с полным доступом к доменной зоне
- В ситуации с кучей доменов на одном или нескольких провайдерах, жизнь так же становится проще, когда все они просто имеют CNAME запись
Автоматизация получения и распространения сертификатов
Мы получили сертификаты, лежат они у нас красиво в ~/.acme.sh и никак не используются. Надо каким-то образом их распространять на сервера. Далее расскажу, как я это делаю с помощью ansible. Ansible используется и для получения\обновления и для распространения. Сразу предупреждаю, мои плейбуки простые как три копейки и заточены под определенную инфраструктуру. Playbooks, hosts на github.Мой сервер с ansible, уже имеет доступ ко всем необходимым серверам, на нём установлен acme.sh и реализовано два плейбука, на получение и распространение. Кстати, не забудьте закомментировать acme.sh в crontab, что бы не было лишних запросов и путаницы.
Playbook для получения сертификатов
В vars указывается только технический домен, эта переменная используется несколько раз. Токен от API вынесен в отдельный vars файл, что бы хранить его в зашифрованном виде в git. Task "Date and time" нужен для логирования, что бы понимать когда именно что-то пошло не так. Следующие два плейбука это простой shell, отличаются друг от друга количеством доменов в одном файле сертификата. Всем доменам, которым не нужно сочетать в себе обычный и wildcard домен, идут списком в loop.Домены, которые должны подходить как для обычного, так и для wildcard идут по втором taks, тоже с помощью loop. Если вам нужно например wilcard вида *.*.itdog.info, то просто добавьте ещё один -d и ещё один subkey в item. Опция ignore_errors необходима, потому что exit code 0 будет только 6 раз за год при обновлении сертификата, в остальное время будут сообщения о том, что сертификат не нужно обновлять, для ansible это ошибка на которой он будет останавливаться.
В одном плейбуке мы собираем всю нашу конфигурацию, доступы и все домены, которым необходим TLS, как минимум, это удобно - не надо копаться конфигах acme.sh. В случае изменения, например, токена, мы просто редактируем его в vars_files, а если нужно добавить ещё один домен\подомен, мы просто добавляем его в loop. Ну и в случае переноса сервера, не нужно переносить ~/.acme.sh, только плейбуки с vars_files взять из git.Для чего плейбук на получение? Ведь в acme.sh и так уже всё настроено!
Playbook для распространения сертификатов
Здесь нужно писать конечно под вашу инфраструктуру, поэтому повторюсь, показываю это для примера.Три типа серверов из моей инфраструктуры:
- tls-hosts - Обычный nginx установленный как пакет из стандартного репозитория
- tls-hosts-docker - Веб проект с тем же nginx, но уже в docker
- tls-hosts-docker-rename - Сторонний продукт, в который надо подкладывать сертификат и ключ с определённым именем в определённую директорию (например Harbor, Zabbix)
Во втором случае всё плюс-минус так же, но уже нужно сделать docker exec project-nginx -s reolad, т.е. уже другой handler.
В третьем случае у нас помимо handler в контейнере, ещё нужно дать сертификату с ключом определённое имя, потому что оно прописано в их конфигурации, которую лучше не трогать, чтоб не было проблем при обновлении.
В моём случае доменов много, пути, по которым сертификаты с ключами хранятся, различаются. Так же есть случаи когда у одного сервера несколько доменов. Что бы была возможность настроить для каждого хоста свой путь и необходимый домен, в hosts для каждого хоста заданы переменные пути и домена.
nginx.itdog.info tls_path=/etc/letsencrypt/*.itdog.info/ DOMAIN=*.itdog.info
Для случаев, когда доменов на сервере несколько, делается два хоста с разными именами и одинаковым ansible_host (Совет, как сделать лучше, приветствуется).
nginx.example.com-1 ansible_host=nginx.example.com tls_path=/etc/letsencrypt/*.example.com/ DOMAIN=example.com
nginx.example.com-2 ansible_host=nginx.example.com tls_path=/etc/letsencrypt/*.example.org/ DOMAIN=example.org
Для каждого типа серверов создана своя группа в hosts. Для каждой группы свои немного отличающиеся друг от друга tasks. Для tls-hosts-docker так же добавлена переменная с именем контейнера nginx. А для tls-hosts-docker-rename добавлена переменная, в которой задаётся конечное имя сертификата и ключа.
docker-zabbix.itdog.info tls_path=/root/docker-zabbix/zbx_env/etc/ssl/nginx/ DOMAIN=*.itdog.info CONTAINER=docker-zabbix_zabbix-web-nginx-pgsql_1 cert_name=ssl.crt key_name=ssl.key
Для nginx нужен fullchain и domain.key - копируются только они. Если файлы различаются, происходит копирование и срабатывает handler nginx -s reload. Так же есть проверка, перед тем как зарелоудить nginx, что это файл. У меня один раз был случай, в самом начале пользования acme.sh, скрипт вместо файла с сертификатом создал директорию. Прямо как traefik 1.7 создаёт acme.json директорию, вместо файла. Поэтому я сделал простую проверку. В идеале нужно делать проверку, что сертификат валидный и не просроченный, но для этого требуется иметь на каждом хосте python-pyOpenSSL.
Crontab
23 3 * * * /usr/bin/ansible-playbook /etc/ansible/playbook-get-tls.yml -v >> /var/log/get-tls.log23 4 * * * /usr/bin/ansible-playbook /etc/ansible/playbook-copy-tls.yml -v >> /var/log/copy-tls.log
Можно без проблем вызывать их каждый день, let's encrypt будет вежливо говорить, что пока не нужно обновляться. А когда придёт срок, сертификаты будут обновлены.
Мониторинг сертификатов
Надо следить за тем, что бы сертификаты на доменах обновлялись. Можно мониторить логи получения и распространения, и когда что-то идёт не так - кидать warning в системе мониторинга. Но мы пойдём с одной стороны на минималках, с другой более основательно и ещё помимо этого захватим другие исталяции, например с traefik, который живёт там сам по себе.Я использую zabbix и скрипт от @selivanov_pavel
Проверим с его помощью мой домен локально
koala@x220 ~/t/acme.sh-test> ./ssl_cert_check.sh expire itdog.info 443
41
41 день сертификат на itdog.info будет актуален. Сертификат в let's encrypt обновляется за 30 дней до протухания. А значит, например, если ему осталось жить 10 дней, значит что-то пошло не так и надо идти смотреть.
Темплейт состоит из одного item и одного trigger. Теплейт есть так же на github
ssl_cert_check.sh["expire","{HOST.NAME}","{$TLS_PORT}"]
В item две переменных, первая HOST.NAME берёт имя хоста, предполагается что у нас хост назван по доменному имени. Переменная $TLS_PORT по дефолту 443, но если нужно проверять на нестандартном порту, то записываем значение порта в macros.
Триггер тоже супер простой
{Check tls expire:ssl_cert_check.sh["expire","{HOST.NAME}","{$TLS_PORT}"].last()}<=10
Если полученное значение меньше 10ти - аллерт. Таким образом, мы узнаем если у нас начнут протухать сертификаты и будет 10 дней на починку.
Нормально работает?
Да, acme.sh + DNS API + ansible у меня крутится два года. acme.sh + DNS Alias + ansible крутится пол года. Проблемы возникали только, когда при тестировании доменов забыл отключить crontab и он принёс staging сертификат на прод. Такая проблема решается проверкой на валидность.Да, в идеале, ansible должен проверять перед копированием, что сертификат валидный и не просроченный. А система мониторинга проверять, помимо expire, валидность сертификатов.
Источник статьи: https://habr.com/ru/post/562026/