Перейти к содержимому
Это документация следующей, ещё не выпущенной версии. Актуальная — 0.8. Перейти →

Как установить панель и личный кабинет Zentro

Инструкция для того, кто ставит фронт Zentro у заказчика или на своём сервере: образы, настройка, запуск, проверка, обновление. Identity ставится своим комплектом (Установка сервера) — здесь только панель, личный кабинет и их прокси (BFF).

Единый вход для приложений организации — Единый вход.

Три образа, четыре контейнера (и пятый — edge из образа zentro-edge, если TLS ставится на этом же сервере, шаг 5):

Контейнер Образ Что делает
portal-web zentro-portal-web Панель администратора: статика + nginx
portal-bff zentro-bff Прокси панели к Identity: хранит сессию, токены в браузер не попадают
account-web zentro-account-web Личный кабинет: вход, согласие, профиль, безопасность
account-bff zentro-bff Прокси кабинета, во внешнем режиме: админский API через него закрыт

Каждое приложение — свой адрес; его прокси — по пути /bff того же адреса:

https://admin.example.com/ панель (только внутренняя сеть)
https://admin.example.com/bff/… прокси панели
https://account.example.com/ личный кабинет (снаружи)
https://account.example.com/bff/… прокси кабинета

Образы одинаковы для всех заказчиков: адресов внутри нет, всё задаётся настройками.

  • Сервер с Docker 24+ и Docker Compose v2. Сколько ресурсов — Требования к серверу.
  • Identity установлен и доступен с этого сервера.
  • Адреса для панели и кабинета: свой TLS-прокси перед ними (nginx, балансировщик) или TLS из комплекта — со своими сертификатами или Let’s Encrypt (шаг 5); либо http://localhost для локальной установки.
  • Комплект Zentro Identity, скачанный и проверенный по инструкции Установка сервера: панель и кабинет лежат в его папке portal/, той же версии, что и сервер.

Папка portal/ из распакованного и проверенного комплекта (на сервер фронта, если он отдельный, — скопируйте её):

Окно терминала
cd zentro-identity-0.7.2/portal
docker load < images/zentro-portal-0.7.2-linux-amd64.tar.gz # arm64-сервер — …-linux-arm64
cd deploy

В папке portal/:

Путь Что
images/zentro-portal-<версия>-linux-<arch>.tar.gz все образы фронта одним архивом: панель, кабинет, BFF, TLS-прокси
deploy/docker-compose.yml, deploy/.env.example запуск и справочник переменных
deploy/docker-compose.tls.yml, deploy/tls/ TLS на этом сервере (шаг 5)
README.md, server-requirements.md инструкция по установке и ресурсы сервера
THIRD_PARTY_NOTICES.txt лицензии сторонних компонентов панели и кабинета
VERSION, SHA256SUMS версия, контрольные суммы (их тоже покрывает подпись всего комплекта)

Все команды дальше — из папки deploy.

