К содержанию
ActionPulse

TLS и секреты

Где заканчивается TLS в поставляемой конфигурации ActionPulse, как выбрать между автоматическим сертификатом, своим сертификатом и своим прокси, какие секреты нужны установке и как их ротировать.

Кому
Инженерам по безопасности и операторам
Проверено
На этой странице

Установка отдаёт наружу ровно один сетевой периметр — обратный прокси из комплекта. Внутри всё остальное живёт в сетях без публикации портов. Эта страница отвечает на два вопроса, которые нужно решить до первого запуска: чем терминируется TLS и какие секреты придётся создать и потом ротировать.

Где заканчивается TLS

В комплекте есть готовая конфигурация обратного прокси. Она делает три вещи:

  1. Принимает трафик и терминирует TLS. Наружу публикуются только порты 80 и 443; шифрование заканчивается здесь, дальше внутрь идёт внутренняя сеть.
  2. Маршрутизирует запросы: пути приёма событий уходят на сервис приёма, пути API — на сервис кабинета, всё остальное — тоже на кабинет.
  3. Ставит заголовки безопасности на документы, которые не относятся к API, и фильтрует свои журналы.

Сертификаты и данные учётной записи удостоверяющего центра хранятся в отдельном томе прокси. Сам прокси запущен непривилегированным пользователем, с файловой системой только для чтения и снятыми capability; право занять порты 80 и 443 выдано отдельной настройкой ядра, а не правами root.

Заголовки, которые получают документы кабинета: политика безопасности контента, запрет встраивания в рамку, запрет угадывания типа содержимого и отказ от передачи адреса источника. Страницы входа по ссылке — приглашение, регистрация, подтверждение адреса, восстановление пароля и публичная ссылка — дополнительно получают запрет кэширования: в их адресах могут быть одноразовые токены.

Ещё одно следствие, о котором лучше знать заранее: кабинет и API обязаны быть на одном сайте. Защита от межсайтовых запросов построена на cookie с областью одного сайта и проверке источника, а передача учётных данных между источниками разрешена только явному списку. В поставляемой конфигурации это выполнено само собой — кабинет, API и приём событий стоят на одном origin.

Варианты терминации

Автоматический сертификат

Базовый вариант: в PP_DOMAIN — ваш реальный домен, и прокси сам получает и продлевает публичный сертификат.

Что для этого нужно:

  • запись DNS домена указывает на узел установки;
  • порты 80 и 443 доступны из интернета — 80 используется и для перенаправления на HTTPS, и для проверки владения доменом;
  • узлу разрешены исходящие HTTPS-соединения к удостоверяющему центру;
  • APP_BASE_URL и адрес приёма событий записаны со схемой https.

Схема https в APP_BASE_URL здесь не косметика: от неё зависит флаг Secure у cookie аутентификации. Адрес для уведомлений об истечении сертификата в поставляемой конфигурации не задан, поэтому писем от удостоверяющего центра не будет — следите за сроком своим мониторингом.

Ваш сертификат в прокси из комплекта

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

Штатный способ работать со своим сертификатом — терминировать TLS на собственном прокси перед установкой, следующий раздел про это. Если же вы хотите оставить прокси из комплекта, ниже минимальный набор правок — но это правка поставляемой конфигурации, и дальше вы отвечаете за неё сами: следующая версия комплекта принесёт свой файл конфигурации, и вашу правку придётся перенести.

Меняются ровно две вещи. В блоке сайта появляется указание на файлы сертификата и закрытого ключа:

{$PP_DOMAIN:localhost} {
	tls /etc/caddy/tls/fullchain.pem /etc/caddy/tls/privkey.pem
	# остальное содержимое блока оставьте без изменений
}

И эти файлы монтируются в контейнер прокси только для чтения:

    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./secrets/tls:/etc/caddy/tls:ro

После правки: закрытый ключ — права 0600 и владелец, совпадающий с пользователем прокси в контейнере; затем docker compose -f docker-compose.onprem.yml --env-file .env config --quiet и перезапуск только сервиса прокси. С явно указанным сертификатом автоматический выпуск для этого сайта больше не выполняется — продление становится вашей задачей.

Свой прокси перед установкой

Вариант для тех, у кого TLS терминируется на общем балансировщике или ingress-контроллере. Тогда ваш прокси принимает HTTPS и проксирует на узел установки, а прокси из комплекта остаётся внутренним.

Здесь есть один пункт, который ломается тише всего:

