Реализованы сервисы ВМ2 - проверка сообщений и синхронизация с Б24 (деплой еще без перевода в боевой режим)
This commit is contained in:
@@ -1,34 +1,47 @@
|
||||
# module-10. Runbook развёртывания HAN Chat
|
||||
|
||||
> Статус: последовательная инструкция первого production-like деплоя и эксплуатации на одной Ubuntu VM.
|
||||
> Статус: целевой runbook ВМ1/ВМ2. Команды существующего stub-контура применимы только до production Safety cutover и явно отмечены как legacy.
|
||||
> Все значения в `<УГЛОВЫХ_СКОБКАХ>` — placeholders. Команды с `cd <BACKEND_ROOT>` требуют подстановки реального пути корня backend-репозитория на VM.
|
||||
> Источники: [`README.md`](README.md), [`arch-00-glossary.md`](arch-00-glossary.md), [`arch-01-system-architecture.md`](arch-01-system-architecture.md), [`arch-02-api-contracts.md`](arch-02-api-contracts.md), [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md), [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md), [`arch-05-agent-development-process.md`](arch-05-agent-development-process.md), [`module-01-api-backend.md`](module-01-api-backend.md)–[`module-09-observability.md`](module-09-observability.md).
|
||||
> Канонические источники: [`../architectory/README.md`](../architectory/README.md), [`../architectory/arch-00-glossary.md`](../architectory/arch-00-glossary.md), [`../architectory/arch-01-system-architecture.md`](../architectory/arch-01-system-architecture.md), [`../architectory/arch-02-api-contracts.md`](../architectory/arch-02-api-contracts.md), [`../architectory/arch-03-docker-compose-blueprint.md`](../architectory/arch-03-docker-compose-blueprint.md), [`../architectory/arch-04-settings-and-content.md`](../architectory/arch-04-settings-and-content.md), [`../architectory/arch-05-agent-development-process.md`](../architectory/arch-05-agent-development-process.md), [`../architectory/arch-06-service-hosting-security.md`](../architectory/arch-06-service-hosting-security.md), [`module-01-api-backend.md`](module-01-api-backend.md)–[`module-09-observability.md`](module-09-observability.md).
|
||||
|
||||
## 1. Неподвижные правила
|
||||
|
||||
1. Один root `docker compose` запускается из `<BACKEND_ROOT>`.
|
||||
2. Ровно один edge nginx публикует `80/443`.
|
||||
3. API, Keycloak, Redis, OTEL, Safety и Bitrix-сервисы не имеют host `ports`.
|
||||
1. Один root Compose project описывается в `<BACKEND_ROOT>`; в steady state его запускает root-owned systemd-unit/helper, а не пользователь из группы `docker`.
|
||||
2. На каждой VM ровно один nginx; ВМ1 и ВМ2 имеют независимые public DNS/TLS ingress и deployment lifecycle.
|
||||
3. Application containers не имеют public host ports. Nginx ВМ1 публикует свой `80/443`; nginx ВМ2 — отдельный `80/443` только для exact CRM webhook и private `8443` для Message Safety/internal access.
|
||||
4. `/internal/*` не маршрутизируется публично.
|
||||
5. Managed PostgreSQL находится вне Compose, в той же VPC, без public IP.
|
||||
6. S3 — внешний Selectel-compatible storage; клиент получает только presigned URL.
|
||||
7. Секреты не коммитятся, не вставляются в команды shell history и не выводятся в отчёты.
|
||||
8. Миграции выполняются отдельными one-shot steps до новой версии приложения.
|
||||
9. Message Safety запускается как documented stub до замены; это не production antivirus/moderation.
|
||||
10. `bitrix-sync` запускается как DB-connectivity stub; полноценной CRM sync нет.
|
||||
10. `bitrix-sync` вводится только после выполнения preflight/cutover gates module-07; до этого `BITRIX_SYNC_ENABLED=false`, public webhook закрыт на edge.
|
||||
11. На ВМ1 и ВМ2 отдельные root Compose projects/systemd units; deploy/rollback выполняются независимо.
|
||||
11. ВМ2 — самостоятельная service VM с минимальным public webhook ingress, allow-listed egress, отдельным IAM principal и service-specific secret files.
|
||||
12. OS-роли, SSH/sudo, secrets delivery, container hardening и private-VM lockdown подчиняются arch-06.
|
||||
|
||||
Прямые `docker compose` команды в этом runbook выполняются `admin` только при bootstrap/recovery либо инкапсулируются в утверждённые root-owned systemd-units. Они не являются основанием выдавать `deploy` доступ к Docker daemon.
|
||||
|
||||
## 2. Роли и обозначения
|
||||
|
||||
- **Cloud admin**: VPC, VM, PG, S3, DNS/security groups.
|
||||
- **Deploy operator**: VM, Compose, migrations, release/rollback.
|
||||
- **Deploy operator / OS user `deploy`**: запуск утверждённых release/rollback/migration systemd-units и root-owned Message Safety mode helper; без группы `docker`, записи в production compose/unit/scripts/config и общего sudo.
|
||||
- **Break-glass OS user `admin`**: bootstrap и аварийное восстановление; не используется для штатного деплоя.
|
||||
- **OS user `tunnel`**: только allow-listed local TCP forwarding к private endpoints; без sudo/shell operations.
|
||||
- **Bitrix admin**: local app, connector, Open Line 8, callbacks.
|
||||
- **Security owner**: secrets, Keycloak admin MFA, firewall, retention.
|
||||
- **Safety Service Owner**: API/data contract, capacity result и v2 cutover/rollback sign-off.
|
||||
- **Rule Pack Owner**: rules bundle, corpus, monitor report и version release.
|
||||
- **Product Owner**: mnemonic `safety.chat.blocked` и business acceptance chat flow.
|
||||
- **Operations Owner**: VM2 alerts, ClamAV signatures, incident/reprovision/restore rehearsal.
|
||||
|
||||
Placeholders:
|
||||
|
||||
```text
|
||||
<PUBLIC_HOST> например chat.example.ru
|
||||
<VM_PUBLIC_IP> публичный IPv4 VM
|
||||
<PROCESSING_PUBLIC_HOST> отдельный public host ВМ2, например processing.example.ru
|
||||
<VM2_PUBLIC_IP> публичный IPv4/LB address ВМ2
|
||||
<VM_PRIVATE_IP> приватный IPv4 VM
|
||||
<VPC_CIDR> например 10.20.0.0/24
|
||||
<PG_PRIVATE_HOST> private FQDN/IP managed PG
|
||||
@@ -58,8 +71,9 @@ Placeholders:
|
||||
- remote observability backend;
|
||||
- RPO/RTO и maintenance window;
|
||||
- ответственных за alerts/Bitrix/Keycloak.
|
||||
- назначенные Safety Service/Rule Pack/Product/Security/Operations owners и approvals cutover.
|
||||
|
||||
Начальный sizing без local Grafana stack:
|
||||
Начальный sizing ВМ2 без local Grafana stack:
|
||||
|
||||
- VM: 4 vCPU, 8 ГБ RAM, 80 ГБ SSD, 4 ГБ swap;
|
||||
- managed PG: минимум 2 vCPU, 4 ГБ RAM, 50 ГБ, HA по возможности;
|
||||
@@ -67,7 +81,16 @@ Placeholders:
|
||||
- OTEL Collector: 512 МиБ + 5–10 ГБ queue;
|
||||
- свободный диск VM после pull/build: не менее 30%.
|
||||
|
||||
Это baseline, не гарантия. До real traffic обязателен load test с long Safety poll и WS.
|
||||
Это baseline, не гарантия. До real traffic обязателен load test module-05 §15.4:
|
||||
|
||||
- sustained 10 text checks/s: p95 ≤2 с, p99 ≤5 с;
|
||||
- sustained 2 file checks/s на 5 worker slots: среднее processing ≤2.5 с, p95 ≤60 с, public wait ≤300 с;
|
||||
- 100 pending принимаются; 101-й file POST получает retryable `503` без новой task;
|
||||
- RPS overflow даёт `429 + Retry-After`;
|
||||
- long Safety poll не блокирует WS/read API;
|
||||
- если gate не пройден, увеличить slots/CPU/clamd scan lanes и повторить; production traffic не открывать.
|
||||
|
||||
Monthly availability SLO для MVP не задаётся; это не отменяет latency/load gates и alerts.
|
||||
|
||||
### Gate 0
|
||||
|
||||
@@ -83,30 +106,37 @@ Placeholders:
|
||||
|
||||
### 4.1. Сеть
|
||||
|
||||
Создать одну private subnet для VM и managed PG. PG получает только private address. VM имеет public IP только для nginx/SSH.
|
||||
Создать private subnet для ВМ1, ВМ2, SigNoz и managed PG. ВМ1 и ВМ2 имеют отдельные public IP/LB только для своих nginx; service-to-service и PostgreSQL traffic остаётся private. SSH к обеим VM — только ops VPN/bastion.
|
||||
|
||||
Security groups:
|
||||
|
||||
| Source | Destination | Port | Rule |
|
||||
|---|---|---:|---|
|
||||
| trusted ops CIDR/VPN | VM | SSH `<SSH_PORT>` | allow |
|
||||
| internet | VM | TCP 80 | allow для redirect/ACME |
|
||||
| internet | VM | TCP 443 | allow |
|
||||
| VM private IP/SG | managed PG | `<PG_PORT>` | allow |
|
||||
| VM | internet | 443 | allow egress: registry, Bitrix, S3, OTLP, ACME, i-Digital Direct |
|
||||
| trusted ops CIDR/VPN | ВМ1, ВМ2 | SSH `<SSH_PORT>` | allow |
|
||||
| internet | ВМ1 | TCP 80/443 | allow edge redirect/ACME/application |
|
||||
| internet | nginx ВМ2 | TCP 80/443 | allow ACME/redirect + exact CRM webhook |
|
||||
| ВМ1 SG | ВМ2 | TCP 8443 | private TLS only |
|
||||
| ВМ1/ВМ2 service SG | managed PG | `<PG_PORT>` | allow по нужным DB roles |
|
||||
| ВМ2 collector | private SigNoz | TCP 4317 | allow |
|
||||
| ВМ2 workers | S3 endpoints | TCP 443 | allow |
|
||||
| ВМ2 `bitrix-sync` | approved Bitrix portal | TCP 443 | allow |
|
||||
| ВМ2 `freshclam` | approved signature CDN | TCP 443/80 по vendor manifest | allow |
|
||||
| ВМ2 | trusted DNS/NTP | UDP/TCP 53, UDP 123 | allow |
|
||||
| internet | managed PG | any | deny |
|
||||
| internet | VM | 6379, 4317, 4318, 8000, 8080, 9000 | deny |
|
||||
| internet | ВМ2 | any кроме nginx 80/443 | deny ingress |
|
||||
| internet | обе VM | 6379, 4317, 4318, 8000, 8080, 8443, 9000 | deny public |
|
||||
|
||||
Если cloud SG не поддерживает egress allow-list, оставить egress open и контролировать destinations приложением/TLS; не ломать S3/Bitrix/OIDC.
|
||||
ВМ2 использует default-deny egress. Registry/OS repositories открываются только в bootstrap/controlled window и затем снова закрываются. Если provider SG не умеет destination allow-list, применяется host firewall/proxy/NAT policy; постоянный open egress для ВМ2 не является допустимым production состоянием.
|
||||
|
||||
### 4.2. DNS
|
||||
|
||||
Создать `A <PUBLIC_HOST> → <VM_PUBLIC_IP>`. Не добавлять `www`, если он не нужен и не включён в certificate. Для отдельного API host действуют правила arch-03; MVP предпочтительно использует один host с paths.
|
||||
Создать `A <PUBLIC_HOST> → <VM1_PUBLIC_IP>`, `A <PROCESSING_PUBLIC_HOST> → <VM2_PUBLIC_IP>` и private DNS `processing.internal → <VM2_PRIVATE_IP>`. Public host ВМ2 используется только CRM webhook; private name не публикуется во внешнем DNS.
|
||||
|
||||
Проверка с рабочей станции:
|
||||
|
||||
```bash
|
||||
dig +short <PUBLIC_HOST>
|
||||
dig +short <PROCESSING_PUBLIC_HOST>
|
||||
```
|
||||
|
||||
Ответ должен совпасть с `<VM_PUBLIC_IP>`.
|
||||
@@ -143,8 +173,9 @@ sudo DEPLOY_USER=deploy \
|
||||
- проверить его версию/review;
|
||||
- не передавать реальные IP/ключи в git;
|
||||
- проверить auto reboot unattended upgrades относительно maintenance;
|
||||
- решить, действительно ли deploy user нужен в группе `docker` (это root-equivalent);
|
||||
- не применять `AllowTcpForwarding no`, если утверждённый break-glass DB tunnel необходим; предпочтителен VPN/bastion.
|
||||
- гарантировать, что `deploy` не добавлен в группу `docker` (это root-equivalent);
|
||||
- оставить `AllowTcpForwarding no` по умолчанию; если нужен DB tunnel, создать отдельного `tunnel` с `AllowTcpForwarding local`, конкретным `PermitOpen`, без sudo/TTY/agent/X11 forwarding;
|
||||
- установить root-owned systemd-units и `/etc/sudoers.d/deploy` с полными командами и конкретными unit names без wildcard.
|
||||
|
||||
### 5.2. Проверки
|
||||
|
||||
@@ -160,12 +191,14 @@ df -h
|
||||
free -h
|
||||
```
|
||||
|
||||
Открыть второй SSH session как `deploy`, затем отключить root/password login. `.env` позже имеет mode `0600`.
|
||||
Открыть второй SSH session как `deploy`, затем отключить root/password login. Production `.env` позже содержит только non-secret config; runtime secrets доставляются по arch-06.
|
||||
|
||||
### Gate 2
|
||||
|
||||
- [ ] SSH key login `deploy` проверен во втором сеансе.
|
||||
- [ ] Root/password auth выключены.
|
||||
- [ ] `deploy` не состоит в группе `docker`; `sudo -l` содержит только утверждённые конкретные systemd-команды.
|
||||
- [ ] Production compose, units, deploy scripts и secret mappings принадлежат root и недоступны `deploy` на запись.
|
||||
- [ ] UFW и DOCKER-USER активны после restart Docker.
|
||||
- [ ] Docker Engine/Compose plugin закреплены поддерживаемой версией.
|
||||
- [ ] NTP active; disk/swap соответствуют sizing.
|
||||
@@ -173,6 +206,32 @@ free -h
|
||||
|
||||
**Ожидаемый результат:** reboot VM не теряет SSH, firewall и Docker service.
|
||||
|
||||
### 5.3. Дополнительный lockdown private/no-egress VM
|
||||
|
||||
Для SigNoz и другой VM, которая после раскатки не должна иметь internet ingress/egress, bootstrap выполняется по lifecycle arch-06.
|
||||
|
||||
До закрытия временного доступа:
|
||||
|
||||
- [ ] SSH разрешён только из trusted ops CIDR и только по ключам.
|
||||
- [ ] Пакеты/images получены из утверждённых источников, версии/digests зафиксированы.
|
||||
- [ ] Health/readiness успешны.
|
||||
- [ ] Проверены необходимые private flows (например app-VM → OTLP/SigNoz).
|
||||
- [ ] Проверен private путь администрирования через bastion/VPN.
|
||||
- [ ] Секреты размещены root-owned файлами; временные копии удалены.
|
||||
|
||||
Lockdown:
|
||||
|
||||
- [ ] Public IP удалён, если поддерживается и не нужен.
|
||||
- [ ] Public SSH и любой internet ingress удалены из cloud SG и host firewall.
|
||||
- [ ] Общий internet egress закрыт; оставлены только явно утверждённые private flows.
|
||||
- [ ] С внешней машины SSH и service ports недоступны.
|
||||
- [ ] С VM не проходит неразрешённый internet egress.
|
||||
- [ ] Из private network работают SSH и обязательные service flows.
|
||||
- [ ] Временные bootstrap credentials/rules/files удалены.
|
||||
- [ ] Результат и время закрытия записаны в release checklist.
|
||||
|
||||
Повторное открытие ingress/egress выполняется только как ограниченная по CIDR/destination и времени break-glass операция. После неё весь lockdown checklist повторяется.
|
||||
|
||||
## 6. Stage 3 — managed PostgreSQL
|
||||
|
||||
### 6.1. Защита и восстановление managed PostgreSQL
|
||||
@@ -188,7 +247,7 @@ free -h
|
||||
|
||||
CA managed PostgreSQL скачать из панели или документации провайдера в защищённый путь VM, например `/opt/han-chat/secrets/pg/ca.pem`, с владельцем `root:deploy` и mode `0440` (либо `0400`, если файл читает один пользователь). Все DSN используют `sslmode=verify-full` и `sslrootcert=/run/secrets/pg-ca.pem` либо эквивалент драйвера. Режимы `disable`, `allow`, `prefer` и `require` без проверки CA для production запрещены.
|
||||
|
||||
Публичный HTTPS-сертификат `<PUBLIC_HOST>` выпускается отдельно через **Let's Encrypt** на Stage 9. Он устанавливается в корневой nginx, а не в PostgreSQL.
|
||||
Публичные HTTPS-сертификаты `<PUBLIC_HOST>` и `<PROCESSING_PUBLIC_HOST>` выпускаются отдельно через **Let's Encrypt** на Stage 9 и устанавливаются только в nginx соответствующей VM, а не в PostgreSQL.
|
||||
|
||||
### 6.2. Роли
|
||||
|
||||
@@ -207,7 +266,7 @@ CA managed PostgreSQL скачать из панели или документа
|
||||
5. runtime: `USAGE`, DML и sequence grants только на свои objects;
|
||||
6. `ALTER DEFAULT PRIVILEGES` от migration owner;
|
||||
7. запретить чужие schemas и public schema create;
|
||||
8. `bitrix_sync_user` не получает `han_app` grants, пока module-07 остаётся stub.
|
||||
8. `bitrix_sync_user` получает только column/table grants и approved procedures из module-07 §13 после применения полной sync migration; broad schema write запрещён.
|
||||
|
||||
Команда bootstrap требует `<PROJECT_PATH>`:
|
||||
|
||||
@@ -241,7 +300,7 @@ psql "host=<PG_PRIVATE_HOST> port=<PG_PORT> dbname=<PG_DATABASE> user=<RUNTIME_U
|
||||
1. `api-backend` Alembic владеет `han_app`, triggers, seed;
|
||||
2. `bitrix-local-app` Alembic владеет `bitrix_local`;
|
||||
3. `message-safety` stub не создаёт PG tables до production implementation;
|
||||
4. `bitrix-sync` stub — optional empty baseline;
|
||||
4. `bitrix-sync` migration role владеет schema `bitrix_sync`; api-backend Alembic отдельно мигрирует shared `han_app.sync_queue`, triggers и mapping;
|
||||
5. Keycloak мигрирует standard tables сам; custom provider имеет собственные versioned migrations;
|
||||
6. `sms-service` владеет versioned migrations/seed schema `sms`; runtime `sms_user` не имеет доступа к `han_app`/`keycloak`.
|
||||
|
||||
@@ -282,18 +341,18 @@ CORS quarantine:
|
||||
{
|
||||
"AllowedOrigins": ["https://<PUBLIC_HOST>"],
|
||||
"AllowedMethods": ["PUT"],
|
||||
"AllowedHeaders": ["Content-Type", "x-amz-*"],
|
||||
"AllowedHeaders": ["Content-Type", "If-None-Match", "x-amz-checksum-sha256", "x-amz-*"],
|
||||
"ExposeHeaders": ["ETag", "x-amz-checksum-sha256"],
|
||||
"MaxAgeSeconds": 600
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Уточнить фактические required signed headers. Не разрешать `*` origin с credentials.
|
||||
Required signed headers для target flow: `Content-Type`, `If-None-Match: *`, checksum. Не разрешать `*` origin с credentials.
|
||||
|
||||
Lifecycle:
|
||||
|
||||
- quarantine: expire orphan objects только после периода, превышающего Safety poll + recovery; initial 2 дня, согласовать;
|
||||
- quarantine: failed/orphan expire через 48 ч и только при отсутствии active `safety_tasks`;
|
||||
- incomplete multipart upload: abort через 1 день;
|
||||
- attachments/documents: без auto-delete до legal retention;
|
||||
- noncurrent versions: policy после legal review.
|
||||
@@ -303,7 +362,10 @@ Lifecycle:
|
||||
- [ ] Все buckets private.
|
||||
- [ ] API key не может list/write вне exact scope.
|
||||
- [ ] Safety key не может write/delete.
|
||||
- [ ] Browser test origin выполняет presigned PUT.
|
||||
- [ ] Browser test origin выполняет presigned PUT с checksum и `If-None-Match: *`; повтор того же key получает `412`.
|
||||
- [ ] Complete фиксирует authoritative `version_id`, ETag и checksum; Safety читает только эту version.
|
||||
- [ ] Wrong version/ETag и изменённый source дают deny/error и не promote-ятся.
|
||||
- [ ] Conditional promote mismatch не создаёт delivery outbox/Bitrix call.
|
||||
- [ ] Quarantine lifecycle не удалит active `safety_tasks`.
|
||||
- [ ] Data lifecycle соответствует retention.
|
||||
|
||||
@@ -483,7 +545,11 @@ cd <BACKEND_ROOT>
|
||||
docker compose config --services
|
||||
```
|
||||
|
||||
В целевом real-SMS release ожидаются: `nginx`, `api-backend`, `message-safety`, `keycloak`, `sms-service`, `sms-worker` (либо документированный worker process), `bitrix-sync`, `bitrix-local-app`, `redis`, `otel-collector` и one-shot jobs/profile components.
|
||||
В target release:
|
||||
|
||||
- root Compose ВМ1: edge `nginx`, `api-backend`, `keycloak`, `sms-service`, `sms-worker`, `bitrix-local-app`, Redis DB0/DB1, local `otel-collector`;
|
||||
- root Compose ВМ2: собственный public/private nginx, `message-safety-api`, `message-safety-worker`, `clamd`, `freshclam`, `bitrix-sync`, Redis Safety, local `otel-collector`;
|
||||
- API и worker Safety используют один immutable image, но отдельные processes/containers. MVP: 1 API + 1 worker container с 5 file-worker slots; при провале gates сначала увеличиваются slots/worker replicas по queue depth.
|
||||
|
||||
Networks:
|
||||
|
||||
@@ -520,7 +586,7 @@ Redis: ACL, AOF everysec, RDB, maxmemory, volume, no host port. OTEL: config rea
|
||||
|
||||
## 12. Stage 9 — TLS bootstrap, фаза 1
|
||||
|
||||
Прототип `ssl-issue.sh` останавливает весь Compose и использует standalone Certbot. Для full stack предпочтителен **webroot two-phase**, чтобы не делать `compose down`.
|
||||
Прототип `ssl-issue.sh` останавливает весь Compose и использует standalone Certbot. Для full stack предпочтителен **webroot two-phase**, чтобы не делать `compose down`. Процедура выполняется независимо в root Compose каждой VM: для ВМ1 с `<PUBLIC_HOST>`, для ВМ2 с `<PROCESSING_PUBLIC_HOST>`.
|
||||
|
||||
### Phase A: HTTP bootstrap
|
||||
|
||||
@@ -528,7 +594,7 @@ Redis: ACL, AOF everysec, RDB, maxmemory, volume, no host port. OTEL: config rea
|
||||
2. Запустить nginx с bootstrap config: только `/.well-known/acme-challenge/` и redirect; TLS block не требует отсутствующий cert.
|
||||
3. Запустить Certbot profile:
|
||||
|
||||
Сначала проверить процесс через Let's Encrypt staging CA, добавив `--staging`. Staging-сертификат не является доверенным браузерами и нужен только для проверки DNS, firewall, ACME webroot и конфигурации. После успешной проверки удалить staging lineage либо выпустить production-сертификат с отдельным `--cert-name <PUBLIC_HOST>`.
|
||||
Сначала проверить процесс через Let's Encrypt staging CA, добавив `--staging`. Staging-сертификат не является доверенным браузерами и нужен только для проверки DNS, firewall, ACME webroot и конфигурации. После успешной проверки удалить staging lineage либо выпустить production-сертификат с отдельным `--cert-name` текущего host. Ни сертификат, ни ACME volume между VM не разделяются.
|
||||
|
||||
Production-выпуск:
|
||||
|
||||
@@ -654,7 +720,7 @@ docker compose run --rm api-backend alembic upgrade head
|
||||
docker compose run --rm bitrix-local-app alembic upgrade head
|
||||
```
|
||||
|
||||
Для message-safety stub PG migration отсутствует. Для bitrix-sync stub — baseline только если реализован. Keycloak стандартную schema мигрирует выбранная pinned версия при controlled startup; provider migration выполняется отдельным approved job.
|
||||
Для message-safety stub PG migration отсутствует. Перед full sync cutover сначала применяется backward-compatible api-backend migration shared queue/mapping/trigger, затем migration schema `bitrix_sync`; grants выдаются после обеих migrations и negative permission test. Keycloak стандартную schema мигрирует выбранная pinned версия при controlled startup; provider migration выполняется отдельным approved job.
|
||||
|
||||
### 13.3. Seed `app_settings`
|
||||
|
||||
@@ -698,6 +764,35 @@ Production cutover запрещён при любом placeholder, несогл
|
||||
|
||||
Rollback SMS: немедленно вернуть Keycloak в mock mode; не удалять schema/journal и не откатывать migrations без доказанной backward compatibility. Остановить новые real orders, дать worker завершить либо зафиксировать in-flight/`uncertain`; предпочтителен forward-fix.
|
||||
|
||||
### 13.5. Controlled rollout `bitrix-sync`
|
||||
|
||||
До включения `BITRIX_SYNC_ENABLED=true`:
|
||||
|
||||
1. создать/проверить custom Contact fields `user_id`, registration flag, citizenship и занести их non-secret names в env; для universal CRM имя `UF_CRM_<digits>` преобразуется в `ufCrm_<digits>`, active filter использует `1`, а wire-значения add/update подтверждаются contract test;
|
||||
2. создать smart process «Конфликты синхронизации», поля/стадии/ответственного/SLA и активировать validated `bitrix_sync.settings`;
|
||||
3. завести отдельный входящий webhook технического пользователя с минимальными правами module-07 §13;
|
||||
4. настроить два HTTP-webhook робота на Contact/alert receiver URLs `https://<PROCESSING_PUBLIC_HOST>/bitrix/sync/webhook/...?token=<receiver-token>`; использовать отдельные высокоэнтропийные query tokens из штатного secret manager, исключить query/body из журналов; local app/event handler для CRM sync не создавать;
|
||||
5. применить expand migrations `han_app`, затем `bitrix_sync`, после чего выдать точечные GRANT и выполнить negative permission tests;
|
||||
6. зафиксировать `cutover_watermark` и one-shot операцией отменить существующие до него pending/retry contact-задачи с причиной `initial_full_sync_cutover`;
|
||||
7. не создавать backfill: это утверждённое ограничение первого релиза;
|
||||
8. запустить image с sync disabled, проверить `/health/live`, expected `sync_disabled`, secret/config validation, portal host/member ID и smoke разрешённых Bitrix methods, включая `crm.item.list` для `entityTypeId=3` с `>=updatedTime`, `opened=1` и registration field `=1`;
|
||||
9. открыть на nginx ВМ2 только два exact webhook routes для version-controlled `BITRIX_WEBHOOK_ALLOWED_CIDRS`, выполнить nginx config test, valid/invalid source IP и query-token form-urlencoded contract tests, подтвердить отсутствие query/body в logs/traces и отсутствие запросов на ВМ1;
|
||||
10. включить sync, проверить обработку только post-watermark canary user, mapping в `bitrix_sync`, отсутствие CRM ID в App DB, suppression, alert/rebind и telemetry;
|
||||
11. наблюдать не менее agreed canary window queue age, CRM limit errors, DLQ, webhook/reconciliation lag, число Contact, восстановленных reconciliation без webhook, source-IP rejects и SigNoz alerts; scheduled full reconciliation не запускать.
|
||||
|
||||
Rollback: закрыть public webhook routes на nginx ВМ2 либо вернуть retryable `503`, остановить claims, bounded drain in-flight, установить `BITRIX_SYNC_ENABLED=false`. ВМ1 не изменяется. Уже созданные Contact/mapping автоматически не удалять; pending после watermark не отменять; schema downgrade с production rows не выполнять.
|
||||
|
||||
#### Изменение source IP Битрикс24
|
||||
|
||||
Сигнал для проверки allow-list — всплеск Contact, восстановленных инкрементальной reconciliation без обработанного webhook, особенно одновременно с ростом `webhook_rejected_total{reason="source_ip"}`.
|
||||
|
||||
1. Сопоставить окно всплеска с bounded-retention журналом source-IP rejects nginx ВМ2; query и body не извлекать и не сохранять.
|
||||
2. Подтвердить принадлежность нового адреса инфраструктуре Битрикс24/портала по согласованному каналу или контролируемым probe. Наличие корректного query token само по себе не является подтверждением.
|
||||
3. Добавить минимально необходимый IP/CIDR в version-controlled `BITRIX_WEBHOOK_ALLOWED_CIDRS`, выполнить peer review, manifest validation и `nginx -t` через штатный deployment unit.
|
||||
4. Применить safe reload, проверить приём Contact/alert webhook и отсутствие query/body в logs/traces.
|
||||
5. Убедиться, что source-IP rejects прекратились, webhook lag нормализовался, а следующие инкрементальные reconciliation run не показывают растущих восстановлений.
|
||||
6. При ошибочном расширении немедленно вернуть предыдущую approved версию allow-list. Автоматическое добавление наблюдаемого IP запрещено.
|
||||
|
||||
## 14. Stage 11 — Keycloak bootstrap
|
||||
|
||||
### 14.1. Первый старт
|
||||
@@ -749,33 +844,13 @@ Custom OTP tables мигрируются versioned mechanism до включен
|
||||
|
||||
Архитектурный порядок:
|
||||
|
||||
1. Redis;
|
||||
2. OTEL Collector;
|
||||
3. API backend/settings;
|
||||
4. SMS service/worker после migrations (при SMS release; Keycloak пока mock);
|
||||
5. Keycloak;
|
||||
6. Message Safety;
|
||||
7. Bitrix local app;
|
||||
8. Bitrix sync;
|
||||
9. nginx.
|
||||
1. На ВМ2 approved systemd unit поднимает Redis Safety и local Collector.
|
||||
2. Затем `clamd`/`freshclam`, Safety API/worker и `bitrix-sync`.
|
||||
3. Последним на ВМ2 поднимается nginx с независимыми public `80/443` и private `8443` server blocks; проверяются оба TLS-контура, exact webhook routes, capability health, signature age и отрицательные ingress/egress tests.
|
||||
4. На ВМ1 unit поднимает локальные Redis/Collector, API, SMS, Keycloak, local app и edge nginx.
|
||||
5. Только private `MESSAGE_SAFETY_URL` ВМ1 переключается на ВМ2 после Safety gates. Public CRM webhook DNS/routes ВМ2 разворачиваются независимо и не требуют изменения ВМ1.
|
||||
|
||||
Команды:
|
||||
|
||||
```bash
|
||||
cd <BACKEND_ROOT>
|
||||
docker compose up -d redis
|
||||
docker compose up -d otel-collector
|
||||
docker compose up -d api-backend
|
||||
docker compose up -d sms-service sms-worker
|
||||
docker compose up -d keycloak
|
||||
docker compose up -d message-safety
|
||||
docker compose up -d bitrix-local-app bitrix-sync
|
||||
docker compose up -d nginx
|
||||
docker compose up -d --wait api-backend keycloak sms-service bitrix-local-app
|
||||
docker compose exec -T nginx nginx -t -c /tmp/nginx.conf
|
||||
docker compose kill -s HUP nginx
|
||||
docker compose ps
|
||||
```
|
||||
Legacy single-VM `docker compose up` из старого stub-контура не является evidence готовности target ВМ2.
|
||||
|
||||
При повторной раскатке reload после readiness upstream обязателен: nginx
|
||||
разрешает Docker DNS при загрузке конфигурации и иначе может продолжить
|
||||
@@ -796,17 +871,17 @@ Expected:
|
||||
- Redis `PONG`;
|
||||
- Keycloak DB/realm/provider ready;
|
||||
- Collector health + exporter queue;
|
||||
- Safety ready и Redis DB2;
|
||||
- Safety v2 capability ready и Redis Safety (target); legacy DB2 проверяется только в stub acceptance;
|
||||
- API DB/Redis/JWKS/settings/S3/Safety ready;
|
||||
- local app до Bitrix install может быть `portal_not_installed`;
|
||||
- bitrix-sync возвращает `mode=db_connectivity_stub`, не CRM-ready;
|
||||
- bitrix-sync до enablement возвращает `sync_disabled`; после preflight — `mode=full`, validated settings/secrets/grants, живые worker/limiter и актуальные reconciliation cursors;
|
||||
- nginx config test success.
|
||||
|
||||
### Gate 12
|
||||
|
||||
- [ ] Все containers live, нет restart loop/OOM.
|
||||
- [ ] Critical readiness green.
|
||||
- [ ] Expected degraded statuses только Bitrix not-installed/sync stub.
|
||||
- [ ] Expected degraded statuses только документированные; sync stub mode отсутствует.
|
||||
- [ ] `docker compose ps` не публикует internal ports.
|
||||
- [ ] Internal `/internal/*` снаружи 404.
|
||||
- [ ] OTEL принимает telemetry.
|
||||
@@ -853,18 +928,22 @@ docker compose run --rm --no-deps <TOOLBOX_SERVICE> \
|
||||
|
||||
## 17. Stage 14 — public smoke и E2E
|
||||
|
||||
### 17.1. Edge
|
||||
### 17.1. Независимые public ingress
|
||||
|
||||
```bash
|
||||
curl -I http://<PUBLIC_HOST>/
|
||||
curl -fsS https://<PUBLIC_HOST>/api/v1/public/app-config
|
||||
curl -fsS https://<PUBLIC_HOST>/api/v1/public/content
|
||||
curl -fsS https://<PUBLIC_HOST>/auth/realms/han-chat/.well-known/openid-configuration
|
||||
curl -i https://<PUBLIC_HOST>/internal/safety/v1/messages/check
|
||||
curl -i https://<PUBLIC_HOST>/internal/safety/v2/messages/check
|
||||
openssl s_client -connect <PUBLIC_HOST>:443 -servername <PUBLIC_HOST>
|
||||
curl -I http://<PROCESSING_PUBLIC_HOST>/
|
||||
curl -i https://<PROCESSING_PUBLIC_HOST>/internal/sync/v1/status
|
||||
curl -i https://<PROCESSING_PUBLIC_HOST>/bitrix/sync/webhook/contact
|
||||
openssl s_client -connect <PROCESSING_PUBLIC_HOST>:443 -servername <PROCESSING_PUBLIC_HOST>
|
||||
```
|
||||
|
||||
Expected: 308; public 200 strict DTO; discovery 200; internal 404; valid cert.
|
||||
Expected ВМ1: 308; public 200 strict DTO; discovery 200; internal 404; valid cert. Expected ВМ2: HTTP redirect/ACME policy, internal 404, GET webhook 405/404, valid отдельный cert. Valid/invalid POST webhook проверяется отдельным form-urlencoded contract test с разрешённого и запрещённого source IP без помещения query token в shell history или логи.
|
||||
|
||||
### 17.2. Auth/frontend
|
||||
|
||||
@@ -883,7 +962,7 @@ Expected: 308; public 200 strict DTO; discovery 200; internal 404; valid cert.
|
||||
Обязательные E2E:
|
||||
|
||||
- text, начинающийся после normalization с `ф/Ф` → public `422 message_blocked`, Bitrix не вызван;
|
||||
- text с цифры → Safety `203`, API poll до `200` или test terminal `400`; клиент никогда не получает `203`;
|
||||
- legacy stub-only: text с цифры → v1 `203`/test terminal `400`; target v2 smoke использует `202`/`200|403|terminal 503`, клиент internal pending не получает;
|
||||
- прочий text → allow;
|
||||
- terminal stub `400` преобразуется в `422`, не в generic validation;
|
||||
- timeout → `503/504`, checkpoint/recovery, без duplicate;
|
||||
@@ -1196,8 +1275,11 @@ certbot delete active cert
|
||||
- managed PG private/TLS/backups/least privilege/migrations работают;
|
||||
- S3 private/IAM/CORS/lifecycle проверены;
|
||||
- exact release/images/frontend deployed;
|
||||
- `.env` validated, secrets protected;
|
||||
- один root Compose, один nginx, только 80/443;
|
||||
- non-secret `.env` validated; runtime secrets разделены по сервисам и защищены;
|
||||
- `deploy` не имеет Docker/root-equivalent доступа; production files root-owned, sudo ограничен конкретными systemd-units;
|
||||
- один root Compose и nginx на VM; ВМ1 и ВМ2 имеют независимые public 80/443, ВМ2 дополнительно private 8443; public ВМ2 ограничен exact CRM webhook;
|
||||
- container hardening и сетевые границы соответствуют arch-06;
|
||||
- для каждой private/no-egress VM завершён и задокументирован lockdown с негативной проверкой внешнего доступа/egress;
|
||||
- Redis/Collector volumes/resources/security работают;
|
||||
- Keycloak realm/provider/PKCE/OTP готов;
|
||||
- ordered startup/readiness пройден;
|
||||
@@ -1208,11 +1290,43 @@ certbot delete active cert
|
||||
- ops/incident/upgrade/DR owners назначены;
|
||||
- все assumptions/TBD приняты до открытия traffic.
|
||||
|
||||
### VM2 cutover, rollback, reprovision и Freshclam
|
||||
|
||||
Cutover gates: private TLS chain/SAN; Safety v2 PG migration и lease/fencing smoke; capability `text|links|files|worker`; S3 Gate 4; performance acceptance; egress negative tests. Safety Service Owner, Rule Pack Owner, Security Owner, Product Owner и Operations Owner фиксируют approvals. Только после них ВМ1 переключает `MESSAGE_SAFETY_URL`. Legacy v1 остаётся rollback target на ограниченное окно, но один `message_id` нельзя одновременно отправлять в v1 и v2.
|
||||
|
||||
Rollback возвращает caller adapter/upstream ВМ1 на предыдущий immutable release. Уже созданные v2 tasks завершаются/reconcile по PG checkpoints; down-migration и удаление quarantine versions запрещены.
|
||||
|
||||
При потере ВМ2 fail-open запрещён. ВМ2 reprovision-ится из immutable image/config; secrets materialize под отдельным IAM, Redis поднимается пустым, migrations/capability/egress gates повторяются. RTO ≤4 ч; restore rehearsal минимум дважды в год.
|
||||
|
||||
#### Message Safety config activation
|
||||
|
||||
Первая migration создаёт `message_safety.config_versions` и seed version 1; readiness не открывается без ровно одной valid active version. Config-only rollout выполняется отдельным root-owned migration/config job под config-admin DB role: создать immutable draft, проверить JSON Schema/cross-field constraints и наличие rules/detector artifacts, записать approvals, затем транзакционно activate. Runtime API/worker имеют только `SELECT` к config table.
|
||||
|
||||
После activation operator проверяет health `config_version`, text/link/file canaries, cache-key version и отсутствие изменения in-flight task version. Изменение, ослабляющее policy или увеличивающее limits, требует Security Owner; остальные — Safety Service Owner и Operations Owner. Rollback не реактивирует retired row: предыдущий payload клонируется в новую monotonic version и активируется с отдельным audit event.
|
||||
|
||||
#### Emergency MOCK
|
||||
|
||||
`deploy` может без root login включить/изменить/выключить mode:
|
||||
|
||||
```bash
|
||||
sudo /usr/local/sbin/han-message-safety-mode mock --text-free true --file-free true
|
||||
sudo /usr/local/sbin/han-message-safety-mode mock --text-free true --file-free false
|
||||
sudo /usr/local/sbin/han-message-safety-mode standard
|
||||
```
|
||||
|
||||
Root устанавливает helper и `/etc/sudoers.d/deploy-message-safety-mode` при bootstrap. Sudoers разрешает `deploy` только этот immutable `root:root 0755` executable; helper имеет строгий parser без shell eval/path arguments, атомарно обновляет `root:han-message-safety 0640` `/etc/han-chat/message-safety-mode.env` (dedicated GID `10001` доступен только non-root контейнеру), валидирует Compose config, выполняет фиксированную Message Safety API recreate/restart operation внутри root project и проверяет private health. На ошибке он восстанавливает предыдущий mode/config и повторяет restart. `deploy` не получает write к config, systemd units, Compose и Docker socket.
|
||||
|
||||
Перед включением operator фиксирует incident/change ID и выбранные text/file policies; после команды проверяет `processing_mode=mock`, forced canaries, отсутствие `202`, metric/active alert и audit actor. Ранее принятые standard tasks сохраняют mode и завершаются без переклассификации; только новые requests используют MOCK. Автоматического timeout нет: mode действует без ограничения по времени до явного `standard`. Поэтому перед закрытием incident обязателен возврат в standard, проверка normal capabilities и text/link/EICAR canary. File, разрешённый в MOCK, маркируется `scan_status=bypassed`, а не `clean`.
|
||||
|
||||
`freshclam` имеет controlled egress только к утверждённому signature CDN. Max signature age 24 ч; stale/failed update выключает только `files` и поднимает alert. Новая база проходит integrity/load/EICAR canary и atomic activate/reload; при regression возвращается последняя валидная база.
|
||||
|
||||
Initial ВМ2: 4 vCPU/8 GiB/80 GiB, resource/PID limits и backpressure. Workers масштабируются первыми по queue depth, `clamd` — scan lanes. ВМ3/scale-out инициируются при sustained CPU/RAM >70%, queue age >30 с, провале module-05 performance gates, contention `bitrix-sync` или независимом release cadence.
|
||||
|
||||
## 28. Допущения, TBD и архитектурные конфликты
|
||||
|
||||
### Допущения
|
||||
|
||||
- D-A1: одна VM и один public host на MVP.
|
||||
- D-A1: отдельные public hosts ВМ1/ВМ2; processing host публикует только CRM webhook, остальные сервисы ВМ2 остаются private.
|
||||
- D-A2: обязательный минимум — `otel-collector`; доступность remote backend не предполагается до закрытия D-TBD11, local Grafana stack не обязателен.
|
||||
- D-A3: managed provider даёт private network, TLS, backups/PITR.
|
||||
- D-A4: Bitrix portal/connector/line остаются разрешёнными значениями architecture.
|
||||
@@ -1221,25 +1335,25 @@ certbot delete active cert
|
||||
### TBD до production
|
||||
|
||||
- D-TBD1: реальные domains, Expo native redirect URI и Bitrix placement frame ancestors.
|
||||
- D-TBD2: final VM/PG sizing, RPS/WS, SLO/RPO/RTO.
|
||||
- D-TBD2: final VM1/PG sizing, public API/WS SLO и общие RPO/RTO; Safety load/latency gates уже зафиксированы, monthly availability SLO Safety в MVP намеренно не вводится.
|
||||
- D-TBD3: legal retention/erasure для PG/S3/audit/telemetry.
|
||||
- D-TBD4: secret manager и rotation windows.
|
||||
- D-TBD5: production Safety вместо stub и antivirus inbound operator files.
|
||||
- D-TBD6: полноценный bitrix-sync/GRANT или явное исключение CRM sync из release.
|
||||
- D-TBD5: реализовать/cutover production Safety v2; inbound operator files сознательно не проходят AV в MVP.
|
||||
- D-TBD6: реализовать код, migrations, portal fields/smart process/webhooks и выполнить bitrix-sync cutover по module-07.
|
||||
- D-TBD7: pinned Keycloak/nginx/Collector versions и SPI compatibility.
|
||||
- D-TBD8: exact migration/seed/toolbox CLI commands после реализации repo.
|
||||
- D-TBD9: Keycloak admin VPN/MFA topology.
|
||||
- D-TBD10: final CSP/CORS/S3 headers и cloud-specific IAM.
|
||||
- D-TBD11: выбрать observability backend/provider, endpoint/auth/retention и alert route либо явно ограничить среду acceptance-режимом без production-ready SLO.
|
||||
- D-TBD11: уточнить auth/retention/alert route выбранного private SigNoz; backend и endpoint уже зафиксированы.
|
||||
|
||||
### Обнаруженные конфликты
|
||||
|
||||
1. `arch-01/02/03` описывают полноценный `bitrix-sync`, но module-07 реализует только `SELECT 1`. Первый release не поддерживает обещанную CRM profile sync.
|
||||
2. `arch-01/02` ожидают production-like Safety и S3 scan, но module-05 — Redis-only random stub, file-only default allow и test-only terminal `400`. Это блокер настоящего production, даже если допустимо для production-like acceptance.
|
||||
1. `module-07` теперь задаёт полный target `bitrix-sync`; до реализации кода/migrations/portal prerequisites сервис обязан оставаться disabled, документация сама по себе не означает выполненный cutover.
|
||||
2. Текущая implementation остаётся v1 stub; module-05 описывает draft target v2. Cutover является production gate.
|
||||
3. Прототипный PG init даёт runtime role `CREATE` schema и не разделяет migration/runtime roles; runbook требует ужесточения.
|
||||
4. Прототипные TLS scripts используют отдельный service Compose/standalone downtime, тогда как целевая архитектура требует root Compose и two-phase webroot.
|
||||
5. Prototype публиковал `/bitrix-internal/*` и использовал `/internal/v1/*`; целевой контур это запрещает и использует `/internal/openlines/v1/*`.
|
||||
6. `arch-04` не содержит ряд proposed env из module-04–09; production `.env.example` должен быть синхронизирован до реализации.
|
||||
6. Runtime artifacts `.env.example`/Compose ещё могут не содержать зафиксированные VM2 env; документация не означает выполненный cutover.
|
||||
7. Точные RPO/RTO, SLO, Keycloak version и Bitrix retry semantics не утверждены; OTP TTL задаётся `app_settings`, SMS journal по module-11 хранится бессрочно.
|
||||
8. `init-managed-postgres.py` по умолчанию не задаёт TLS parameters при bootstrap connection и печатает credential-bearing DSN; его production-hardening обязателен.
|
||||
9. Текущие Compose/env/config artifacts могут ещё не содержать `sms-service`; документация не разрешает real mode до реализации и прохождения rollout gates.
|
||||
|
||||
Reference in New Issue
Block a user