Аудитория: суперадминистратор, ИБ-служба, комиссия по записи в Реестр. Закрывает вопрос: №7 («почему нет внешних сервисов / зачем именно этот стек»). Сценарий: S6 — защита перед комиссией: продукт автономен и отечественно-совместим.
«Лицензиар» работает в полностью изолированном контуре: в рантайме он не делает ни одного обращения в интернет, не тянет шрифты/аналитику/CDN, не звонит на чужие API. Всё ПО в составе поставки — на пермиссивных лицензиях без копилефта (MIT/ISC/BSD/Apache/0BSD). Это два сознательных гард-рейла, заданных с первого дня, потому что оба — условия записи в Реестр отечественного ПО и условия эксплуатации в закрытом контуре госорганизации.
Зачем. Госконтур часто не имеет выхода в интернет; любое «фоновое» обращение к внешнему домену — это и риск ИБ, и потенциальный отказ при аттестации, и зависимость от иностранного сервиса (несовместимо с Реестром).
Как обеспечено:
- Фронт собирается статически (
next build, static export) — нет серверного рантайма Node,
тянущего внешнее. Шрифты, иконки (Phosphor), графики (Recharts) — в бандле, не с CDN.
- Все данные фронт берёт только со своего API того же origin (
/api/...), через nginx. - В проде CORS закрыт на конкретные origin (
CORS_ORIGINS, не*), cookie — same-origin.
Как проверить (приёмка):
# поднять стенд и убедиться, что в браузере Network нет запросов на внешние домены
docker compose -f docker-compose.phase1.yml up --build
# web: http://localhost:3007 api: http://localhost:8001В DevTools → Network не должно быть ни одного запроса за пределы localhost. Это и есть демонстрация автономности перед комиссией.
Зачем. Лицензии GPL/AGPL/MPL («копилефт») при включении в продукт могут требовать раскрытия исходного кода всего продукта. Для коммерческой поставки и депонирования это неприемлемо.
Как обеспечено:
- Прод-дерево зависимостей проверено: SBOM.csv — 61 пакет фронта, все пермиссивные
(MIT/ISC/BSD/Apache/0BSD), копилефта нет.
- Бэкенд — 21+ py-пакет, GPL/AGPL/MPL нет. LGPL допущен только у драйвера БД (
psycopg) и
argon2-cffi — LGPL при динамической линковке не «заражает» продукт.
- Ключевой пример выбора: обезличивание сделано на
gostcrypto(MIT), а неpygost
(GPL-3) — именно ради этого гард-рейла (см. статью 01).
- Проверка автоматизирована: скрипт лицензионного аудита license-sweep (гоняется в CI)
падает при появлении копилефт-пакета.
Как проверить:
bash deploy/license-sweep.sh # чисто = ни одного копилефт-пакета в проде
cat SBOM.csv # пофайлово: пакет → лицензия| Слой | Технология | Почему |
|---|---|---|
| Фронт | Next.js 14 (App Router) + TypeScript + Tailwind + shadcn/ui | Залоченный стек проекта; статический экспорт → автономность; типобезопасный контракт с бэкендом |
| Графики/иконки | Recharts + Phosphor | В бандле, без CDN |
| Бэкенд | FastAPI (Python) | Лёгкий, без тяжёлого фреймворк-рантайма; OpenAPI «из коробки» (/docs) |
| БД | PostgreSQL + RLS | Изоляция арендаторов на уровне СУБД (см. ниже), зрелость, отеч.-совместимость |
| Обезличивание | gostcrypto (Стрибог) | Отечественный алгоритм, MIT |
| Пароли | argon2-cffi (Argon2id) | Современный парольный хеш, разведён со Стрибогом |
| Сбор | APScheduler | Планировщик в отдельном контейнере, MIT |
| Упаковка | Docker Compose | Запуск одной командой, воспроизводимость |
Почему не «тяжёлый» стек (Keycloak/Kafka/Kubernetes/облако)? Продукт — портал аудита для одной–нескольких организаций, а не высоконагруженная платформа. Тяжёлая обвязка добавила бы внешние зависимости, копилефт-риски и сложность аттестации без пользы для задачи. SSO/Keycloak, очереди и Metabase осознанно оставлены «за горизонтом» (ROADMAP, Фаза 2+) — до реального спроса со стороны внедрения.
┌────────────┐ fetch /api/{tenant}/… ┌──────────────┐ SQL + RLS ┌────────────┐
│ web │ ──────────────────────► │ api │ ──────────► │ db │
│ Next 14 │ camelCase JSON │ FastAPI │ роль │ Postgres │
│ (static) │ ◄────────────────────── │ Стрибог,JWT │ licenziar_app│ sam+vault │
└────────────┘ └──────────────┘ (не суперюзер)└────────────┘
▲ nginx (CSP, same-origin) ▲
│ ┌──────────┴──────────┐
│ │ collector (worker) │ по расписанию собирает телеметрию
│ └─────────────────────┘
dbinit — разовый сервис: роль приложения, миграции, сид, Identity Vault.Ключевая деталь автономности и безопасности: роль приложения licenziar_app — не суперпользователь Postgres, поэтому Row-Level Security реально применяется (арендатор не видит чужие строки даже при SELECT * без WHERE). Подробно про изоляцию — в статье 07 и статье 02.
- Автономность: Network-вкладка пустая на внешние домены + nginx-конфиг с CSP.
- Лицензионная чистота: ведомость состава ПО (SBOM) + зелёный лицензионный аудит в CI.
- Импортонезависимость: отечественный Стрибог в обезличивании; стек разворачивается на
отечественной ОС (совместим с Astra Linux — есть в реестре распознавания систем).
- Воспроизводимость: один
docker compose upподнимает весь продукт без ручных шагов.
- Сборка web-образа — только `DOCKER_BUILDKIT=0` (legacy-сборщик). BuildKit на ряде машин
упирается в TLS-таймаут к Docker Hub, а docker compose build при этом молча отдаёт exit 0 — образ тихо не обновляется. Симптом: «поправил код, а на стенде по-старому».
- Не добавляйте внешние шрифты/аналитику «по привычке». Любой
<link>/fetchна чужой
домен ломает гард-рейл автономности — это регрессия, а не мелочь.
- Прод не стартует со стендовыми секретами.
validate_prod_secrets(fail-fast) валит
запуск, если JWT_SECRET/ANON_SALT/APP_DB_PASSWORD остались дефолтными — это защита, а не баг.
Связанные статьи: 01 — Не СКЗИ · 07 — Роли и доступ.