Остальное: публичные адреса (APP_BASE_URL, адрес приёма событий, CORS_ORIGINS) записывайте так, как их видит браузер — то есть внешним адресом вашего прокси, а не внутренним. Если вы решите не публиковать порты прокси из комплекта наружу, а ходить в него по внутренней сети, помните, что требование HTTPS браузеру ставит тот прокси, который отдаёт документ.

Что доступно извне, а что только внутри

Наружу — только порты 80 и 443 обратного прокси: через них идут кабинет, API и приём событий. Всё остальное доступно исключительно из внутренних сетей.

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

Доступ администратора к этим интерфейсам — контролируемый временный проброс порта или VPN. Постоянной публикации для них не предусмотрено.

Какие секреты нужны установке

Значений здесь нет и быть не может: каждое из них вы генерируете сами. Требования к формату и длине проверяют ./preflight.sh .env и старт сервисов, поэтому «пока поставлю короткое» не сработает.

Подпись и шифрование приложения

Переменная Требование Назначение
JWT_SECRET не короче 32 символов, без пробелов, не заглушка, минимум 8 различных символов; сгенерируйте своё Подпись токенов доступа; из него же выводятся ключи там, где не задан отдельный
MFA_ENCRYPTION_KEY не короче 32 символов, если задана; сгенерируйте своё Шифрование семян второго фактора. Задайте до первого включения 2FA
EMAIL_OUTBOX_ENC_KEY не короче 32 символов, если задана; сгенерируйте своё Шифрование содержимого очереди писем. Пусто — выводится из JWT_SECRET
ALERT_WEBHOOK_ENC_KEY ровно 32 байта до кодирования base64; сгенерируйте своё Шифрование секрета подписи исходящих уведомлений
IMPORT_ENC_KEY 32 байта до кодирования base64; сгенерируйте своё Шифрование сохранённых подключений к источникам импорта
IMPORT_HMAC_CURRENT_SECRET не короче 32 символов, без запятых и пробелов; сгенерируйте своё Подпись передачи импортированных строк между сервисами
IMPORT_FENCE_HMAC_CURRENT_SECRET то же Подпись маркеров устойчивости импорта
IMPORT_PRIVACY_HMAC_CURRENT_SECRET то же Устойчивые обезличенные токены субъектов для удаления данных

Четыре последних значения обязаны быть попарно различными и отличаться от JWT_SECRET — приложение сверяет это на старте и отказывается работать при совпадении. Резервного вывода из JWT_SECRET для них не существует.

Одно исключение из «проверяется до старта»: длину ключа исходящих уведомлений никто на старте не смотрит. Неверное значение проявится позже — в момент, когда вы создадите канал уведомлений с секретом подписи, и при доставке.

Пароли хранилищ

Переменная Требование Назначение
PG_PASSWORD не короче 24 символов; сгенерируйте своё Общая учётная запись PostgreSQL
CH_DEFAULT_PASSWORD то же Совместимая учётная запись аналитического хранилища
CH_MIGRATE_PASSWORD то же Роль миграций
CH_WRITE_PASSWORD то же Роль записи
CH_READ_PASSWORD то же Роль чтения
CH_BACKUP_PASSWORD то же Роль резервного копирования, только чтение
CH_CONSOLE_PASSWORD то же Пользователь SQL-консоли
Учётные данные внутри REDIS_URL не короче 16 символов; сгенерируйте своё Доступ к кэшу
Учётные данные внутри NATS_URL не короче 16 символов; сгенерируйте своё Доступ к очереди
GRAFANA_ADMIN_PASSWORD не короче 24 символов; сгенерируйте своё Администратор панелей
ELASTIC_PASSWORD то же Хранилище логов
KIBANA_SYSTEM_PASSWORD то же Служебная учётная запись интерфейса логов
ELASTICSEARCH_FILEBEAT_PASSWORD то же Учётная запись доставки логов

Каждое значение — независимое. Один пароль на две роли обесценивает всё разделение прав: смысл отдельных ролей в том, что утечка одной не даёт прав другой.

Файлы, которые не живут в .env

Список доступа кэша, конфигурация очереди с аутентификацией и закрытые ключи внутреннего TLS передаются файлами и монтируются как файловые секреты.

Переменная Требование Назначение
REDIS_ACL_FILE обычный файл, не симлинк, права 0600 Список доступа кэша: анонимный пользователь выключен, у каждого сервиса свой пароль
NATS_CONFIG_FILE то же Конфигурация очереди с аутентификацией и включённым JetStream
ELASTIC_CA_FILE обычный непустой файл Удостоверяющий центр внутреннего TLS логов
ELASTICSEARCH_CERT_FILE, KIBANA_CERT_FILE то же Сертификаты хранилища и интерфейса логов
ELASTICSEARCH_KEY_FILE, KIBANA_KEY_FILE права 0600 Их закрытые ключи