Окно терминала
cp .env.example .env
chmod 600 .env
Переменная Что вписать
VERSION Версия образов — уже вписана версия комплекта
IDENTITY_BASE_URL Адрес Identity с сервера фронта: https://id.example.com или внутренний. Identity на этом же сервере — http://host.docker.internal:8080
PORTAL_SESSION_KEY Ключ шифрования сессий панели: openssl rand -base64 32
ACCOUNT_SESSION_KEY Ключ сессий кабинета — другой: openssl rand -base64 32
PORTAL_BIND, PORTAL_PORT На каком адресе и порту слушает панель. По умолчанию 127.0.0.1:8090 — только локально
ACCOUNT_BIND, ACCOUNT_PORT То же для кабинета, по умолчанию 127.0.0.1:8091
CSP_IMG_HOSTS Если логотип или картинки бренда лежат на внешних хостах — эти хосты через пробел (те же, что в настройке Identity ui.asset_allowed_hosts). Пусто — только картинки, загруженные в панели
HSTS За своим TLS-прокси: max-age=31536000. С TLS из комплекта — не нужен (ставит edge). Без TLS (http://localhost) — пусто
PORTAL_PUBLIC_URL, ACCOUNT_PUBLIC_URL Адреса панели и кабинета с /bff, как их видит браузер: https://admin.example.com/bff, https://account.example.com/bff. Нужны корпоративному входу с переходом (OIDC, Keycloak): провайдер вернёт человека в то приложение, где он начал. Нет таких провайдеров — можно не задавать
REAL_IP_FROM Только если перед фронтом стоит свой прокси (TLS-балансировщик): его адреса или сети через пробел — шаг 5. Пусто — пользователи ходят к контейнерам напрямую
RATE_LIMIT_PER_MIN, AUTH_RATE_LIMIT_SCALE Лимиты запросов прокси. Оставьте по умолчанию, если нет причин

Ключи сессий — секреты. Файл .env — только владельцу. Смена ключа завершает все сессии: пользователям придётся войти заново.

Окно терминала
docker compose -f docker-compose.yml --env-file .env up -d
docker compose -f docker-compose.yml --env-file .env ps

С TLS из комплекта — команды из шага 5. Все контейнеры — running. Не задан обязательный параметр — compose откажется запускаться и назовёт его.

Контейнеры работают без root, с файловой системой только для чтения. Данных не хранят: всё состояние — в Identity, бэкапить здесь нечего, кроме .env.

В env Identity (делает тот, кто его разворачивает; после правки — перезапуск Identity):

Переменная Значение
WEBAUTHN_RP_ORIGIN оба адреса фронта через запятую: https://account.example.com,https://admin.example.com
OIDC_AUTHORIZE_UI_URL https://account.example.com/oidc/consent
PASSWORD_RESET_URL, EMAIL_VERIFY_URL, EMAIL_CHANGE_URL адреса кабинета: /reset-password, /verify-email, /confirm-email-change
TRUSTED_PROXY_CIDRS адрес, с которого Identity видит прокси фронта (сеть Docker этого сервера или адрес сервера)

Для локальной установки (http://localhost) — те же переменные с http://localhost:8091 (кабинет) и http://localhost:8090 (панель).

Если подключаются внешние провайдеры входа с переходом на их страницу (OIDC, Keycloak) — в провайдере и в Zentro адреса возврата обоих приложений: https://account.example.com/bff/auth/idp/<имя>/callback и https://admin.example.com/bff/auth/idp/<имя>/callback (подробно — Единый вход, шаг 4).

Контейнеры слушают http. Для адресов с доменом нужен TLS — два варианта.

Когда своего TLS-прокси нет — например, всё на одном VPS. Порты 80 и 443 сервера занимает контейнер edge (nginx): он отдаёт панель, кабинет и, если нужно, Identity по имени домена.

  1. DNS доменов — на этот сервер; порт 80 открыт снаружи (Let’s Encrypt проверяет домен по нему).
  2. В .env — блок «TLS на этом сервере»: PORTAL_HOST, ACCOUNT_HOST, при необходимости IDENTITY_HOST (Identity на этом же сервере, свой nginx у него тогда не нужен), почта LETSENCRYPT_EMAIL. REAL_IP_FROM и HSTS задавать не нужно.
  3. Сертификаты — для каждого домена свой выбор:
    • свои (корпоративный центр, закрытый контур): tls/certs/<домен>/fullchain.pem и privkey.pem, ключ читает только uid 101 — tls/certs/README.md;
    • Let’s Encrypt — ничего не класть: сертификат получит и будет продлевать сервис certbot.
  4. Запуск:
Окно терминала
# только свои сертификаты:
docker compose -f docker-compose.yml -f docker-compose.tls.yml --env-file .env up -d
# Let's Encrypt (хотя бы для одного домена):
docker compose -f docker-compose.yml -f docker-compose.tls.yml --env-file .env --profile letsencrypt up -d

Пока сертификата нет, edge отдаёт временный самоподписанный (браузер предупредит) — так он может ответить Let’s Encrypt. Настоящий появляется за минуту-две, подхватывается без перезапуска; продление — само, за 30 дней до конца. Что с сертификатами — в логе:

Окно терминала
docker compose -f docker-compose.yml -f docker-compose.tls.yml --env-file .env logs edge certbot
# edge: admin.example.com → portal-web:8080, сертификат: letsencrypt (own / selfsigned)

Сначала можно проверить на тестовом центре Let’s Encrypt: LETSENCRYPT_STAGING=true — лимиты выдачи не тратятся, но браузер такому сертификату не верит. Потом false, удалить тестовые сертификаты (docker volume rm <проект>_letsencrypt <проект>_edge-le) и запустить заново.

Панель снаружи. По умолчанию панель открыта всем по своему домену — защищают вход и второй фактор. Закрыть её по адресам — ADMIN_ALLOW_FROM в .env (примеры там же: подсеть VPN, офисные адреса, только частные сети); остальным — 403. Кабинет и Identity этим не ограничиваются.

Identity через edge. IDENTITY_UPSTREAM — как edge видит Identity (по умолчанию host.docker.internal:8080 — на этом же сервере). Identity должен доверять адресу, с которого к нему приходит edge (TRUSTED_PROXY_CIDRS, по умолчанию у Identity — шлюз его подсети, этого хватает), и знать свой внешний адрес (PUBLIC_BASE_URL=https://id.example.com).

Перед контейнерами ставится TLS-прокси заказчика:

  • admin.example.com → PORTAL_BIND:PORTAL_PORT, account.example.com → ACCOUNT_BIND:ACCOUNT_PORT; PORTAL_BIND / ACCOUNT_BIND — адрес, на который ходит прокси;
  • прокси сохраняет заголовок Host и дописывает адрес пользователя в X-Forwarded-For (nginx: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;);
  • REAL_IP_FROM в .env — адреса прокси, как их видит контейнер (10.0.0.5/32 или сеть 10.20.0.0/16, несколько — через пробел);
  • HSTS в .env — задать;
  • панель наружу не публиковать: только внутренняя сеть или VPN.

Зачем REAL_IP_FROM. Без него адрес пользователя для фронта — адрес прокси: все пользователи делят один лимит попыток входа, а в сессиях и аудите Identity стоит прокси. С ним адрес берётся из X-Forwarded-For, но только от перечисленных адресов: прямой запрос с подставленным заголовком ничего не подменит. 0.0.0.0/0 не принимается. Неверное значение — контейнер не стартует и пишет, что не так (docker compose … logs portal-web).

Как проверить, что адрес доходит: войти через прокси и открыть кабинет → Сессии — у текущей сессии ваш адрес, а не адрес прокси.

Окно терминала
A=https://account.example.com # или http://localhost:8091
P=https://admin.example.com # или http://localhost:8090
curl -s $A/bff/healthz # ok
curl -s $P/bff/healthz # ok
curl -sI $A/ | grep -i content-security # заголовок есть
curl -s -o /dev/null -w '%{http_code}\n' $A/bff/api/identity/admin/users # 403: кабинет не пускает в админку

В браузере:

  1. Кабинет открывается, вход работает, «Забыли пароль?» присылает письмо со ссылкой на кабинет.
  2. Панель открывается (из внутренней сети), вход администратором, второй фактор, разделы с данными.
  3. Ключ (passkey) заводится и входит — и в кабинете, и в панели.
  4. Панель не открывается снаружи.

Панель и кабинет обновляются из папки portal/ нового комплекта Zentro Identity (скачать и проверить подпись — как при установке; сервер Identity обновляет upgrade.sh, Установка сервера).

Окно терминала
docker load < zentro-identity-<новая>/portal/images/zentro-portal-<новая>-linux-amd64.tar.gz
# VERSION=<новая> в .env; сравнить portal/deploy/.env.example нового комплекта со своим .env — новые переменные
docker compose -f docker-compose.yml --env-file .env up -d # с TLS — те же -f и --profile, что при запуске

Контейнеры пересоздаются за секунды, сессии пользователей сохраняются (ключи те же). Откат — вернуть прежнюю VERSION и повторить up -d. Порядок относительно Identity — по примечаниям к версии: обычно сначала Identity, затем фронт.

Окно терминала
docker compose -f docker-compose.yml --env-file .env down # с TLS — добавить -f docker-compose.tls.yml

Данных нет — после down удалять больше нечего, кроме сертификатов Let’s Encrypt в томах (down -v удалит и их — при следующем запуске выпустятся заново). Образы — docker image rm.

Что видно Причина Что сделать
compose не запускается: «задайте …» Не заполнен обязательный параметр .env, шаг 2
Браузер предупреждает о сертификате, в логе edge — selfsigned Let’s Encrypt ещё не выдал или не смог: DNS не на этот сервер, порт 80 закрыт, не запущен профиль letsencrypt logs certbot — там причина; проверить DNS и порт 80, шаг 5
edge не стартует, в логе privkey.pem не читается Свой ключ недоступен пользователю прокси chown 101 + chmod 600 на ключ, tls/certs/README.md
Панель отвечает 403, кабинет работает Ваш адрес не в ADMIN_ALLOW_FROM дописать сеть в .env, up -d
Страница открывается, при входе — «Сервис входа временно недоступен» Прокси не достучался до Identity IDENTITY_BASE_URL: открывается ли он с сервера фронта (из контейнера)
Ключ (passkey) не работает Адреса фронта нет в WEBAUTHN_RP_ORIGIN Identity Шаг 4
Вход в приложение через SSO обрывается OIDC_AUTHORIZE_UI_URL не указывает на кабинет Шаг 4
Логотип бренда не показывается Картинка на внешнем хосте, которого нет в CSP_IMG_HOSTS Добавить хост, перезапустить *-web
После смены .env все вышли Сменились ключи сессий Так и должно быть; ключи менять только намеренно
Все упираются в «слишком много попыток»; в Сессиях у всех один адрес Фронт или Identity видят адрес прокси вместо пользователей Фронт — REAL_IP_FROM (шаг 5); Identity — TRUSTED_PROXY_CIDRS (шаг 4)
Контейнер *-web сразу останавливается, в логе REAL_IP_FROM: … Не адрес/сеть или 0.0.0.0/0 Адреса прокси в виде 10.0.0.5/32, через пробел
Сайт перестал открываться по http после включения HSTS HSTS заставляет браузер ходить только по https HSTS — только при настоящем TLS