TLS и секреты
Где заканчивается TLS в поставляемой конфигурации ActionPulse, как выбрать между автоматическим сертификатом, своим сертификатом и своим прокси, какие секреты нужны установке и как их ротировать.
На этой странице
- Где заканчивается TLS
- Варианты терминации
- Автоматический сертификат
- Ваш сертификат в прокси из комплекта
- Свой прокси перед установкой
- Что доступно извне, а что только внутри
- Какие секреты нужны установке
- Подпись и шифрование приложения
- Пароли хранилищ
- Файлы, которые не живут в .env
- Ключи проверки подписи
- Одноразовые значения
- Как сгенерировать
- Ротация секретов
- Смена подписи токенов
- Пароли хранилищ
- Ключи импорта
- Ключи подписи резервных копий
- Ключ лицензии
- Чего не делать
- Что проверить
Установка отдаёт наружу ровно один сетевой периметр — обратный прокси из комплекта. Внутри всё остальное живёт в сетях без публикации портов. Эта страница отвечает на два вопроса, которые нужно решить до первого запуска: чем терминируется TLS и какие секреты придётся создать и потом ротировать.
Где заканчивается TLS
В комплекте есть готовая конфигурация обратного прокси. Она делает три вещи:
- Принимает трафик и терминирует TLS. Наружу публикуются только порты 80 и 443; шифрование заканчивается здесь, дальше внутрь идёт внутренняя сеть.
- Маршрутизирует запросы: пути приёма событий уходят на сервис приёма, пути API — на сервис кабинета, всё остальное — тоже на кабинет.
- Ставит заголовки безопасности на документы, которые не относятся к 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 |
| Выданные одноразовые коды подтверждения | Перестают подходить: они солятся производным ключом. Пользователь запрашивает новый код |
| Обезличенные отпечатки адресов в журналах | Меняются, поэтому старые и новые записи перестают сопоставляться по отпечатку |
| Браузерные токены и серверные ключи проектов | Не затрагиваются: они хранятся хешами и от этого секрета не зависят |
| Приглашения и ссылки восстановления пароля | Не затрагиваются: тоже хранятся хешами |
Порядок, который не ломает пользователей:
- Заранее — до первого включения 2FA и до штатной работы почты — задайте
отдельные
MFA_ENCRYPTION_KEYиEMAIL_OUTBOX_ENC_KEY. Тогда смена подписи токенов не касается ни семян, ни очереди писем. - Перед сменой убедитесь, что очередь писем пуста.
- Смените значение и перезапустите кабинет, приём событий и фоновые задачи одной операцией. Сервису записи в аналитическое хранилище этот секрет не выдаётся вовсе — он не выпускает и не проверяет токены.
- Проверьте вход, продление сессии, отправку письма и приём тестового события.
Пароли хранилищ
- Аналитическое хранилище. Пароли ролей приходят в конфигурацию пользователей из окружения и применяются при перезапуске хранилища: смените значения, перезапустите хранилище, затем сервисы. Исключение — пользователь SQL-консоли: он создаётся миграцией один раз, и повторный прогон миграций пароль не переписывает. Новое значение придётся синхронизировать в самом хранилище.
- PostgreSQL. Пароль записывается при инициализации тома. Меняйте его
в самой базе и в
.envодной процедурой, иначе сервисы потеряют доступ. - Кэш и очередь. Пароль живёт в двух местах: во внешнем файле и в соответствующем URL. Меняйте оба одновременно и перезапускайте сервер вместе с сервисами — иначе проверка перед запуском пройдёт, а сервисы не аутентифицируются.
- Логи и панели. Смена пароля доставки логов без перезапуска доставки выглядит как «логи просто перестали приходить». Проверка состояния контейнера доставки ловит это явно.
Ключи импорта
Ключи подписи передачи и подписи маркеров ротируются постепенно, в три фазы, и независимо друг от друга:
- Добавьте новый ключ в список прежних версий на обоих участвующих сервисах — на этой фазе он только принимается при проверке.
- Когда все реплики умеют проверять оба ключа, сделайте новый текущим, а старый перенесите в список прежних.
- Уберите старый ключ, когда истекли окна потока событий, повторов импорта и сверки. Больше четырёх прежних записей не поддерживается.
Ключ шифрования подключений к источникам импорта намеренно стабилен: сохранённые значения зашифрованы им без версии, поэтому постепенная ротация небезопасна. Его смена — отдельная процедура: остановить новые импорты, дождаться завершения текущих, перешифровать сохранённые настройки и перевести кабинет и фоновые задачи на новый ключ вместе.
Версию ключа обезличенных токенов субъектов нельзя переиспользовать с другим секретом: сервисы фиксируют отпечаток версии и отказываются работать при несовпадении.
Ключи подписи резервных копий
Ротация безопасна именно потому, что список открытых ключей — список:
- Опубликуйте новый открытый ключ во всех местах, где проверяется манифест.
- Переключите подписывающий контейнер на новый закрытый ключ.
- Уберите старый открытый ключ только тогда, когда ни один сохранённый снимок им больше не подписан.
Порядок «сначала подписать новым» ломает проверку и очистку по хранению: они проверяют подпись теми же ключами.
Ключ лицензии
Замена — это метод API от имени владельца, перезапуск не нужен: новый ключ
подхватывается всеми сервисами не позже чем через секунду. LICENSE_PUBKEY
при этом не меняется — это ключ вендора.
Чего не делать
- Один секрет на несколько окружений. Общий
JWT_SECRETу тестового и промышленного контура означает, что токен из тестового принимается в промышленном. То же с паролями хранилищ и ключами импорта. - Секрет в образе. Ни один секрет не должен попадать в слой образа, в
конфигурацию сборки или в репозиторий. В поставляемой схеме секреты приходят
только из окружения и из файлов оператора с правами
0600. - Секрет в журнале. Прокси из комплекта фильтрует и журнал доступа, и
журнал ошибок: значения параметров вида
token,key,password,secret,authorizationзаменяются наREDACTED, а заголовки авторизации, ключа API, cookie и адреса источника удаляются целиком. Это страховка от случайной утечки, а не разрешение передавать секреты в адресе запроса — и она действует только в этом прокси. Ваш внешний балансировщик, CDN и система мониторинга журналируют по своим правилам, проверьте их отдельно. - Секрет в аргументах команды. Он останется в истории оболочки и в списке процессов. Пароли передавайте переменными окружения или файлами, а не аргументами.
- Секрет в хранилище резервных копий.
.env, ключ лицензии, материал ключей шифрования и ключ исходящих уведомлений не должны лежать там же, где данные копий: восстановление в этом случае восстанавливает и доступ. - Заглушка из шаблона. Значения вида
CHANGE_ME…иunsetотклоняются проверкой перед запуском по списку известных заглушек — но метка-заглушка из файлов доступа к кэшу и очереди в этот список не входит, а короткий «временный» пароль из головы отклоняется только по длине. Генерируйте, а не придумывайте. - Оставленные
BOOTSTRAP_*. Пара для создания первого владельца — это постоянно действующий пароль, пока она заполнена. Очистите её сразу.
Что проверить
./preflight.sh .envзавершается строкой об успешной проверке — это единственная проверка, которая смотрит одновременно на длины секретов, права файлов и роли в строках подключения.- Снаружи отвечают только 80 и 443. Порты хранилищ, метрик, панелей и логов недоступны с любого адреса, кроме внутренних сетей установки.
- Сертификат отдаётся тот, который вы ожидаете, и срок его действия есть в вашем мониторинге.
- Проверка живости через прокси отвечает
{"status":"ok"}—curl -fsS "https://${PP_DOMAIN}/healthz". - В журнале прокси после тестового запроса с параметром вида
?token=…вместо значения стоитREDACTED, а заголовка авторизации нет вовсе. - Права:
.env—0600, каталог с файлами секретов —0700, закрытые ключи —0600, ни один из этих путей не является символьной ссылкой. - У каждой переменной из таблиц выше своё независимое значение, и ни одного значения из шаблона не осталось.
Полный перечень переменных с обязательностью и поведением при неверном значении — в статье Конфигурация.