Автоматическое продление wildcard-сертификатов на Synology NAS
Проблема
В решениях компании Synology присутствует vendor-lock на использование wildcard-сертификатов Let’s Encrypt. Решение «из коробки» подразумевает использование wildcard-сертификатов только для динамических DNS компании Synology. На это накладывается требование держать открытым 80 порт, без которого автопродление не работает. В результате раз в три месяца надо открыть порты, обновить сертификат, закрыть порты, вот это вот всё.
Типичные решения?
Сразу стоит отметить, почему этот мануал актуален и почему нельзя, совершенно закономерно, установить acme в файловую систему и закрыть вопрос раз и навсегда. Моё первое решение было именно таким: установить acme из консоли из под root, установить cronjob на обновление сертификата и подтягивание обновлённых сертификатов из дебрей файловой системы в директорию, доступную из GUI для последующего применения. Оказалось, ребята из Synology тоже не дураки и регулярно очищают файловую систему от неожиданных юзерскриптов, даже если они лежат в /root. По крайней мере, когда вчера автоматика запустилась и вместо пакета сертификата выдала **not found** — было обидненько. Да, можно просто регулярно переустанавливать acme, но зачем тогда вообще что-то автоматизировать, если всё равно надо раз в N времени пинать его и переделывать?
Свой вариант
Свой вариант реализовывался исключительно средствами Synology, чтобы избежать проблемы с очисткой упомянутой выше. Используется Docker, официальный контейнер acme.sh и встроенные в GUI cronjobs. По отдельности всё просто, но свести воедино — есть нюансы.
Мануал рассчитан на тех, кто бегло разбирается в оболочке Synology DSM 7.1.
Докер
Находим и выкачиваем необходимый контейнер. Контейнер очень лёгкий, основан на alpine и на момент написания статьи весит всего 27 мегабайт.
Создаём директорию для файлов контейнера, где будут лежать конфиги и куда будут падать сгенерированные сертификаты. Директория может лежать где угодно. Обычно, если контейнеров много люди рано или поздно приходят к какому либо типичному решению вроде /docker/%containerName%.
При создании контейнера нужно выбрать скачавшийся образ и далее во вкладке Сеть нужно выбрать опцию Использовать ту же сеть, что и сеть хоста Docker. Если упустить этот момент, в дальнейшем это выльется в нетривиальный траблшутинг по общению acme с внешним миром, внешние файрволлы, внутренние файрволлы, что-то обратный прокси перехватит, динамические адреса контейнеров ни у кого не спрашивая ворвутся с конфликтом адресов в уже существующую подсеть (Synology использует для сетей контейнеризации подсети 172.17.0.0/16, 172.18.0.0/16 и.т.д с повышением второго октета на каждую новую сеть) — масса ненужного веселья.
Далее, стоит включить опцию Включить автоматический перезапуск, чтобы не заморачиваться каждый раз пинать контейнер после каждой перезагрузки. После чего нажать на кнопку Дополнительные настройки и во вкладке Команда выполнения в поле Команда установить параметр daemon.
Ключ для запуска acme в режиме демона явно упоминается в мануале и используется для того, чтобы можно было обратиться к acme через внешний планировщик задач. Малозаметная опция в малозаметном месте, которую легко упустить и непросто отдебажить. Synology будто специально прячут такие штуки поглубже в настройках, на третьем уровне вложенности.
После применения настроек, продолжая устанавливать контейнер, перейдя во вкладку Настройки тома надо нажать кнопку Добавить папку и привязать созданную ранее директорию к директории /acme.sh внутри контейнера.
Привязка директории на Synology к директории внутри контейнера.
На этом настройка контейнера завершена, система предложит проверить все настройки и запустить конейнер после создания.
Последующие настройки производятся в терминале контейнера. Тут стоит явно обратить внимание лишь на два момента:
Окно терминала по умолчанию является терминалом без оболочки. Не зная этого нюанса можно биться в стену не понимая что происходит.
При нажатии кнопки Создать создаётся новый терминал с оболочкой bash. Но, так как контейнер acme собран на alpine, в нем нет bash — используется оболочка sh. Нужно при создании терминала выбирать Запустить при помощи команды и явно указывать sh.
Acme.sh
Нюансам настройки посвящено пол-интернета, поэтому стоит по быстрому пробежаться по ключевым моментам.
acme.sh --set-default-ca --server letsencrypt
По умолчанию, начиная с осени 2021 года acme по умолчанию переключился на работу с сертификатами ZeroSSL. Многие мануалы писались до 2021 года и после этого изменения не потрудились эти самые мануалы обновить. Не переключив вручную на letsencrypt можно долго недоумевать почему выхлоп скрипта не соответствует мануалам.
Далее я предпочитаю использовать DNS manual mode. Во первых, у Synology грустно с DNS API — всё же это в первую очередь NAS, а не домашний хостинг. Во вторых, не хотелось бы связываться с HTTP mode из-за упомянутой выше проблемы с пробросом порта 80 прямо на конкретный контейнер.
На этом этапе acme сделала дополнительную нетривиальную проверку в виде ключа --yes-I-know-dns-manual-mode-enough-go-ahead-please, без которого acme не запускается, но чтобы понять почему надо рыть дебаг. Хотя по факту он просто подтверждает «да, я прочитал мануал». Опять же, в мануале acme это уже есть, другие мануалы не затрагивают использование wildcard-сертификатов, как результат: это можно найти, но случай частный, который нужно просто один раз запомнить.
acme.sh --issue -d *.domain.name --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
На этом этапе необходимые сертификаты создадутся в директории докер-контейнера, но их ещё нельзя использовать, потому что надо явно прописать DNS TXT запись _acme-challenge.domain.name. Ошибиться сложно, acme caм подскажет что и куда прописывать.
Пока делал описание терминал отвалился по таймауту, отсюда «блеклость».
После прописывания DNS TXT выжидаем какое-то время и вызываем
acme.sh -d *.domain.name --renew --yes-I-know-dns-manual-mode-enough-go-ahead-please
Всё работает, сертификаты создаются. Но нужна автоматизация.
Автоматизация
Всё заметно упростилось с появлением встроенного решения от acme, но есть нюансы.
Для начала — нужен пользователь с административными правами. Можно использовать встроенную запись администратора, можно свою. Я рекомендую создать нового пользователя, добавить в группу Administrators, явно запретить доступ ко всем приложениям и шарам и установить запрет на смену пароля. Исключительно по причине того, что пароль указывается в конфигурационном файле acme и лучше иметь отдельного пользователя с гигантским статическим паролем, чем не забывать модифицировать файл при каждом изменении своего.
Далее, нужно остановить созданный контейнер, сходить в директорию, которую мы подключали ранее и модифицировать файл account.conf, добавив в него данные про Synology NAS.
AUTO_UPGRADE='1'
DEFAULT_ACME_SERVER='https://acme-v02.api.letsencrypt.org/directory'
# Ниже добавляем:
export SYNO_Username="acme"
export SYNO_Password="myStrongPassword"
export SYNO_Certificate="acme.sh (docker) certificate"
export SYNO_Create=1
Дополнительное уточнение по параметру export SYNO_Create=1. Я советую включать его при первичной настройке, а потом, когда сертификаты установились, отключать его от греха подальше. Включение этой опции разрешает установку новых, несуществующих сертификатов в систему, отключение же разрешает лишь продление уже существующих. Даже при условии, что на прошлом шаге при настройке контейнера созданные сертификаты уже были добавлены в систему вручную есть смысл явно перезаписывать их ещё раз уже при помощи опции deploy, потому что можно столкнуться с тем, что она положит копии сертификатов рядом с оригиналами, они задвоятся, в результате при видимости успешной работы без каких-либо ошибок через три месяца сертификаты протухнут, потому что при кажущейся идентичности обновляются не оригиналы, а клоны.
Далее сохраняем файл, заново запускаем контейнер и после в терминале вводим
acme.sh --deploy -d *.domain.name --deploy-hook synology_dsm
Если всё прошло гладко, увидим Success.Сертификат успешно появился в DSM.
Продление по расписанию
Идём в Настройки → Планировщик задач → Создать → Запланированная задача → Скрипт заданный пользователем. Вместо ContainerName подставить имя созданного ранее контейнера.
docker exec ContainerName acme.sh --renew -d *.domain.name --yes-I-know-dns-manual-mode-enough-go-ahead-please
Неочевидный нюанс: стоит явно указывать, что задача должна выполняться из под root. У любого пользователя с самыми расширенными полномочиями не хватает прав на выполнение скрипта внутри конейнера, при этом система явно не генерирует ошибок, как результат решение проблемы ищется совершенно не там. Спасибо неизвестному чуваку, который на ныне умершей борде где-то в 2016 году обнаружил это нетривиальное поведение.
К слову, на любом этапе можно обратиться в поддержку Synology, которая после всестороннего изучения вопроса вынесет вердикт, что:
Such option is not implemented yet.
Using wildcard-certificates is allowed only with Synology … бла-бла-бла вендор-лок.
Please use ONLY Synology’s options, because … ну вы поняли.
Тем не менее, всё работает.
Мануал не претендует на оригинальность, всё можно найти в разрозненных источниках и набить кучу своих шишек, но зачем?