Примеры файла доступа и конфигурации очереди приходят в комплекте намеренно нерабочими: вместо каждого пароля в них стоит явная метка-заглушка. Подставьте свои значения и синхронизируйте их с соответствующими адресами в .env.

Ключи проверки подписи

Переменная Требование Назначение
LICENSE_PUBKEY публичный ключ вендора, не секрет, но обязателен Офлайн-проверка лицензии
BACKUP_MANIFEST_SIGNING_KEY формат идентификатор:base64; сгенерируйте своё Закрытый ключ подписи манифеста резервной копии
BACKUP_MANIFEST_PUBLIC_KEYS список того же формата через запятую Открытые ключи, которыми проверяется манифест

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

Сам ключ лицензии в .env не хранится и секретом установки не является: он загружается владельцем через кабинет или методом API.

Одноразовые значения

BOOTSTRAP_EMAIL и BOOTSTRAP_PASSWORD создают первого владельца при первом старте. Пароль — не короче 16 символов, адрес — канонический, в нижнем регистре, не на демонстрационном домене. Обе переменные должны быть заполнены или пусты вместе. После появления владельца очистите оба значения и перезапустите сервис кабинета.

Как сгенерировать

По одному независимому значению на каждую строку таблиц выше:

openssl rand -base64 32

Такой вызов даёт 44 символа — этого хватает и там, где требуется 24, и там, где требуется 32, и он же даёт ровно 32 байта до кодирования там, где это требование точное.

Пара ключей подписи манифеста резервной копии создаётся утилитой из образа резервного копирования — это команда комплекта, репозиторий продукта для неё не нужен:

docker run --rm --entrypoint /opt/actionpulse-backup/apbackupctl \
  "$PP_BACKUP_IMAGE_REF" keygen -key-id 2026-07

Идентификатор ключа выбираете вы; удобно брать год и месяц выпуска — по нему потом видно, какой ключ пора выводить из обращения.

Ротация секретов

Смена подписи токенов

JWT_SECRET — самый связный секрет установки, потому что из него выводятся ключи там, где не задан отдельный. Что происходит при смене:

Что Что с ним станет
Токены доступа кабинета Перестают проверяться немедленно
Сессии пользователей Сохраняются: обновление сессии идёт по cookie, а сами refresh-токены хранятся хешами и от этого секрета не зависят
Семена второго фактора Нечитаемы, если MFA_ENCRYPTION_KEY не задан отдельно. Пользователям придётся подключать 2FA заново
Недоставленные письма Нечитаемы, если EMAIL_OUTBOX_ENC_KEY не задан отдельно. Задача доставки переведёт такие строки в терминальное состояние с причиной payload_undecryptable
Выданные одноразовые коды подтверждения Перестают подходить: они солятся производным ключом. Пользователь запрашивает новый код
Обезличенные отпечатки адресов в журналах Меняются, поэтому старые и новые записи перестают сопоставляться по отпечатку
Браузерные токены и серверные ключи проектов Не затрагиваются: они хранятся хешами и от этого секрета не зависят
Приглашения и ссылки восстановления пароля Не затрагиваются: тоже хранятся хешами

Порядок, который не ломает пользователей:

  1. Заранее — до первого включения 2FA и до штатной работы почты — задайте отдельные MFA_ENCRYPTION_KEY и EMAIL_OUTBOX_ENC_KEY. Тогда смена подписи токенов не касается ни семян, ни очереди писем.
  2. Перед сменой убедитесь, что очередь писем пуста.
  3. Смените значение и перезапустите кабинет, приём событий и фоновые задачи одной операцией. Сервису записи в аналитическое хранилище этот секрет не выдаётся вовсе — он не выпускает и не проверяет токены.
  4. Проверьте вход, продление сессии, отправку письма и приём тестового события.

Пароли хранилищ

  • Аналитическое хранилище. Пароли ролей приходят в конфигурацию пользователей из окружения и применяются при перезапуске хранилища: смените значения, перезапустите хранилище, затем сервисы. Исключение — пользователь SQL-консоли: он создаётся миграцией один раз, и повторный прогон миграций пароль не переписывает. Новое значение придётся синхронизировать в самом хранилище.
  • PostgreSQL. Пароль записывается при инициализации тома. Меняйте его в самой базе и в .env одной процедурой, иначе сервисы потеряют доступ.
  • Кэш и очередь. Пароль живёт в двух местах: во внешнем файле и в соответствующем URL. Меняйте оба одновременно и перезапускайте сервер вместе с сервисами — иначе проверка перед запуском пройдёт, а сервисы не аутентифицируются.
  • Логи и панели. Смена пароля доставки логов без перезапуска доставки выглядит как «логи просто перестали приходить». Проверка состояния контейнера доставки ловит это явно.

