fix: harden sing-box egress rollout
This commit is contained in:
@@ -40,7 +40,7 @@ URI + policy
|
||||
▼ ├─ last-good backup
|
||||
no restart ├─ atomic replace
|
||||
├─ systemctl restart
|
||||
└─ bounded healthcheck
|
||||
└─ bounded local connectivity healthcheck
|
||||
│
|
||||
fail ┴ success
|
||||
│ │
|
||||
@@ -66,11 +66,11 @@ Outbound сохраняет DNS hostname и содержит `bind_interface=eth
|
||||
## Файловая модель
|
||||
|
||||
```text
|
||||
/etc/vpn-egress/
|
||||
/etc/vpn-egress/ root:root 0700
|
||||
├── policy.json root:root 0600
|
||||
└── hysteria2.uri root:root 0600
|
||||
|
||||
/var/lib/vpn-egress/
|
||||
/var/lib/vpn-egress/ root:root 0700
|
||||
├── state.json без секретов, 0600
|
||||
├── last-good.json содержит secrets, 0600
|
||||
└── backups/ ограниченная история, 0700/0600
|
||||
@@ -86,6 +86,11 @@ Guard читает имена интерфейсов из того же policy
|
||||
таблицу одной batch-транзакцией. Если новый ruleset некорректен, nft не оставляет
|
||||
систему с частично заменённой таблицей.
|
||||
|
||||
Package-managed drop-in добавляет для `sing-box.service` зависимости
|
||||
`Requires=` и `After=` от guard. Поэтому при совместном запуске ошибка guard
|
||||
блокирует старт sing-box. Уже активный oneshot не является watchdog: ручное
|
||||
удаление nftables-таблицы обнаруживает `doctor`, но не systemd dependency.
|
||||
|
||||
## Версионная граница
|
||||
|
||||
В коде существует только `renderer_1_13_19.py`. Renderer для 1.14 не является
|
||||
|
||||
@@ -40,8 +40,9 @@ fallback rule 32768.
|
||||
### `healthcheck`
|
||||
|
||||
По умолчанию выполняется HTTPS-запрос к Cloudflare trace и ожидается HTTP 200 с
|
||||
маркером `ip=`. Этот запрос идёт после запуска sing-box и подтверждает не только
|
||||
состояние systemd, но и рабочий data plane.
|
||||
маркером `ip=`. Этот локальный post-activation connectivity healthcheck идёт
|
||||
после запуска sing-box и проверяет состояние systemd и исходящую связность
|
||||
самого шлюза. Он не заменяет acceptance-тест forwarding с workload за `eth1`.
|
||||
|
||||
`url: null` оставляет только проверку `systemctl is-active`. Это допустимо для
|
||||
изолированного стенда, но слабее production-проверки.
|
||||
@@ -64,6 +65,9 @@ fallback rule 32768.
|
||||
1.13.19 используют разные виды certificate hash. `ech` будет добавлен только
|
||||
после доказанного преобразования формата config list.
|
||||
|
||||
Специальные символы в auth должны быть percent-encoded: сырой `@` отклоняется,
|
||||
а `%40` декодируется в `@`.
|
||||
|
||||
## Bandwidth
|
||||
|
||||
`up_mbps=50` и `down_mbps=200` являются локальной политикой и намеренно не
|
||||
|
||||
+29
-2
@@ -56,8 +56,11 @@ systemctl status vpn-egress-guard.service --no-pager
|
||||
nft list table inet vpn_egress_guard
|
||||
```
|
||||
|
||||
Новый unit имеет `Before=sing-box.service`; это закрывает boot window, который
|
||||
существовал у старого `After=sing-box.service`.
|
||||
Новый unit имеет `Before=sing-box.service`, а package-managed drop-in для
|
||||
`sing-box.service` добавляет `Requires=` и `After=` от guard. Это закрывает boot
|
||||
window и блокирует запуск sing-box, если совместно запущенный guard завершился с
|
||||
ошибкой. Зависимость не является watchdog для ручного удаления nftables-таблицы;
|
||||
текущее runtime-состояние проверяет `vpn-egressctl doctor`.
|
||||
|
||||
## 5. Dry run и первый import
|
||||
|
||||
@@ -116,7 +119,31 @@ ip -4 rule show
|
||||
ip -4 route show table 2022
|
||||
nft list table inet vpn_egress_guard
|
||||
nft list table inet sing-box
|
||||
systemctl show sing-box.service -p Requires -p After
|
||||
```
|
||||
|
||||
В `config.json` и nftables не должно быть ни старого `185.156.108.141`, ни
|
||||
текущего `85.208.119.160`.
|
||||
|
||||
С настоящего workload в `10.30.0.0/24` обязательно проверить:
|
||||
|
||||
1. DNS через шлюз;
|
||||
2. TCP и UDP через VPN;
|
||||
3. HTTPS-запрос к контролируемому endpoint или Cloudflare trace;
|
||||
4. соответствие наблюдаемого public IP ожидаемому VPN egress;
|
||||
5. повтор тех же проверок после restart sing-box.
|
||||
|
||||
Anti-leak проверяется только из консоли canary/staging, чтобы не потерять
|
||||
удалённый доступ к production:
|
||||
|
||||
```bash
|
||||
# На шлюзе:
|
||||
systemctl stop sing-box.service
|
||||
|
||||
# На workload за eth1: запрос наружу должен завершиться ошибкой, а не пойти напрямую.
|
||||
curl --fail --connect-timeout 5 https://www.cloudflare.com/cdn-cgi/trace
|
||||
|
||||
# На шлюзе:
|
||||
systemctl start sing-box.service
|
||||
vpn-egressctl doctor
|
||||
```
|
||||
|
||||
+4
-3
@@ -48,9 +48,10 @@ vpn-egressctl rollback
|
||||
перезапускает сервис и выполняет тот же healthcheck. Повторный rollback возвращает
|
||||
конфигурацию, которая была активна до первого rollback.
|
||||
|
||||
После ручного rollback desired URI остаётся прежним, поэтому status показывает
|
||||
drift. Перед включением нового `sync` нужно либо исправить URI, либо осознанно
|
||||
вернуть desired state.
|
||||
После ручного rollback desired URI остаётся прежним. `status` показывает
|
||||
`status=rolled_back`, а `doctor` сообщает `config-drift`, потому что production
|
||||
config отличается от заново отрендеренного desired state. Перед новым `sync`
|
||||
нужно либо исправить URI, либо осознанно вернуть desired state.
|
||||
|
||||
## Реакция systemd.path
|
||||
|
||||
|
||||
+10
-6
@@ -10,7 +10,8 @@ generated config и все backups.
|
||||
- Ошибки parser не включают исходное значение.
|
||||
- diff заменяет secret values на `<REDACTED>`.
|
||||
- state хранит только SHA-256 source/config и безопасный endpoint label.
|
||||
- URI/config/backups имеют `0600`, каталоги — `0700`.
|
||||
- Policy/URI/config/state/backups имеют `root:root 0600`, каталоги —
|
||||
`root:root 0700`; `doctor` проверяет этот ограниченный набор объектов.
|
||||
- subprocess вызывается массивом аргументов без shell.
|
||||
|
||||
После попадания действующего URI в чат, issue, shell history или journal оба
|
||||
@@ -22,7 +23,7 @@ credential следует перевыпустить.
|
||||
|
||||
- policy schema;
|
||||
- URI syntax и поддерживаемые параметры;
|
||||
- точная версия и `with_quic`;
|
||||
- точная версия, `with_quic` и `with_gvisor` для `stack=mixed`;
|
||||
- deterministic candidate;
|
||||
- `sing-box check`.
|
||||
|
||||
@@ -35,7 +36,9 @@ credential следует перевыпустить.
|
||||
на `eth0`. Она не принадлежит sing-box и остаётся отдельной от динамической
|
||||
таблицы `inet sing-box`.
|
||||
|
||||
Guard запускается до sing-box. Остановка или удаление guard unit является
|
||||
Guard запускается до sing-box. Drop-in `sing-box.service` одновременно задаёт
|
||||
requirement и ordering dependency: ошибка запуска guard блокирует sing-box.
|
||||
Остановка, удаление guard unit или ручное изменение его nftables-таблицы является
|
||||
security-sensitive операцией и не выполняется CLI автоматически.
|
||||
|
||||
Имена интерфейсов поступают из уже провалидированного policy, subprocess не
|
||||
@@ -43,9 +46,10 @@ security-sensitive операцией и не выполняется CLI авт
|
||||
|
||||
## Healthcheck
|
||||
|
||||
HTTPS healthcheck подтверждает data plane, но раскрывает проверочному endpoint
|
||||
факт обращения с VPN egress IP. URL можно заменить внутренним контролируемым
|
||||
endpoint. Отключение URL ослабляет проверку до состояния systemd.
|
||||
HTTPS healthcheck подтверждает локальную post-activation связность шлюза, но не
|
||||
весь forwarded path `eth1 -> TUN -> HY2`. Он раскрывает проверочному endpoint факт
|
||||
обращения с VPN egress IP. URL можно заменить внутренним контролируемым endpoint.
|
||||
Отключение URL ослабляет проверку до состояния systemd.
|
||||
|
||||
## Ограничения URI 1.13.19
|
||||
|
||||
|
||||
+18
-4
@@ -6,7 +6,7 @@
|
||||
2. Failure injection: candidate rejection, restart failure, URI/config rollback,
|
||||
idempotency и manual rollback swap.
|
||||
3. Real binary: `sing-box 1.13.19 check` на Linux и schema/format validation на Windows.
|
||||
4. Privileged Linux: TUN, nftables, systemd и HTTP data-plane healthcheck.
|
||||
4. Privileged Linux: TUN, nftables, systemd и локальный HTTP connectivity healthcheck.
|
||||
5. Canary: DNS A change без endpoint CIDR и credential rotation.
|
||||
6. Reboot: guard ordering, persisted config и path watcher.
|
||||
|
||||
@@ -22,9 +22,9 @@ make check
|
||||
SING_BOX_1_13_19=/usr/bin/sing-box make test
|
||||
```
|
||||
|
||||
Он предварительно проверяет exact version и `with_quic`. Windows-бинарник не
|
||||
может создать Linux auto-redirect и поэтому выполняет `format` полной схемы;
|
||||
обязательный `check` остаётся в Linux CI/Incus.
|
||||
Он предварительно проверяет exact version, `with_quic` и `with_gvisor`.
|
||||
Windows-бинарник не может создать Linux auto-redirect и поэтому выполняет
|
||||
`format` полной схемы; обязательный `check` остаётся в Linux CI/Incus.
|
||||
|
||||
## Privileged acceptance
|
||||
|
||||
@@ -39,4 +39,18 @@ SING_BOX_1_13_19=/usr/bin/sing-box make test
|
||||
7. изменить A-запись endpoint, не меняя URI/config;
|
||||
8. перезагрузить контейнер и повторить doctor.
|
||||
|
||||
Отдельный dependency failure test выполняется только в disposable Incus:
|
||||
|
||||
1. остановить sing-box и guard;
|
||||
2. временно задать в test policy отсутствующий upstream interface;
|
||||
3. убедиться, что `systemctl start sing-box.service` завершается ошибкой из-за
|
||||
неуспешного guard;
|
||||
4. восстановить policy и подтвердить успешный совместный запуск;
|
||||
5. проверить drop-in командой `systemd-analyze verify` и содержимое собранного
|
||||
Debian-пакета через `dpkg-deb -c`.
|
||||
|
||||
Локальный HTTPS healthcheck не заменяет запросы DNS/TCP/UDP с реального workload
|
||||
за `eth1`. При остановленном sing-box такой workload не должен получить прямой
|
||||
доступ через `eth0`.
|
||||
|
||||
Production A-запись не используется для эксперимента: нужен staging hostname.
|
||||
|
||||
@@ -48,7 +48,8 @@ timestamped backup из консоли контейнера, затем `sing-bo
|
||||
|
||||
Проверить DNS, handshake Hysteria2, доступность health URL и nftables. Временно
|
||||
ставить `url: null` на production нельзя без отдельного решения: это скрывает
|
||||
неработающий data plane.
|
||||
неработающую локальную post-activation связность. Даже успешный healthcheck не
|
||||
заменяет отдельную проверку forwarded path с workload за `eth1`.
|
||||
|
||||
## Watcher failed
|
||||
|
||||
|
||||
Reference in New Issue
Block a user