TLS и сертификаты
Режимы http, self-signed, own-cert и ACME для Plane; у ноды HTTPS работает сразу — как оставить его, взять сертификат Plane или выпустить свой.
TLS в системе два независимых: свой у Plane (админка и API) и свой у ноды (видео и её API). Браузер обращается к обоим напрямую, поэтому у каждого должен быть рабочий сертификат. У ноды он есть с первого запуска — самоподписанный, на её собственное имя; ниже разобрано, как оставить его или заменить на доверенный.
Если админка открыта по https://, а нода отдаёт видео по http://, браузер заблокирует
поток: страница загрузится, а плеер останется пустым. Правило простое — обе стороны по HTTPS
либо обе по HTTP. У ноды HTTPS работает с первого запуска, поэтому так получается только если
ноду намеренно завели по http:// — например за прокси, который сам терминирует TLS.
Режимы доступа Plane#
Режим выбирается при установке и хранится в /etc/hastreamer-cctv/plane.toml в секции [tls].
Названия в установщике и значения в файле различаются — соответствие такое:
| Ответ установщику | [tls] mode |
Слушатель | public_url |
Обязательные ключи | Когда применять |
|---|---|---|---|---|---|
http |
off |
обычный HTTP | http:// |
— | Изолированная сеть, TLS нигде не нужен |
| — (только правкой файла) | external |
обычный HTTP | https:// |
— | Впереди реверс-прокси, который сам терминирует TLS |
own-cert |
pem |
HTTPS | https:// |
cert, key |
Сертификат уже куплен или выдан корпоративным центром |
self-signed |
self_signed |
HTTPS | https:// |
hosts |
Быстрый старт и тест в локальной сети |
acme |
acme |
HTTPS | https:// |
hosts, желательно acme_contact |
Публичный домен — рекомендуемый режим |
Порт по умолчанию: 8080 для http, 443 для всех режимов с TLS.
public_url обязана соответствовать режимуPlane проверяет это при старте и отказывается запускаться при несоответствии:
tls.mode=off requires an http:// public_url tls.mode=pem requires cert and key paths tls.mode=self_signed requires at least one hostname tls.hosts must include the public_url hostname "cctv.example.com"
Отдельно запомните разницу off и external: за реверс-прокси нужен именно external —
он отдаёт обычный HTTP, но объявляет https:// во всех ссылках, редиректах и обращениях нод.
С off такой public_url будет отвергнут.
Все параметры [tls] читаются один раз при старте. Любое изменение вступает в силу только
после sudo systemctl restart hastreamer-cctv-plane.
Переключение self-signed → ACME#
Типовой сценарий: сначала подняли Plane с самоподписанным сертификатом, потом получили домен. Переход поддерживается и не требует переустановки.
Сначала убедитесь, что выполнены оба условия ACME:
getent hosts cctv.example.com # должен вернуть внешний IP этого сервера curl -s https://api.ipify.org; echo # фактический внешний IP сервера — адреса должны совпасть sudo ss -tlnp | grep -w ':80 ' # порт 80 должен быть свободен (кроме самого Plane)
Правьте plane.toml точечно — в файле лежит пароль базы, пересоздавать его нельзя:
sudo sed -i 's/^mode = "self_signed"/mode = "acme"/' /etc/hastreamer-cctv/plane.toml grep -q '^acme_contact' /etc/hastreamer-cctv/plane.toml || \ sudo tee -a /etc/hastreamer-cctv/plane.toml >/dev/null <<'EOF' acme_contact = "admin@example.com" acme_listen = "0.0.0.0:80" EOF sudo systemctl restart hastreamer-cctv-plane
sudo journalctl -u hastreamer-cctv-plane -b | grep -iE "issuing via ACME|issuance.*succeeded"
Ожидаемые строки:
persisted plane identity is self-signed, not CA-issued — issuing via ACME ACME plane identity issuance/renewal succeeded and was activated
И проверка снаружи — издатель должен быть настоящим центром сертификации:
echo | openssl s_client -connect cctv.example.com:443 -servername cctv.example.com 2>/dev/null \ | openssl x509 -noout -issuer -dates
Обратный переход выполняется так же: mode = "self_signed" и перезапуск. Строки acme_*
можно оставить — в режиме self_signed они не используются.
Требования ACME и продление#
Проверка Let's Encrypt (HTTP-01) устроена одинаково для Plane и для ноды:
- Домен — публичный и резолвится на этот хост. IP-адреса и wildcard-имена не подходят:
ACME HTTP-01 requires DNS hostnames, not IP literals. - Порт 80 доступен из интернета — на него приходит запрос
http://cctv.example.com/.well-known/acme-challenge/<токен>. - Проверка отвечает на отдельном обычном HTTP-слушателе (
acme_listen, по умолчанию0.0.0.0:80). Он обязан отличаться от основногоlisten, и порт 80 не должен занимать другой веб-сервер. - Разрешён исходящий HTTPS к центру сертификации.
Продление полностью автоматическое и не настраивается:
| Что | Значение |
|---|---|
| Продление начинается | за 30 суток до истечения |
| Периодичность проверки | каждые 12 часов |
| Повтор после неудачи | раз в час |
Продление использует ту же проверку, что и первый выпуск. Если после установки закрыть порт 80, сертификат перестанет обновляться, и примерно через два месяца сайт станет недоступен по HTTPS.
Если выпустить сертификат не удалось, Plane всё равно поднимется — на временном
самоподписанном сертификате (браузер предупредит) и продолжит попытки в фоне. В API это видно
как temporary_self_signed: true. Как только DNS и порт 80 исправлены, настоящий сертификат
подставляется автоматически, без перезапуска сервиса.
Свой сертификат Plane (own-cert)#
[tls] mode = "pem" cert = "/etc/hastreamer-cctv/plane-cert.pem" key = "/etc/hastreamer-cctv/plane-key.pem"
cert — цепочка в PEM: сначала сертификат сервера, затем промежуточные. key — приватный ключ.
Список hosts в этом режиме не нужен: имена берутся из самого сертификата.
# права на ключ — только root sudo chown root:root /etc/hastreamer-cctv/plane-key.pem sudo chmod 600 /etc/hastreamer-cctv/plane-key.pem # проверка, что ключ и сертификат — пара (обе строки должны совпасть) openssl x509 -noout -pubkey -in /etc/hastreamer-cctv/plane-cert.pem | openssl sha256 openssl pkey -pubout -in /etc/hastreamer-cctv/plane-key.pem | openssl sha256
Автоматики нет. После замены файлов обязательно перезапустите сервис:
sudo systemctl restart hastreamer-cctv-plane. Заведите напоминание за 30 суток до истечения —
проверить срок можно командой
openssl x509 -noout -enddate -in /etc/hastreamer-cctv/plane-cert.pem.
Plane за реверс-прокси (external)#
Минимальная конфигурация: Plane слушает только петлевой интерфейс, TLS делает прокси.
listen = "127.0.0.1:8080" public_url = "https://cctv.example.com" database_url = "postgres://hastreamer_cctv:ПАРОЛЬ@127.0.0.1:5432/cctv" [tls] mode = "external"
Настройте прокси так, чтобы он перезаписывал (а не дополнял) заголовок X-Forwarded-For:
Plane берёт из него IP клиента для аудита и ограничения частоты запросов. Привязка listen к
127.0.0.1 гарантирует, что напрямую, в обход прокси, до Plane не достучаться.
TLS ноды#
Нода отдаёт видео браузеру напрямую, поэтому сертификат ей нужен собственный. Он работает сразу после установки: стартовая конфигурация, которую выдаёт Plane, уже содержит TLS-слушатель и самоподписанную пару, выпущенную на настоящее имя ноды — то, что стоит в её адресе управления.
http 80; # обычный слушатель: проверка ACME и редирект на HTTPS https 443; # видео, плеер и API ноды поверх TLS rtsp 8554; cores 0; log_level info; tls { self_signed_names node.example.com; # самоподписанная пара на имя ноды }
Прежней двухэтапной установки — «сначала обычный http, потом руками перевести на https» — больше
нет. Схема эффективного адреса управления (Control URL, а если он пуст — Public URL) решает всё
один раз, при создании ноды:
| Эффективный адрес управления | Слушатели ноды | Собственный TLS |
|---|---|---|
https://node.example.com |
http 80; + https 443; |
да, самоподписанный на имя ноды |
https://cctv.example.com:9443 |
http 9080; + https 9443; |
да; так ставится нода рядом с Plane, которому принадлежат 80 и 443 |
http://node.example.com:8090 |
http 8090; |
нет — TLS терминирует прокси впереди |
http://Такая нода перенаправляет любой обычный HTTP-запрос на HTTPS, а при перенаправлении теряется
заголовок авторизации: Plane получает 401, управление молча перестаёт работать, и нода уходит в
offline, хотя видео раздаёт. Именно поэтому адрес управления задаётся по https:// с самого начала.
Если нода уже заведена с http://, приведите оба её адреса к https://:
Ноды → нода → Сеть и TLS → Адреса ноды → Изменить. Дополнительный эффект: конфигурация с
логинами и паролями камер уходит на ноду в шифрованном виде, а не открытым текстом.
Самоподписанный сертификат браузер доверенным не считает — при первом заходе на страницу с видео он предупредит. Дальше есть три пути.
| Вариант | Когда подходит | Что делать |
|---|---|---|
| Оставить самоподписанный | закрытый контур, операторы работают в той же сети и один раз принимают исключение | ничего |
| Взять сертификат Plane | нода стоит на одном хосте с Plane (совмещённая установка) | переключить владельца сертификата в админке |
| Выпустить свой по ACME | нода на отдельном сервере с публичным доменным именем | добавить блок certificate { acme; } |
Сертификат Plane (нода на одном хосте с Plane)#
Совмещённой ноде ACME недоступен: проверка Let's Encrypt приходит строго на порт 80, а его на этом хосте занял Plane. Отдельный сертификат ей и не нужен — она берёт сертификат Plane.
В админке: Ноды → нода → Сеть и TLS → Управлять TLS, поле «Владелец сертификата» → «Сертификат Plane · тот же хост». Локальный синхронизатор копирует сертификат Plane в снимок ноды и подхватывает его продления сам, без перезапуска и без передачи приватного ключа на другие хосты. Состояние видно тут же: «Сертификаты Plane и Node совпадают».
Совмещённый установщик (installers/hastreamer-cctv-all/install.sh) делает это переключение сам —
ставить и настраивать ничего не требуется. Подробности о вариантах владельца —
Ноды и флот.
Нода отдаёт ровно тот сертификат, который отдаёт Plane. Если Plane работает в режиме acme,
доверенным становится и адрес ноды; если Plane самоподписанный, предупреждение браузера останется
и на ноде — лечится переводом Plane на acme.
Свой сертификат по ACME (отдельный сервер ноды)#
Вариант для ноды на собственном хосте с публичным именем. Отредактируйте
/etc/hastreamer-cctv-node/node.conf — только верхнюю, системную часть файла, выше управляемой
Plane секции — и добавьте блок certificate:
http 80; # сюда приходит проверка HTTP-01 https 443; # видео отдаётся по HTTPS rtsp 8554; cores 0; log_level info; tls { self_signed_names node.example.com; # запасная пара: работает, пока ACME не выпустил } certificate le { # имя блока произвольное hosts node.example.com; # только конкретные имена, без wildcard acme; # выпуск и продление автоматически }
Условия выпуска на ноде — те же четыре, что выше, плюс два специфических:
- Должен быть включён хотя бы один TLS-слушатель (
https,rtsps). Без него задача выпуска вообще не стартует — сертификат некуда ставить. В стартовой конфигурации отдельной ноды он уже есть. - Проверка HTTP-01 отвечает на
http-слушателе ноды, но центр сертификации всегда стучится на порт 80. У отдельной ноды в стартовой конфигурации стоитhttp 80;— это и нужно. Если слушатель перенесён на другой порт (http 9080;у совмещённой ноды,http 8090;за прокси), проверка не дойдёт: вернитеhttp 80;либо пробросьте внешний 80 на порт слушателя.
sudo systemctl reload hastreamer-cctv-node sudo journalctl -u hastreamer-cctv-node -f | grep -i acme # ACME certificate "le" issuance attempt started for [node.example.com] # acme: issued/renewed cert(s) hot-swapped into the SNI store
Выпуск обычно занимает 30–90 секунд: фоновая задача сканирует конфигурацию раз в минуту. Готовый сертификат подставляется «на лету», без перезапуска и без разрыва сессий зрителей. Продление начинается за 30 суток; после неудачи интервал повторов растёт с 15 минут до 6 часов, поэтому запаса времени достаточно.
Пока настраиваете новый хост, используйте тестовый каталог — его лимиты гораздо мягче:
acme_directory https://acme-staging-v02.api.letsencrypt.org/directory;
Сертификаты оттуда браузер не признаёт — это нормально, они нужны только чтобы убедиться, что
проверка проходит. Затем уберите строку и перезапустите ноду (acme_directory применяется
только при рестарте).
Готовый сертификат ноды#
Если сертификат уже есть, укажите файлы вместо acme:
certificate node-public { hosts node.example.com; cert certs/node-public/fullchain.pem; # абсолютный путь или относительно хранилища TLS key certs/node-public/privkey.pem; ca certs/node-public/chain.pem; # необязательно }
Хранилище TLS ноды — /var/lib/hastreamer-cctv-node/tls-state; относительные пути считаются от
него. Одновременно указать автоматический выпуск и готовые файлы нельзя — конфигурация будет
отвергнута:
certificate "node-public": `acme` and explicit `cert`/`key` are mutually exclusive
Проверка#
# Plane: издатель и сроки действия echo | openssl s_client -connect cctv.example.com:443 -servername cctv.example.com 2>/dev/null \ | openssl x509 -noout -issuer -subject -dates # Нода: то же самое echo | openssl s_client -connect node.example.com:443 -servername node.example.com 2>/dev/null \ | openssl x509 -noout -issuer -subject -dates # цепочка валидна целиком (verify=0 — успех) curl -sS -o /dev/null -w '%{http_code} verify=%{ssl_verify_result}\n' https://cctv.example.com/admin/
Для самоподписанного сертификата verify будет отличен от нуля — это ожидаемо и не является
ошибкой.
Диагностика#
| Симптом | Вероятная причина | Что сделать |
|---|---|---|
ACME … issuance failed в журнале |
Порт 80 закрыт снаружи или DNS не указывает на этот хост | Проверьте getent hosts и межсетевой экран; Plane продолжит попытки сам |
| Браузер предупреждает о сертификате Plane | Режим self_signed либо временный сертификат после неудачного выпуска |
Перейдите на acme (см. выше) и убедитесь, что выпуск удался |
| Нода недоступна в админке, хотя раздаёт видео | Адрес управления http:// у ноды со своим TLS: при редиректе теряется авторизация |
Приведите оба адреса ноды к https:// — TLS ноды |
| Плеер пустой, в консоли браузера — mixed content | Админка по HTTPS, нода по HTTP | Выпустите сертификат ноде и переведите public_url на https:// |
Нода не запускается после правки node.conf |
Ошибка синтаксиса — конфигурация не проходит проверку | journalctl -u hastreamer-cctv-node -n 50: сообщение содержит номер строки |
| Сертификат ноды не выпускается | Нет TLS-слушателя, wildcard в hosts или порт 80 не приходит на http-слушатель |
Задайте https, укажите конкретное имя, пробросьте порт 80 |
Address already in use при старте Plane |
Порт 80 или 443 занят другим веб-сервером | Освободите порт либо перейдите на режим external и поставьте прокси впереди |
sudo systemctl reload hastreamer-cctv-node проверяет файл и применяет изменения «на лету».
При ошибке нода продолжит работать на прежней конфигурации и напишет в журнал
config reload REJECTED: … (running config unchanged). Перезапуск же при ошибке в файле уронит
сервис в цикл рестартов. Перезапуск нужен только для смены портов и параметров тюнинга — см.
node.conf.