Документация

К списку статей
05. Автономность и стек: ноль внешних обращений, ноль копилефта

Аудитория: суперадминистратор, ИБ-служба, комиссия по записи в Реестр. Закрывает вопрос: №7 («почему нет внешних сервисов / зачем именно этот стек»). Сценарий: S6 — защита перед комиссией: продукт автономен и отечественно-совместим.


Короткий ответ

«Лицензиар» работает в полностью изолированном контуре: в рантайме он не делает ни одного обращения в интернет, не тянет шрифты/аналитику/CDN, не звонит на чужие API. Всё ПО в составе поставки — на пермиссивных лицензиях без копилефта (MIT/ISC/BSD/Apache/0BSD). Это два сознательных гард-рейла, заданных с первого дня, потому что оба — условия записи в Реестр отечественного ПО и условия эксплуатации в закрытом контуре госорганизации.


Гард-рейл 1: ноль внешних обращений в рантайме

Зачем. Госконтур часто не имеет выхода в интернет; любое «фоновое» обращение к внешнему домену — это и риск ИБ, и потенциальный отказ при аттестации, и зависимость от иностранного сервиса (несовместимо с Реестром).

Как обеспечено:

  • Фронт собирается статически (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. Это и есть демонстрация автономности перед комиссией.

Гард-рейл 2: ноль копилефта

Зачем. Лицензии 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 — Роли и доступ.