Путь, удобный для compliance. Поскольку ваше приложение — обычный OIDC-клиент, ни один
компонент Veriqa не попадает в вашу сборку и SBOM. Если в организации сканеры лицензий проходят
по зависимостям — выбирайте этот режим: вы интегрируетесь по протоколу, а не по пакету.
Встроенная альтернатива — .NET quickstart.
1. Получите образ
Каждый релиз публикуется в реестр контейнеров публичного репозитория:latest — самый свежий релиз. Чтобы закрепить релиз в продакшене, укажите
его версию тегом (registry.gitlab.com/veriqa/veriqa:<версия>).
Опубликованный образ —
linux/amd64. На другой архитектуре соберите его из корня репозитория —
Dockerfile там публикует тот же standalone-хост — и подставьте свой локальный тег вместо имени
образа в командах ниже:1654), экспонирует порт 8080 по HTTP и несёт HEALTHCHECK против /health/live. TLS
ожидается терминированным перед ним — см. шаг 6.
2. Настройте его
Контейнер читает ту же конфигурацию, что и любое ASP.NET Core приложение, поэтому каждая настройка может быть передана переменной окружения. Вложенные ключи используют двойное подчёркивание как разделитель:Veriqa:OpenIddict:Database:Provider превращается в
Veriqa__OpenIddict__Database__Provider.
docker run
Veriqa:OpenIddict:Database— OIDC-клиенты, авторизации и токены. Умолчания нет: контейнер с пустой секцией не стартует — проверка на старте отказывает и называет лечение (вDevelopmentтот же текст пишется какWarning). Для реального использования задайтеProvider=PostgreSQLилиSqlServerи укажитеConnectionString;Provider=InMemory— это явный выбор волатильного хранилища: каждый рестарт сбрасывает выданные токены — подходит для разработки и для коннектора на одном инстансе.Veriqa:TransactionEngine:Store— сами транзакции входа. ЗаданиеConnectionStringпереключает его на EF Core; пустое значение оставляет хранилище в памяти.Provider—PostgreSQL(он же при пустом ключе) илиSqlServer.Veriqa:Logging:Store— журнал аудита, необязательно: по умолчанию он делит БД с транзакциями. См. Сток аудита.
Server=sqlserver;Database=veriqa;User Id=veriqa;Password=….
Резолверы — fail-fast: нераспознанное значение провайдера или реляционный провайдер без
строки подключения останавливают контейнер на старте, а не молча падают в фолбэк.
Сток аудита
Записи аудита пишутся только приVeriqa:Logging:Mode = Audit. Куда они попадают, решают два
необязательных ключа, и каждый откатывается на своё значение независимо:
Оставьте оба незаданными — журнал живёт в БД транзакций. Отдельная БД нужна, когда:
- учётной записью, под которой хранятся записи, должен владеть аудитор — отдельно от той, которой пишет путь входа;
- профили нагрузки и бэкапа разные: транзакции исчезают за минуты, записи аудита живут
RetentionDays; - транзакции остаются в памяти, а журнал должен пережить рестарт — задайте только
Veriqa:Logging:Store:ConnectionString.
Provider: задайте и строку стока, либо уберите провайдер. Если строка стока
отличается от строки транзакций, у readiness появляется отдельная проверка (postgresql-audit или
sqlserver-audit).
Существующая установка. У установки, работавшей без этих ключей, AuditRecords лежат в БД
транзакций. Задание Veriqa:Logging:Store:ConnectionString их не переносит: новая БД начинает с
пустой таблицы, а оставшиеся строки ретенция больше не вычищает. Перенесите их или удалите в рамках
переключения.
Режиму Audit нужен долговечный сток. При Veriqa:Logging:Mode = Audit и незаданных обеих
строках подключения записи хранились бы в памяти и терялись при рестарте — поэтому вне Development
контейнер отказывается стартовать:
Development тот же текст пишется как Warning. Проверка выполняется на старте: переключение в
Audit позже, без рестарта, повторно не проверяется.
3. Предоставьте сертификаты токенов
ВнеDevelopment сервер отказывается стартовать без обоих сертификатов OpenIddict — подписи и
шифрования: авто-генерируемых dev-ключей, на которые можно откатиться, нет. Передайте каждый либо
путём к файлу, либо инлайн как Base64 PFX.
Подойдёт любой сертификат RSA-2048 (или сильнее), самоподписанного достаточно. Один из способов
выпустить оба через OpenSSL:
PFX_PASSWORD хранит пароль, который затем передаётся как …CertificatePassword. Файлы
.key не кладите ни в образ, ни в репозиторий. Оба файла .pfx openssl создаёт с правами
-rw-------, владельцем становится запустивший его пользователь — это важно, как только вы
монтируете их в контейнер, см. предупреждение ниже.
…Path (SigningCertificatePath / EncryptionCertificatePath) принимают путь к
PFX, смонтированному в контейнер, — обычно вариант лучше, так как Base64-форма даёт очень длинные
значения переменных окружения.
4. Объявите свой клиент
Приложение, которое выполняет вход пользователей, регистрируется как OIDC-клиент вVeriqa:OpenIddict:Clients. Образ не несёт клиентов, поэтому этот шаг обязателен — и каждый
клиент валидируется на старте: отсутствие ClientId, пустой AllowedRedirectUris или пустой
AllowedScopes останавливают контейнер с сообщением, называющим проблемную запись.
С env-файлом это удобнее выразить как JSON — смонтируйте файл настроек veriqa.json в контейнер по
пути /app/config/veriqa.json (переменная VERIQA_SETTINGS_FILE переносит его в другое место; если по заданному в ней пути файла нет, контейнер не стартует). В файле
допустимы любые настройки Veriqa, не только клиенты; ключ, заданный через окружение или --env-file,
перекрывает тот же ключ из файла:
veriqa.json
redirect_uri сверяется точным совпадением с AllowedRedirectUris — wildcard’ы не
поддерживаются, URI должны быть абсолютными, а http принимается только для loopback-адресов
(localhost, 127.0.0.1, [::1]) в разработке.
5. Направьте своё приложение на него
Ваше приложение — стандартный OIDC-клиент. Ничего специфичного для Veriqa в него не попадает — это та же конфигурация, что вы написали бы для любого OpenID Connect провайдера. В ASP.NET Core она приезжает одним пакетом:Program.cs (ваше приложение, не Veriqa)
picture) гейтится Veriqa-специфичным scope avatar: добавьте
options.Scope.Add("avatar") и options.GetClaimsFromUserInfoEndpoint = true, а avatar впишите в
AllowedScopes клиента из шага 4. В id_token аватар не попадает никогда —
см. OIDC + Veriqa: разбор.
Discovery, authorization code flow и выдача токенов — обычное поведение OpenIddict, поэтому подходит
любая совместимая OIDC-клиентская библиотека — .NET, Node, Go, Java, Python.
Блок выше — только регистрация. Чтобы провести вход из шага 8, нужны ещё конвейер
(app.UseAuthentication(), app.UseAuthorization()) и конечная точка, выдающая challenge, —
полная программа клиента, вместе с ними обоими, лежит в
шаге 5 .NET quickstart. Та программа
написана для встроенного quickstart, поэтому два значения в ней свои: Authority указывает на
https://localhost:7300, и она запрашивает scope email, которого нет в AllowedScopes клиента
из шага 4 — либо уберите options.Scope.Add("email"), либо добавьте
email в AllowedScopes. Остальное применимо как есть.
6. Поставьте за reverse-proxy
Контейнер говорит по HTTP; TLS терминируется на вашем прокси. Чтобы OpenIddict строил корректныеhttps-URL, прокси должен пробрасывать X-Forwarded-Proto / X-Forwarded-For, а хосту нужно
сообщить, каким прокси доверять, — в том же veriqa.json из шага 4:
veriqa.json
X-Forwarded-Proto, продолжает считать запрос http, и даже
/.well-known/openid-configuration отвечает invalid_request («This server only accepts HTTPS
requests»). Как только сеть прокси перечислена, discovery возвращает https-URL и вход работает.
Оставьте прокси запас и на заголовки запроса. Запросы входа несут cookie: собственные cookie Veriqa
и, если ваше приложение делит с ней имя хоста или родительский домен (в разработке все порты
localhost — один хост), cookie корреляции, nonce и сессии OIDC-клиента. NGINX по умолчанию
ограничивает один заголовок 8k и на более длинный отвечает 400 Request Header Or Cookie Too Large
— поднимите лимит в блоке server:
limit_req в NGINX, правило WAF,
fail2ban) — часть этого же шага, см.
Hardening к продакшну.
7. Включите каналы
Настройка каналов — токены ботов, регистрация вебхуков, Meta Cloud API, SMTP — идентична в обоих режимах поставки и живёт в Настройке каналов. Одна деталь специфична для self-hosting: вебхуки каналов должны быть доступны из интернета поhttps://auth.your-domain.com/api/channels/…, поэтому reverse-proxy обязан выставить эти пути.
Хотя бы один канал обязателен — без него вход подтверждать негде. В команде из
шага 2 включён Telegram; вот что значат его ключи, чтобы дойти до рабочего
входа, не уходя с этой страницы:
Вебхук Veriqa регистрирует в Bot API сама на старте — вручную
setWebhook вызывать не нужно.
Регистрацию проверьте через Bot API:
url равен https://auth.your-domain.com/api/channels/telegram/webhook, last_error_message
отсутствует. В логе контейнера есть и строка Telegram webhook registered: …, но только на уровне
Information: образ в Production пишет лог от Warning, поэтому там этой строки нет.
Секция валидируется на старте, как и остальные: включённый адаптер без BotToken, без
WebhookSecretToken или с относительным либо не-HTTPS WebhookBaseUrl останавливает контейнер
сообщением, называющим ключ.
8. Проверьте
Критерий готовности: зелёные шаги 1–4. Процесс жив, зависимости отвечают, discovery отдаётhttps-URL, канал зарегистрирован — установка развёрнута. Шаг 5 проверяет уже вашу интеграцию (клиента
и сценарий), а не сам сервер.
1
Проверьте liveness
curl -f http://localhost:8080/health/live — процесс поднят.2
Проверьте readiness
curl -f http://localhost:8080/health/ready — настроенные зависимости (БД, Redis, RabbitMQ)
отвечают. Именно этот probe стоит завести в оркестратор.3
Проверьте discovery
curl https://auth.your-domain.com/.well-known/openid-configuration возвращает OIDC-метаданные с
https-URL. Если они выходят как http, вернитесь к шагу 6.4
Проверьте, что канал поднялся
Для режима
Webhook: getWebhookInfo бота отдаёт в url ваш /api/channels/telegram/webhook
без last_error_message — см. шаг 7. url пуст — подтверждать вход будет
нечем. В режиме Polling такого вызова нет: читайте /health/channels, помня, на какие вопросы
он не отвечает (ниже).5
Проведите вход
Инициируйте вход из вашего приложения и подтвердите его в доверенном канале на телефоне. У Email —
и у любого канала при
Veriqa:LoginConfirmation:Mode = OnWebPage — страница входа затем
спрашивает ещё раз: подтвердите и там.На что
/health/channels отвечает, а на что — нет. Эндпоинт сообщает состояние каждого
включённого канала отдельно, и читать нужно именно значение канала: healthy, unhealthy или
timeout.- HTTP-код всегда
200. Отказ канала отдаётся какDegraded, но никогда какUnhealthy, и это сделано намеренно: недоступный мессенджер не должен выбивать работающую реплику из ротации. Поэтомуcurl -fуспешен даже когда лежат все каналы — алертить нужно по телу ответа, а не по коду. По той же причине каналов нет в/health/ready. - Отчёт может быть устаревшим. Это снимок, обновляемый не чаще раза в 15 секунд, и двигает
его только чтение: запрос, заставший снимок просроченным, всё равно отвечает по старому, а
обновление запускает в фоне. Первое чтение после падения канала ещё может сказать
healthy, правду скажет следующее, — поэтому, если вы по нему алертите, опрашивайте его периодически. - Он не знает, кому принадлежит очередь апдейтов. Зонд — аутентифицированный вызов платформы,
так что неверный или отозванный токен действительно выйдет как
unhealthy; но токен, который валиден и уже занят другим опрашивающим, ответит безупречно. См. Запуск более одного инстанса.
9. Типичные ошибки первого запуска
Почти всё, что ломается на первом запуске, ломается на старте — контейнер не остаётся жить в полусломанном состоянии, а завершается с сообщением, называющим ключ. Поэтому первое действие при любом сбое —docker logs veriqa.
Запуск более одного инстанса
Одному контейнеру не нужно ничего дополнительно. Масштабирование требует двух общих кусков состояния:
Оставьте оба незаданными — и каждый инстанс держит свои ключи в памяти: нормально для одного
контейнера, сломано для нескольких. Общий том для
KeysDirectory работает как альтернатива Redis.
Само собой не масштабируется ещё одно: доставка апдейтов в режиме Polling. Bot API отдаёт
очередь апдейтов единственному клиенту getUpdates, поэтому второй инстанс на том же токене бота
не получает вообще ничего — и при этом считает себя healthy, ведь токен валиден и платформа ему
отвечает. Выдаёт его только лог: цикл опроса повторяет отказ 409 Bot API, записанный как
Conflict: terminated by other getUpdates request — самого номера в строке нет. Масштабируйтесь
на режиме Webhook, где каждый инстанс кормит прокси, либо дайте каждой установке своего бота —
последнее касается staging-установки рядом с боевой ровно так же, как второй реплики.
Дальше
Настройка каналов
MAX, Telegram, WhatsApp, другие мессенджеры и Email — от начала до конца.
Справочник конфигурации
Каждая секция, ключ и дефолт в одном месте.
Продакшн-харденинг
Что проверить перед выкаткой.
Хранилища
Хранилище транзакций, ретенция и миграции.
Установка службой ОС
Тот же сервер без Docker — systemd или служба Windows, из архива.