Внесены правки в документацию
This commit is contained in:
@@ -217,6 +217,15 @@ AllowUsers deploy admin tunnel
|
||||
|
||||
`deploy` не запускает `docker`, `docker compose` или произвольные root-скрипты через sudo. Docker Compose запускается root-owned systemd-юнитом или root-owned deployment helper с фиксированным интерфейсом.
|
||||
|
||||
Обновление executable, config или helper, используемого уже активным
|
||||
`Type=oneshot` unit с `RemainAfterExit=yes`, требует явного
|
||||
`systemctl restart <unit>`. `enable --now` включает unit и стартует только
|
||||
неактивный unit, но не применяет новый helper к уже active/exited unit.
|
||||
Rollout проверяет фактический live state после restart: созданные firewall
|
||||
rules, freshness materialized secrets, owner/mode файлов и exit status
|
||||
последнего запуска. Один `ActiveState=active` не доказывает применение новой
|
||||
версии.
|
||||
|
||||
Sudoers хранится только в `/etc/sudoers.d/deploy` и проверяется через `visudo`. `/etc/sudoers` напрямую не редактируется.
|
||||
|
||||
Разрешения перечисляют полные команды и конкретные unit names без wildcard. Принципиальный пример:
|
||||
@@ -263,7 +272,16 @@ Root-owned и недоступны `deploy` на запись:
|
||||
- отсутствуют symlink/path traversal;
|
||||
- compose config прошёл валидацию;
|
||||
- image reference pinned по version/digest;
|
||||
- миграции и rollout соответствуют release manifest.
|
||||
- миграции и rollout соответствуют release manifest;
|
||||
- release manifest задаёт owner/group/mode для каждого класса файлов:
|
||||
data/config `0644` или строже, secrets отдельно, executable scripts,
|
||||
preflight, hooks и helpers — `0755`/`0750` по назначению;
|
||||
- перед активацией выполняется `test -x` для каждого executable из manifest.
|
||||
|
||||
Глобальный `rsync --chmod=F644`, рекурсивный `chmod` или иной blanket mode,
|
||||
снимающий executable bit со scripts/hooks/preflight, запрещён. Права
|
||||
восстанавливаются из version-controlled manifest или явными `install -m`
|
||||
для каждого класса артефактов, а не после первой ошибки запуска.
|
||||
|
||||
Если такого валидатора пока нет, compose/unit changes выполняет `admin`, а `deploy` ограничивается запуском уже подготовленного релиза. Выдавать `deploy` запись в production compose — не допустимая замена автоматизации.
|
||||
|
||||
@@ -313,9 +331,14 @@ Wildcard, временный `NOPASSWD: ALL` и включение в `docker` g
|
||||
Значения `uid`, `gid` и `mode` в Compose file secrets нельзя считать
|
||||
security boundary: Docker Compose при bind-backed secret может их игнорировать.
|
||||
Фактические owner/mode задаются host-side materializer'ом и проверяются через
|
||||
`stat` и негативный тест от постороннего UID. Предупреждение Compose об
|
||||
игнорировании этих атрибутов не подавляется и не трактуется как подтверждение
|
||||
прав.
|
||||
`stat`, позитивный read-test от фактического container UID/GID и негативный
|
||||
тест от постороннего UID. Проверяется также execute/traverse permission всех
|
||||
parent directories. `root:root 0600` нельзя bind-mount в процесс, работающий
|
||||
не от root: для несекретного control file используется dedicated host group,
|
||||
совпадающий с primary GID контейнера, и минимальный режим `0640`; secrets
|
||||
получают эквивалентный минимальный ACL/group contract. Предупреждение Compose
|
||||
об игнорировании этих атрибутов не подавляется и не трактуется как
|
||||
подтверждение прав.
|
||||
|
||||
Структурированные секреты валидируются до старта потребителя. Для PEM это
|
||||
означает проверку парсинга certificate/private key, отсутствие повторного
|
||||
@@ -382,7 +405,7 @@ revision, а не ручным `stamp` или правкой production DB.
|
||||
|
||||
| Путь | Владелец / режим | Назначение |
|
||||
|---|---|---|
|
||||
| `/opt/han-chat/releases/<version>` | `root:root`, `0755`/файлы `0644` | immutable release |
|
||||
| `/opt/han-chat/releases/<version>` | `root:root`; каталоги `0755`, data/config `0644`, executable по manifest `0755/0750` | immutable release |
|
||||
| `/opt/han-chat/current` | `root:root` | active release link; меняет только deployment helper/admin |
|
||||
| `/var/lib/han-deploy/incoming` | `deploy:deploy`, `0750` | загрузка неактивированных артефактов |
|
||||
| `/etc/han` | `root:root`, `0750` или строже | конфигурация и secret mappings |
|
||||
@@ -455,6 +478,27 @@ non-root + `read_only`, даже если сам daemon способен раб
|
||||
writable-path inventory, healthcheck semantics и фактический UID/GID считаются
|
||||
частью security contract образа.
|
||||
|
||||
### Ownership persistent named volumes
|
||||
|
||||
Новый Docker named volume нельзя считать writable для non-root process:
|
||||
начальный owner часто `root:root`, даже если target path в image принадлежит
|
||||
service UID. Persistent volume до запуска потребителя подготавливает
|
||||
идемпотентный one-shot init service:
|
||||
|
||||
- использует уже утверждённый pinned image с необходимыми `sh/chown/chmod`, а
|
||||
не непроверенный floating utility image;
|
||||
- запускается с `user: 0:0`, `read_only: true`, `cap_drop: [ALL]` и возвращает
|
||||
только `CHOWN`/`FOWNER`, если они действительно нужны;
|
||||
- не имеет сети (`network_mode: none`) и доступа к secrets;
|
||||
- меняет owner/mode только mount root конкретного volume, без recursive chown
|
||||
чужого state;
|
||||
- завершается с кодом `0`, а consumer зависит от
|
||||
`condition: service_completed_successfully`;
|
||||
- безопасно повторяется после recreate и отдельно проверяется в preflight.
|
||||
|
||||
Запуск основного collector/service от root, `chmod 0777` и ручная правка
|
||||
`/var/lib/docker/volumes` на host запрещены как способы исправления ownership.
|
||||
|
||||
### TLS для non-root edge
|
||||
|
||||
Root-only дерево ACME/Certbot не монтируется целиком в non-root nginx и не
|
||||
@@ -463,7 +507,11 @@ Root-only дерево ACME/Certbot не монтируется целиком
|
||||
`han-nginx-tls` (канонический GID `11001`); nginx получает этот каталог
|
||||
read-only. Renewal hook сначала обновляет staged files, затем выполняет полный
|
||||
config test и только после успеха отправляет reload. Права и соответствие
|
||||
certificate/key проверяются preflight.
|
||||
certificate/key проверяются preflight. Успешный hook обязан завершаться с
|
||||
кодом `0` и пустым stderr: benign output `nginx -t` и progress Compose
|
||||
перехватываются/подавляются на success path, но полностью выдаются в stderr
|
||||
при ненулевом exit code. Иначе Certbot/оркестратор может пометить успешный
|
||||
renewal как hook error.
|
||||
|
||||
### Daemon и updater как разные security-профили
|
||||
|
||||
@@ -500,6 +548,23 @@ capability.
|
||||
|
||||
Default policy для ingress — deny. Разрешение задаёт source, destination, protocol, port и назначение. Правила «вся private network на все порты» запрещены.
|
||||
|
||||
`DOCKER-USER` обрабатывает forwarded packet после Docker DNAT. Поэтому policy
|
||||
для published host ports сопоставляет original destination через
|
||||
`-m conntrack --ctorigdstport <HOST_PORT>` (или эквивалент nftables), а не
|
||||
текущий `--dport`, который уже может быть container port. Для каждого
|
||||
разрешённого host port обязательны:
|
||||
|
||||
- positive external/private probe и рост counter именно allow rule;
|
||||
- negative probe неразрешённого source/port и рост deny counter;
|
||||
- проверка `iptables -S`/nft ruleset после setup, restart firewall unit,
|
||||
restart Docker и reboot;
|
||||
- запрет считать UFW INPUT counter доказательством фильтрации published
|
||||
Docker port: такой трафик может обходить INPUT.
|
||||
|
||||
Firewall setup обязан обновлять helper и явно перезапускать active oneshot
|
||||
unit; наличие нового текста helper без изменения live rules является
|
||||
неуспешным rollout.
|
||||
|
||||
Cloud SG для managed PostgreSQL разрешает TLS-подключения только от VM/SG сервисов, которым нужна соответствующая схема. PostgreSQL, Redis, OTLP receivers, admin UI и internal API не публикуются в интернет.
|
||||
|
||||
### Egress
|
||||
@@ -561,9 +626,13 @@ Fail2ban обязателен для SSH, временно или постоян
|
||||
- root/password SSH отключены после проверки key access;
|
||||
- `deploy` не состоит в `docker` и имеет только конкретные systemd-команды;
|
||||
- production-файлы root-owned и недоступны `deploy` на запись;
|
||||
- release manifest сохраняет executable modes; preflight/hooks запускаются
|
||||
напрямую без обхода через `bash`;
|
||||
- каждый контейнер проверен по hardening baseline;
|
||||
- для каждого image digest проверены entrypoint, UID/GID и полный inventory
|
||||
writable paths;
|
||||
- bind files проходят positive read-test от container UID/GID и negative test
|
||||
от постороннего UID; named volumes подготовлены ограниченным init-job;
|
||||
- секреты разделены по сервисам/VM и отсутствуют в обычном `.env`;
|
||||
- bind-backed secrets и staged TLS проверены по фактическим owner/mode и
|
||||
содержимому, а не только по декларации Compose;
|
||||
@@ -571,7 +640,13 @@ Fail2ban обязателен для SSH, временно или постоян
|
||||
grants выданы и отозваны владельцем;
|
||||
- healthcheck проверяет процесс, реально присутствующий в контейнере, а
|
||||
updater freshness контролируется отдельным сигналом;
|
||||
- healthcheck-команда и все её binaries подтверждены внутри exact pinned
|
||||
digest; отсутствие `curl`/`wget` не обнаруживается впервые в production;
|
||||
- внутренние ports недоступны извне;
|
||||
- live `DOCKER-USER` rules проверены по original host ports после DNAT и
|
||||
переживают Docker restart/reboot;
|
||||
- обновлённые oneshot helpers применены explicit restart, а renewal hooks
|
||||
имеют пустой stderr при успехе;
|
||||
- backup/restore и rollback проверены в объёме релиза;
|
||||
- для private/no-egress VM завершён и зафиксирован lockdown;
|
||||
- проверена недоступность внешних портов и неразрешённого egress;
|
||||
|
||||
Reference in New Issue
Block a user