Skip to content

0055. Встроенный HTTPS с перезагрузкой сертификата ​

Статус: accepted. Owner: Ian Panaev. Дата: 2026-10-02. Дополняет ADR 0048 и ADR 0052.

Контекст ​

Первый промышленный профиль — Standalone: один сервер с PostgreSQL и диском, клиенты (CI, агенты развёртывания, консоль) работают с других машин. До этого решения API слушал только HTTP, и для удалённого доступа требовался отдельный TLS-терминатор (nginx). На Windows-серверах это лишний компонент со своим обновлением и мониторингом, а неправильно настроенный proxy легко оставляет backend-порт открытым по HTTP. Сертификаты ACME и корпоративного УЦ обновляются каждые 60–90 дней; перезапуск API ради нового сертификата обрывает долгие передачи.

Решение ​

  • Встроенный HTTPS включается двумя переменными: ARKVORY_TLS_CERT_FILE (PEM, лист и при необходимости цепочка) и ARKVORY_TLS_KEY_FILE (PEM-ключ без пароля). Одна переменная без другой — ошибка конфигурации, а не тихий переход на HTTP. Минимальная версия — TLSv1.2 по умолчанию, допускается TLSv1.3.
  • При запуске сертификат и ключ проверяются: файлы не больше 1 MiB, PEM разбирается, ключ подходит к сертификату, срок не истёк. Проверка выполняется до открытия БД и других ресурсов, поэтому плохой сертификат останавливает запуск.
  • Fastify получает HTTPS-сервер через serverFactory. Тип listener остаётся http.Server, поэтому маршруты, hooks и тесты не различают транспорт.
  • Перезагрузка: файлы опрашиваются раз в ARKVORY_TLS_RELOAD_SECONDS (по умолчанию 300 с; 0 отключает). Опрос, а не fs.watch: события файловой системы ненадёжны на сетевых томах и при замене файла через rename. Новый материал проходит ту же проверку и устанавливается через setSecureContext. Новые соединения получают новый сертификат, открытые передачи продолжаются со старым. Ошибка обновления оставляет рабочий сертификат и пишет tls.reload_failed с константной причиной, без путей.
  • Наблюдаемость:
    • api.listening содержит tls;
    • за 14 дней до окончания срока раз в сутки пишется tls.expiring с notAfter и daysLeft;
    • метрика arkvory_tls_certificate_expiry_timestamp_seconds для алертов;
    • не-loopback HTTP без доверенного proxy даёт предупреждение http.plaintext_exposed.
  • Встроенный listener отправляет Strict-Transport-Security: max-age=31536000. За TLS-терминирующим proxy этот заголовок выставляет proxy.
  • Reverse proxy остаётся поддержанным вариантом; поведение ARKVORY_TRUSTED_PROXIES не меняется.

Альтернативы ​

  • Только reverse proxy: лишний компонент на Windows и риск открытого backend-порта; оставлен как вариант, но не как единственный путь.
  • Перезапуск API при обновлении сертификата: обрывает многочасовые передачи.
  • Встроенный ACME-клиент: тянет сетевую зависимость, хранение аккаунта и выбор challenge внутрь хранилища. Выпуск сертификата остаётся задачей внешнего инструмента (certbot, win-acme, корпоративный УЦ), Arkvory только подхватывает файлы.
  • mTLS для клиентов: сервисная авторизация уже построена на ключах с политиками; клиентские сертификаты потребуют отдельной модели отзыва. Не входит в это решение.

Последствия ​

Оператор отвечает за выпуск и продление сертификата и за права на ключ: доступ только у учётной записи службы. Зашифрованный паролем ключ не поддерживается: секрет для его расшифровки пришлось бы хранить рядом. HTTP/2 не включён. На нативной установке HTTPS включается командой arkvory configure --root <root> --tls-cert <pem> --tls-key <pem> [--listen-host <addr>] и выключается --tls-off. Команда проверяет файлы так же, как API, записывает config/runtime.json, перезапускает службы и требует проверки готовности по новому транспорту. При любой ошибке она возвращает прежнюю конфигурацию и перезапускает службы с ней. Пути не копируются, а указываются: обновления, записанные на месте, API подхватывает сам. Учётная запись службы должна иметь право читать оба файла.

Инструменты установки (проверка готовности после установки и обновления, создание владельца, healthcheck контейнера) обращаются к своему API через apps/deploy/src/local-api.ts. При встроенном TLS соединение проверяется по самому настроенному сертификату: файл из ARKVORY_TLS_CERT_FILE служит доверенным корнем (allowPartialTrustChain), а SHA-256 листа должен совпасть. Имя хоста при этом не проверяется, потому что инструменты подключаются к 127.0.0.1, а сертификат выписан на публичное имя. Проверка TLS не отключается. Режим Compose со встроенным TLS отклоняется с подсказкой использовать reverse proxy: путь к сертификату внутри контейнера недоступен инструментам на хосте.

Проверка ​

tests/tls.test.mjs:

  • проверка настроек и признака открытого HTTP;
  • отказ при несовпадающем ключе, истёкшем сертификате и не-PEM;
  • обслуживание по HTTPS с HSTS;
  • замена сертификата без перезапуска и сохранение рабочего сертификата при испорченном обновлении;
  • суточное предупреждение об окончании срока.

tests/metrics.test.mjs проверяет метрику срока действия. tests/local-api.test.mjs и tests/integration/deployment-health.test.mjs проверяют инструменты установки: HTTP, закреплённый сертификат, отказ на чужом сертификате и на HTTP к HTTPS-порту, отказ Compose с TLS. tests/tls-setup.test.mjs проверяет команду установщика: запись настроек, перезапуск с проверкой готовности, откат при сбое и отказ до изменений при неверных файлах. Тестовые сертификаты создаются в памяти (tests/tls-certificate.mjs) и не хранятся в репозитории.

Arkvory · ProAnimaStudio