Ужесточить контейнеры сервисов
Прикладные образы работают non-root (deal, UID 10001, HOME=/tmp); в compose добавлены read_only root FS, tmpfs /tmp, no-new-privileges, cap_drop ALL и лимиты mem/cpus. Логи stateless-сервисов вынесены в /tmp/logs. Проверено compose config (dev/prod/observability).
This commit is contained in:
+2
-2
@@ -27,7 +27,7 @@
|
||||
| BL-KAFKA | Kafka как шина данных между сервисами (сейчас gRPC + БД-outbox) | ТЗ §12, обсуждение | P3 | DEFERRED |
|
||||
| BL-CF | Cloudflare / внешний периметр (сейчас Caddy, mTLS; конфиг вне кода) | ТЗ §11, техдок §10 | P2 | MANUAL |
|
||||
| BL-CI | Внешний CI/CD + SAST + скан зависимостей в пайплайне (сейчас вручную) | техдок §8 | P2 | BACKLOG |
|
||||
| BL-IMG-HARDEN | non-root/read-only дефолты контейнеров, seccomp/limits | техдок §10 | P2 | TECHDEBT |
|
||||
| BL-IMG-HARDEN | **Сделано (2026-09-11):** non-root USER (deal, UID 10001, HOME=/tmp) во всех прикладных образах; в compose — read_only root FS + tmpfs /tmp, no-new-privileges, cap_drop ALL, mem_limit/cpus; логи stateless-сервисов в /tmp/logs. Проверено `compose config` (dev/prod/observability); живой прогон — MANUAL | техдок §10 | P2 | DONE (live — MANUAL) |
|
||||
| BL-BACKUP-CRON | Автоматизация бэкапов (cron/systemd-примеры есть, реальный прогон — MANUAL) | техдок §9 | P2 | MANUAL |
|
||||
|
||||
## 3. SaaS / мультитенантность
|
||||
@@ -59,7 +59,7 @@
|
||||
| TD-OLD-DOCS | Исторические доки несут старые термины под пометками (переписывать не нужно) | docs sweep | P3 | TECHDEBT |
|
||||
| TD-SOURCE-PROVIDER | Провайдеры содержимого источников. **Сделано (2026-09-11):** `TelegramSourceContentProvider` + `ReadSource` RPC + `GET /api/cards/{id}/source` + UI «Обновить из источника». Осталось: провайдеры прочих источников по мере появления | generic source 2026-09-11 | P2 | TECHDEBT |
|
||||
| TD-STORE-ATTACH | Выгрузка вложений источника в Storage-сервис адаптером. **Решение (2026-09-11): медиа-посты Telegram пропускаем** — извлечение/выгрузка не делаются. Остались на будущее: вложения прочих источников (файл/диск/таблица) и `ISourceContentProvider` для remote-просмотра | generic source 2026-09-11 | P3 | BACKLOG |
|
||||
| TD-TG-CORE-SPLIT | Перенести оставшуюся Telegram-специфику ядра (`TelegramStore`, таблицы `Dialogs`/`TgMessages`, Discovery) в telegram-сервис. **Сделано (2026-09-11):** входящий поток переведён на generic `sources.proto`/`PushSource`, `PushMessage` удалён, приём в ядре generic (`SourceIngressGrpcService`). Осталось: каталог (`SyncDialogs`/`ReportStatus`), `TelegramStore`, Discovery | generic source 2026-09-11 | P2 | TECHDEBT |
|
||||
| TD-TG-CORE-SPLIT | Перенос оставшейся Telegram-специфики ядра в telegram-сервис. **Закрыто (2026-09-11): не требуется.** Задача «дашборды/карточки не знают о Telegram» решена generic-контрактом источника; оставшиеся `TelegramStore`/`Dialogs`/`TgMessages`/Discovery — это состояние тенанта (ядро — владелец данных, telegram-service — stateless-шлюз), перенос отдал бы шлюзу доступ к схеме тенанта | generic source 2026-09-11 | — | CLOSED |
|
||||
| TD-SOURCE-CONTACTS | Квалификатор контактов знает форматы профилей (t.me/`@handle`) — вынести в расширяемые правила источников | generic source 2026-09-11 | P3 | BACKLOG |
|
||||
| TD-APIMAP-COUNT | Ручной подсчёт числа операторских ручек в `api-map` (может расходиться с группировкой) | docs sweep | P3 | TECHDEBT |
|
||||
|
||||
|
||||
@@ -26,6 +26,17 @@
|
||||
# dotnet run, и сервисы ходят в ингресс хоста через DEAL_CORE_INGRESS=http://host.docker.internal:5082
|
||||
# (см. env telegram-service ниже).
|
||||
|
||||
# Общий hardening прикладных сервисов (BL-IMG-HARDEN): read-only root FS + tmpfs /tmp,
|
||||
# запрет новых привилегий и сброс capabilities. Данные — только в именованных volume.
|
||||
x-service-hardening: &service-hardening
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
@@ -72,6 +83,9 @@ services:
|
||||
context: ..
|
||||
dockerfile: src/core/Deal.Api/Dockerfile
|
||||
container_name: deal-core
|
||||
<<: *service-hardening
|
||||
mem_limit: 1g
|
||||
cpus: 1.5
|
||||
environment:
|
||||
ASPNETCORE_ENVIRONMENT: Development
|
||||
ASPNETCORE_URLS: http://0.0.0.0:5080
|
||||
@@ -123,11 +137,15 @@ services:
|
||||
context: ..
|
||||
dockerfile: src/telegram-service/Deal.Telegram/Dockerfile
|
||||
container_name: deal-telegram-service
|
||||
<<: *service-hardening
|
||||
mem_limit: 512m
|
||||
cpus: 1.0
|
||||
environment:
|
||||
GRPC_PORT: "5101"
|
||||
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:-deal_dev_service_token}
|
||||
DEAL_TELEGRAM_SESSION_KEY: ${DEAL_TELEGRAM_SESSION_KEY:-ZmVkY2JhOTg3NjU0MzIxMGZlZGNiYTk4NzY1NDMyMTA=}
|
||||
DEAL_TELEGRAM_SESSION_DIR: /data/sessions
|
||||
DEAL_LOGS_DIR: /tmp/logs
|
||||
SERVICES__CORE__INGRESS: ${DEAL_CORE_INGRESS:-http://core:5082}
|
||||
ports:
|
||||
- "5101:5101"
|
||||
@@ -151,9 +169,13 @@ services:
|
||||
context: ..
|
||||
dockerfile: src/ai-service/Deal.Ai/Dockerfile
|
||||
container_name: deal-ai-service
|
||||
<<: *service-hardening
|
||||
mem_limit: 512m
|
||||
cpus: 1.0
|
||||
environment:
|
||||
GRPC_PORT: "5102"
|
||||
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:-deal_dev_service_token}
|
||||
DEAL_LOGS_DIR: /tmp/logs
|
||||
ports:
|
||||
- "5102:5102"
|
||||
healthcheck:
|
||||
@@ -173,10 +195,14 @@ services:
|
||||
context: ..
|
||||
dockerfile: src/ml-service/Deal.Ml/Dockerfile
|
||||
container_name: deal-ml-service
|
||||
<<: *service-hardening
|
||||
mem_limit: 768m
|
||||
cpus: 1.0
|
||||
environment:
|
||||
GRPC_PORT: "5103"
|
||||
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:-deal_dev_service_token}
|
||||
DEAL_ML_DATA_DIR: /data/ml # файлы моделей data/ml/<tenantId>.sqlite на volume deal_ml_data (Ruling 4/12)
|
||||
DEAL_LOGS_DIR: /tmp/logs
|
||||
ports:
|
||||
- "5103:5103"
|
||||
volumes:
|
||||
@@ -195,6 +221,9 @@ services:
|
||||
context: ..
|
||||
dockerfile: src/storage-service/Deal.Storage/Dockerfile
|
||||
container_name: deal-storage-service
|
||||
<<: *service-hardening
|
||||
mem_limit: 512m
|
||||
cpus: 1.0
|
||||
environment:
|
||||
GRPC_PORT: "5104"
|
||||
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:-deal_dev_service_token}
|
||||
@@ -203,6 +232,7 @@ services:
|
||||
DEAL_STORAGE_SECRET_KEY: deal_minio_secret
|
||||
DEAL_STORAGE_BUCKET: deal-attachments
|
||||
DEAL_STORAGE_SECURE: "false"
|
||||
DEAL_LOGS_DIR: /tmp/logs
|
||||
ports:
|
||||
- "5104:5104"
|
||||
depends_on:
|
||||
|
||||
@@ -48,6 +48,17 @@
|
||||
# Frontend: статика монтируется из ../src/frontend/dist (СОБРАТЬ ДО ПОДЪЁМА: cd src/frontend &&
|
||||
# npm run build). Конфигурация caddy — deploy/caddy/Caddyfile (см. его шапку про домен/TLS).
|
||||
|
||||
# Общий hardening прикладных сервисов (BL-IMG-HARDEN): read-only root FS + tmpfs /tmp,
|
||||
# запрет новых привилегий и сброс capabilities. Данные/логи — только в именованных volume или tmpfs.
|
||||
x-service-hardening: &service-hardening
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
@@ -84,6 +95,9 @@ services:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: src/core/Deal.Api/Dockerfile
|
||||
<<: *service-hardening
|
||||
mem_limit: 1g
|
||||
cpus: 1.5
|
||||
environment:
|
||||
ASPNETCORE_ENVIRONMENT: Production
|
||||
ASPNETCORE_URLS: http://0.0.0.0:5080
|
||||
@@ -149,11 +163,15 @@ services:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: src/telegram-service/Deal.Telegram/Dockerfile
|
||||
<<: *service-hardening
|
||||
mem_limit: 512m
|
||||
cpus: 1.0
|
||||
environment:
|
||||
GRPC_PORT: "5101"
|
||||
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:?DEAL_SERVICE_TOKEN не задан}
|
||||
DEAL_TELEGRAM_SESSION_KEY: ${DEAL_TELEGRAM_SESSION_KEY:?DEAL_TELEGRAM_SESSION_KEY не задан (ключ AES-GCM сессий)}
|
||||
DEAL_TELEGRAM_SESSION_DIR: /data/sessions
|
||||
DEAL_LOGS_DIR: /tmp/logs
|
||||
SERVICES__CORE__INGRESS: ${DEAL_CORE_INGRESS:-http://core:5082}
|
||||
DEAL_MTLS_ENABLED: ${DEAL_MTLS_ENABLED:-0}
|
||||
DEAL_MTLS_CA_PEM: /etc/deal/certs/ca.pem
|
||||
@@ -176,9 +194,13 @@ services:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: src/ai-service/Deal.Ai/Dockerfile
|
||||
<<: *service-hardening
|
||||
mem_limit: 512m
|
||||
cpus: 1.0
|
||||
environment:
|
||||
GRPC_PORT: "5102"
|
||||
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:?DEAL_SERVICE_TOKEN не задан}
|
||||
DEAL_LOGS_DIR: /tmp/logs
|
||||
DEAL_MTLS_ENABLED: ${DEAL_MTLS_ENABLED:-0}
|
||||
DEAL_MTLS_CA_PEM: /etc/deal/certs/ca.pem
|
||||
DEAL_MTLS_SERVER_CERT_PFX: /etc/deal/certs/ai-service-server.pfx
|
||||
@@ -199,10 +221,14 @@ services:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: src/ml-service/Deal.Ml/Dockerfile
|
||||
<<: *service-hardening
|
||||
mem_limit: 768m
|
||||
cpus: 1.0
|
||||
environment:
|
||||
GRPC_PORT: "5103"
|
||||
DEAL_SERVICE_TOKEN: ${DEAL_SERVICE_TOKEN:?DEAL_SERVICE_TOKEN не задан}
|
||||
DEAL_ML_DATA_DIR: /data/ml
|
||||
DEAL_LOGS_DIR: /tmp/logs
|
||||
DEAL_MTLS_ENABLED: ${DEAL_MTLS_ENABLED:-0}
|
||||
DEAL_MTLS_CA_PEM: /etc/deal/certs/ca.pem
|
||||
DEAL_MTLS_SERVER_CERT_PFX: /etc/deal/certs/ml-service-server.pfx
|
||||
|
||||
@@ -428,8 +428,15 @@ DEAL_MTLS_ENABLED=0|1 DEAL_MTLS_CERT_PASSWORD=... DEAL_DEFAULT_AI_BU
|
||||
- Криптография/код: пароли Argon2id; секреты настроек AES-256-GCM (`enc:`, ключ
|
||||
`DEAL_ENCRYPTION_KEY`); сессии Telegram AES-256-GCM (`DEAL_TELEGRAM_SESSION_KEY`); SQL
|
||||
параметризуется; секреты в логи/аудит не пишутся.
|
||||
- **Hardening контейнеров (BL-IMG-HARDEN, 2026-09-11):** прикладные образы (core/telegram/ai/ml/storage)
|
||||
работают non-root (пользователь `deal`, UID 10001) с `HOME=/tmp`; в compose заданы `read_only: true`,
|
||||
`tmpfs: /tmp`, `security_opt: no-new-privileges`, `cap_drop: ALL` и лимиты `mem_limit`/`cpus` (якорь
|
||||
`x-service-hardening`). Данные — в именованных volume (`/app/data` core, `/data/sessions` telegram,
|
||||
`/data/ml` ml); логи stateless-сервисов — `DEAL_LOGS_DIR=/tmp/logs` (tmpfs), у core — volume
|
||||
`/app/data/logs`. Проверено `docker compose config` (dev и prod, включая профиль observability);
|
||||
живой подъём с этими ограничениями — ⚠ Manual.
|
||||
- **Вне этапа (не настроено; заделы §11/roadmap):** Cloudflare (конфигурация вне кода — шапка
|
||||
Caddyfile), k8s, non-root/read-only-дефолты образов, биллинг, UI админок, саморегистрация.
|
||||
Caddyfile), k8s, биллинг, UI админок, саморегистрация.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -35,4 +35,9 @@ COPY --from=ghcr.io/grpc-ecosystem/grpc-health-probe:v0.4.35 /ko-app/grpc-health
|
||||
# Volume-ов нет: ai-service без БД и без персистентных файлов (Ruling 5) — конфиг провайдера
|
||||
# (id/base/model/apiKey/api_style) приходит в теле каждого запроса от ядра, ответы не хранятся.
|
||||
|
||||
# Non-root: процесс работает пользователем deal (UID 10001); персистентных файлов нет.
|
||||
RUN useradd --uid 10001 --shell /usr/sbin/nologin --no-create-home deal
|
||||
ENV HOME=/tmp
|
||||
USER deal
|
||||
|
||||
ENTRYPOINT ["dotnet", "Deal.Ai.dll"]
|
||||
|
||||
@@ -32,4 +32,12 @@ COPY --from=ghcr.io/grpc-ecosystem/grpc-health-probe:v0.4.35 /ko-app/grpc-health
|
||||
# (/app/data); каталог монтируется volume-ом deal_api_data из compose.dev.yml. В полном dev-стеке файлы
|
||||
# идут в MinIO (Storage__Minio__* env, см. compose.dev.yml) — volume остаётся для ключа шифрования.
|
||||
|
||||
# Non-root: процесс работает пользователем deal (UID 10001); /app/data — каталог данных (volume).
|
||||
# HOME=/tmp — рабочий каталог для временных/DP-файлов (совместимо с read_only root FS + tmpfs /tmp).
|
||||
RUN useradd --uid 10001 --shell /usr/sbin/nologin --no-create-home deal \
|
||||
&& mkdir -p /app/data \
|
||||
&& chown -R deal:deal /app/data
|
||||
ENV HOME=/tmp
|
||||
USER deal
|
||||
|
||||
ENTRYPOINT ["dotnet", "Deal.Api.dll"]
|
||||
|
||||
@@ -35,4 +35,11 @@ COPY --from=ghcr.io/grpc-ecosystem/grpc-health-probe:v0.4.35 /ko-app/grpc-health
|
||||
# Файлы моделей тенантов — data/ml/<tenantId>.sqlite (Ruling 4): каталог монтируется volume-ом
|
||||
# deal_ml_data из compose.dev.yml; каталог и файлы создаёт движок модели (задачи 5–6).
|
||||
|
||||
# Non-root: процесс работает пользователем deal (UID 10001); /data/ml — каталог моделей (volume).
|
||||
RUN useradd --uid 10001 --shell /usr/sbin/nologin --no-create-home deal \
|
||||
&& mkdir -p /data/ml \
|
||||
&& chown -R deal:deal /data/ml
|
||||
ENV HOME=/tmp
|
||||
USER deal
|
||||
|
||||
ENTRYPOINT ["dotnet", "Deal.Ml.dll"]
|
||||
|
||||
@@ -23,4 +23,10 @@ WORKDIR /app
|
||||
EXPOSE 5104
|
||||
COPY --from=build /app/publish .
|
||||
COPY --from=ghcr.io/grpc-ecosystem/grpc-health-probe:v0.4.35 /ko-app/grpc-health-probe /bin/grpc_health_probe
|
||||
|
||||
# Non-root: процесс работает пользователем deal (UID 10001); персистентных файлов нет.
|
||||
RUN useradd --uid 10001 --shell /usr/sbin/nologin --no-create-home deal
|
||||
ENV HOME=/tmp
|
||||
USER deal
|
||||
|
||||
ENTRYPOINT ["dotnet", "Deal.Storage.dll"]
|
||||
|
||||
@@ -35,4 +35,11 @@ COPY --from=ghcr.io/grpc-ecosystem/grpc-health-probe:v0.4.35 /ko-app/grpc-health
|
||||
# Сессии тенантов — файлы /data/sessions (Ruling 3): каталог монтируется volume-ом deal_tg_sessions
|
||||
# из compose.dev.yml; создаётся при первом сохранении сессии (задачи 9–11).
|
||||
|
||||
# Non-root: процесс работает пользователем deal (UID 10001); /data/sessions — каталог сессий (volume).
|
||||
RUN useradd --uid 10001 --shell /usr/sbin/nologin --no-create-home deal \
|
||||
&& mkdir -p /data/sessions \
|
||||
&& chown -R deal:deal /data/sessions
|
||||
ENV HOME=/tmp
|
||||
USER deal
|
||||
|
||||
ENTRYPOINT ["dotnet", "Deal.Telegram.dll"]
|
||||
|
||||
Reference in New Issue
Block a user