Ключи импорта

Ключи подписи передачи и подписи маркеров ротируются постепенно, в три фазы, и независимо друг от друга:

  1. Добавьте новый ключ в список прежних версий на обоих участвующих сервисах — на этой фазе он только принимается при проверке.
  2. Когда все реплики умеют проверять оба ключа, сделайте новый текущим, а старый перенесите в список прежних.
  3. Уберите старый ключ, когда истекли окна потока событий, повторов импорта и сверки. Больше четырёх прежних записей не поддерживается.

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

Версию ключа обезличенных токенов субъектов нельзя переиспользовать с другим секретом: сервисы фиксируют отпечаток версии и отказываются работать при несовпадении.

Ключи подписи резервных копий

Ротация безопасна именно потому, что список открытых ключей — список:

  1. Опубликуйте новый открытый ключ во всех местах, где проверяется манифест.
  2. Переключите подписывающий контейнер на новый закрытый ключ.
  3. Уберите старый открытый ключ только тогда, когда ни один сохранённый снимок им больше не подписан.

Порядок «сначала подписать новым» ломает проверку и очистку по хранению: они проверяют подпись теми же ключами.

Ключ лицензии

Замена — это метод API от имени владельца, перезапуск не нужен: новый ключ подхватывается всеми сервисами не позже чем через секунду. LICENSE_PUBKEY при этом не меняется — это ключ вендора.

Чего не делать

  • Один секрет на несколько окружений. Общий JWT_SECRET у тестового и промышленного контура означает, что токен из тестового принимается в промышленном. То же с паролями хранилищ и ключами импорта.
  • Секрет в образе. Ни один секрет не должен попадать в слой образа, в конфигурацию сборки или в репозиторий. В поставляемой схеме секреты приходят только из окружения и из файлов оператора с правами 0600.
  • Секрет в журнале. Прокси из комплекта фильтрует и журнал доступа, и журнал ошибок: значения параметров вида token, key, password, secret, authorization заменяются на REDACTED, а заголовки авторизации, ключа API, cookie и адреса источника удаляются целиком. Это страховка от случайной утечки, а не разрешение передавать секреты в адресе запроса — и она действует только в этом прокси. Ваш внешний балансировщик, CDN и система мониторинга журналируют по своим правилам, проверьте их отдельно.
  • Секрет в аргументах команды. Он останется в истории оболочки и в списке процессов. Пароли передавайте переменными окружения или файлами, а не аргументами.
  • Секрет в хранилище резервных копий. .env, ключ лицензии, материал ключей шифрования и ключ исходящих уведомлений не должны лежать там же, где данные копий: восстановление в этом случае восстанавливает и доступ.
  • Заглушка из шаблона. Значения вида CHANGE_ME… и unset отклоняются проверкой перед запуском по списку известных заглушек — но метка-заглушка из файлов доступа к кэшу и очереди в этот список не входит, а короткий «временный» пароль из головы отклоняется только по длине. Генерируйте, а не придумывайте.
  • Оставленные BOOTSTRAP_*. Пара для создания первого владельца — это постоянно действующий пароль, пока она заполнена. Очистите её сразу.

Что проверить

  1. ./preflight.sh .env завершается строкой об успешной проверке — это единственная проверка, которая смотрит одновременно на длины секретов, права файлов и роли в строках подключения.
  2. Снаружи отвечают только 80 и 443. Порты хранилищ, метрик, панелей и логов недоступны с любого адреса, кроме внутренних сетей установки.
  3. Сертификат отдаётся тот, который вы ожидаете, и срок его действия есть в вашем мониторинге.
  4. Проверка живости через прокси отвечает {"status":"ok"}curl -fsS "https://${PP_DOMAIN}/healthz".
  5. В журнале прокси после тестового запроса с параметром вида ?token=… вместо значения стоит REDACTED, а заголовка авторизации нет вовсе.
  6. Права: .env0600, каталог с файлами секретов — 0700, закрытые ключи — 0600, ни один из этих путей не является символьной ссылкой.
  7. У каждой переменной из таблиц выше своё независимое значение, и ни одного значения из шаблона не осталось.

Полный перечень переменных с обязательностью и поведением при неверном значении — в статье Конфигурация.