release: prepare 0.2.0 for sing-box 1.14
This commit is contained in:
+51
-70
@@ -2,97 +2,78 @@
|
||||
|
||||
## Границы ответственности
|
||||
|
||||
Hysteria2 URI содержит только переносимые реквизиты VPN:
|
||||
Hysteria2 URI содержит переносимые реквизиты: auth, hostname/ports, SNI,
|
||||
`insecure`, тип obfs и obfs password. Production HY2XS выдаёт Gecko URI.
|
||||
|
||||
- authentication;
|
||||
- hostname и port/port ranges;
|
||||
- SNI и `insecure`;
|
||||
- `salamander` и obfs password.
|
||||
|
||||
`policy.json` содержит локальную инфраструктурную политику:
|
||||
|
||||
- `eth0` — upstream;
|
||||
- `eth1` — VPN LAN;
|
||||
- `tun-sb0`, `172.19.0.1/30`, MTU 1400;
|
||||
- TUN routes, nftables marks и rule/table indexes;
|
||||
- bootstrap DNS и DoH через `hy2-out`;
|
||||
- bandwidth hints и healthcheck.
|
||||
|
||||
Renderer не патчит существующий JSON. Полная конфигурация каждый раз строится
|
||||
из typed model. Это устраняет config drift и исторические endpoint `/32`.
|
||||
`policy.json` хранит локальную инфраструктуру: `eth0`, `eth1`, TUN CIDR и MTU,
|
||||
route exclusions, таблицу 2022, marks, NFQUEUE, DNS, bandwidth и healthcheck.
|
||||
Renderer никогда не патчит старый JSON, а строит полный deterministic config из
|
||||
typed model.
|
||||
|
||||
## Поток применения
|
||||
|
||||
```text
|
||||
URI + policy
|
||||
│
|
||||
├─ strict parsing / schema validation
|
||||
├─ exact sing-box version and build-tag gate
|
||||
├─ deterministic render
|
||||
├─ secure candidate in /etc/sing-box
|
||||
└─ sing-box check -c candidate
|
||||
├─ strict validation
|
||||
├─ exact 1.14.0 + build-tag gate
|
||||
├─ renderer_1_14_0
|
||||
└─ sing-box check на private candidate
|
||||
│
|
||||
▼
|
||||
redacted comparison
|
||||
│
|
||||
unchanged ─────── changed
|
||||
│ │
|
||||
▼ ├─ last-good backup
|
||||
no restart ├─ atomic replace
|
||||
unchanged ───── changed
|
||||
│ │
|
||||
▼ ├─ проверка provenance текущего config
|
||||
state.json ├─ versioned last-good backup
|
||||
├─ atomic replace
|
||||
├─ systemctl restart
|
||||
└─ bounded local connectivity healthcheck
|
||||
│
|
||||
fail ┴ success
|
||||
│ │
|
||||
▼ ▼
|
||||
rollback state.json
|
||||
└─ bounded healthcheck
|
||||
```
|
||||
|
||||
Все операции изменения сериализованы `flock`-совместимой блокировкой. Поэтому
|
||||
ручной `import` и запоздалый event от systemd.path не могут применять два
|
||||
candidate одновременно.
|
||||
Первое применение после чистой установки не создаёт `last-good`: предыдущего
|
||||
управляемого конфига нет. При следующих изменениях backup разрешён только если
|
||||
SHA установленного config совпадает со state этого же релиза и sing-box 1.14.0.
|
||||
|
||||
## Почему endpoint IP не нужен
|
||||
## Gecko и DNS
|
||||
|
||||
Outbound сохраняет DNS hostname и содержит `bind_interface=eth0`. В 1.13.19
|
||||
`route.auto_detect_interface` не применяется к outbound с явным
|
||||
`bind_interface`. Bootstrap DNS также привязан к `eth0`. Endpoint IP поэтому не
|
||||
является частью TUN policy.
|
||||
Renderer добавляет для Gecko `min_packet_size=512` и
|
||||
`max_packet_size=1200`. Эти параметры являются частью локального HY2XS
|
||||
compatibility profile и не читаются из URI.
|
||||
|
||||
После смены A-записи следующая Hysteria2-сессия разрешает имя заново через
|
||||
`bootstrap-dns`. `doctor` дополнительно проверяет, что текущие A-записи не
|
||||
попали в exclusions.
|
||||
В sing-box 1.14.0 TUN по умолчанию использует `dns_mode=hijack`, что меняет
|
||||
настройки интерфейса и platform-level interception. Проект явно задаёт
|
||||
`dns_mode=disabled` и сохраняет собственное route action `port 53 ->
|
||||
hijack-dns`. Bootstrap UDP DNS привязан к `eth0`; remote DoH использует detour
|
||||
`hy2-out`.
|
||||
|
||||
## Endpoint routing
|
||||
|
||||
Hysteria outbound хранит hostname и `bind_interface=eth0`, а его
|
||||
`domain_resolver` указывает на bootstrap DNS через `eth0`. Публичный IP сервера
|
||||
не добавляется в `route_exclude_address`; смена A-записи не требует изменения
|
||||
policy.
|
||||
|
||||
## Файловая модель
|
||||
|
||||
```text
|
||||
/etc/vpn-egress/ root:root 0700
|
||||
├── policy.json root:root 0600
|
||||
└── hysteria2.uri root:root 0600
|
||||
/etc/vpn-egress/ root:root 0700
|
||||
├── policy.json root:root 0600
|
||||
└── hysteria2.uri root:root 0600
|
||||
|
||||
/var/lib/vpn-egress/ root:root 0700
|
||||
├── state.json без секретов, 0600
|
||||
├── last-good.json содержит secrets, 0600
|
||||
└── backups/ ограниченная история, 0700/0600
|
||||
/var/lib/vpn-egress/ root:root 0700
|
||||
├── state.json без секретов, 0600
|
||||
├── last-good.json содержит secrets, 0600
|
||||
├── last-good.meta.json версия и SHA-256, 0600
|
||||
└── backups/ config + соседний .meta, 0700/0600
|
||||
|
||||
/etc/sing-box/
|
||||
└── config.json root:root 0600
|
||||
/etc/sing-box/config.json root:root 0600
|
||||
```
|
||||
|
||||
Service sing-box уже имеет `CAP_DAC_READ_SEARCH`, поэтому сохраняется текущая
|
||||
рабочая модель `root:root 0600`.
|
||||
Metadata не обеспечивает доверие от root-компрометации. Её задача — исключить
|
||||
случайное смешивание релизов и повреждённых backup-файлов.
|
||||
|
||||
Guard читает имена интерфейсов из того же policy и устанавливает всю nftables
|
||||
таблицу одной 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 не является
|
||||
пустой заготовкой: он появится только в отдельной миграции после аудита схемы,
|
||||
маршрутизации и полных интеграционных тестов.
|
||||
0.2.0 не содержит кода миграции 0.1.0. `debian/preinst` отклоняет in-place
|
||||
upgrade, а `postinst` отклоняет legacy policy/state или unmanaged
|
||||
`/etc/sing-box/config.json`. Same-release reinstall допускается, если state и
|
||||
config согласованы.
|
||||
|
||||
+47
-44
@@ -2,73 +2,76 @@
|
||||
|
||||
## Policy schema version 1
|
||||
|
||||
Production-образец находится в `config/policy.json`. Неизвестные и отсутствующие
|
||||
ключи являются ошибкой.
|
||||
Production-образец находится в `config/policy.json`. Все секции имеют строгий
|
||||
набор ключей; неизвестные и отсутствующие значения являются ошибкой.
|
||||
|
||||
### `sing_box`
|
||||
|
||||
- `binary`: абсолютный путь к бинарнику;
|
||||
- `config_path`: production JSON;
|
||||
- `service`: systemd service;
|
||||
- `required_version`: допускается только `1.13.19`.
|
||||
- `config_path`: генерируемый production JSON;
|
||||
- `service`: имя systemd service;
|
||||
- `required_version`: только `1.14.0`.
|
||||
|
||||
### `runtime`
|
||||
|
||||
- `uri_path`: desired-state secret;
|
||||
- `state_dir`: state и backups;
|
||||
- `uri_path`: защищённый desired-state URI;
|
||||
- `state_dir`: state, last-good и backups;
|
||||
- `lock_path`: межпроцессная блокировка;
|
||||
- `backup_keep`: число timestamped backups, от 1 до 100.
|
||||
- `backup_keep`: число same-release backups, от 1 до 100.
|
||||
|
||||
### `network`
|
||||
|
||||
Здесь явно фиксируются интерфейсы, TUN CIDR, MTU, exclusions, таблица 2022,
|
||||
начальный rule index 9000, marks `0x2023`/`0x2024`/`0x2025`, NFQUEUE 100 и
|
||||
fallback rule 32768.
|
||||
|
||||
Публичные адреса не должны добавляться в `route_exclude_address`. Локальные
|
||||
подсети `10.20.0.0/24`, `10.30.0.0/24` и loopback сохраняются.
|
||||
Policy фиксирует `eth0`, `eth1`, `tun-sb0`, `172.19.0.1/30`, MTU 1400,
|
||||
таблицу 2022, rule index 9000, marks `0x2023/24/25`, NFQUEUE 100 и fallback
|
||||
rule 32768. Публичные endpoint IP не добавляются в exclusions.
|
||||
|
||||
### `dns`
|
||||
|
||||
Текущий контракт:
|
||||
|
||||
- UDP bootstrap `1.1.1.1:53`, bind `eth0`;
|
||||
- bootstrap `1.1.1.1:53`, bind `eth0`;
|
||||
- DoH `1.1.1.1:443/dns-query`, SNI `cloudflare-dns.com`;
|
||||
- DoH detour `hy2-out`;
|
||||
- только `ipv4_only`.
|
||||
- стратегия только `ipv4_only`.
|
||||
|
||||
### `healthcheck`
|
||||
### `bandwidth`
|
||||
|
||||
По умолчанию выполняется HTTPS-запрос к Cloudflare trace и ожидается HTTP 200 с
|
||||
маркером `ip=`. Этот локальный post-activation connectivity healthcheck идёт
|
||||
после запуска sing-box и проверяет состояние systemd и исходящую связность
|
||||
самого шлюза. Он не заменяет acceptance-тест forwarding с workload за `eth1`.
|
||||
`up_mbps=50` и `down_mbps=200` выбирают Hysteria congestion control. Поэтому
|
||||
renderer не задаёт `bbr_profile`: BBR profile применяется только когда bandwidth
|
||||
values не заданы.
|
||||
|
||||
`url: null` оставляет только проверку `systemctl is-active`. Это допустимо для
|
||||
изолированного стенда, но слабее production-проверки.
|
||||
## Hysteria2 URI
|
||||
|
||||
## Поддерживаемая часть Hysteria2 URI
|
||||
Production-форма HY2XS:
|
||||
|
||||
Поддерживаются:
|
||||
```text
|
||||
hysteria2://<peer-secret>@<host>:<port>/?insecure=0&obfs=gecko&obfs-password=<secret>&sni=<domain>
|
||||
```
|
||||
|
||||
- `hysteria2://` и `hy2://`;
|
||||
- percent-encoded auth, включая `username:password`;
|
||||
- DNS, IPv4, bracketed IPv6 и IDNA;
|
||||
- port 443 по умолчанию;
|
||||
- одиночный port и официальный multi-port/ranges;
|
||||
- `sni`, `insecure=0|1`;
|
||||
- `obfs=salamander&obfs-password=...`;
|
||||
- fragment как необязательное display name.
|
||||
Поддерживаются `hysteria2://`/`hy2://`, auth/userpass, DNS/IPv4/bracketed IPv6,
|
||||
IDNA, port hopping, `sni`, `insecure=0|1`, fragment, Gecko и Salamander.
|
||||
|
||||
Отклоняются `gecko`, `pinSHA256`, `ech`, client modes, неизвестные и
|
||||
повторяющиеся параметры. Причина для `pinSHA256`: Hysteria URI и sing-box
|
||||
1.13.19 используют разные виды certificate hash. `ech` будет добавлен только
|
||||
после доказанного преобразования формата config list.
|
||||
Правила obfs:
|
||||
|
||||
Специальные символы в auth должны быть percent-encoded: сырой `@` отклоняется,
|
||||
а `%40` декодируется в `@`.
|
||||
```text
|
||||
нет obfs -> допустимо для внешнего generic endpoint
|
||||
obfs=gecko -> требуется obfs-password; профиль всегда 512/1200
|
||||
obfs=salamander -> требуется obfs-password; compatibility fallback
|
||||
другой obfs -> ошибка
|
||||
password без obfs -> ошибка
|
||||
```
|
||||
|
||||
## Bandwidth
|
||||
Для HY2XS допустимым production-режимом считается только Gecko. Salamander не
|
||||
включается на сервере и не выбирается автоматически.
|
||||
|
||||
`up_mbps=50` и `down_mbps=200` являются локальной политикой и намеренно не
|
||||
принимаются из URI.
|
||||
Query parser применяет RFC percent-decoding, но не form decoding: сырой `+`
|
||||
сохраняется как `+`, `%2B` также превращается в `+`, а `%26`/`%3D` безопасно
|
||||
остаются частью значения после разбора разделителей.
|
||||
|
||||
`pinSHA256`, `ech`, Realm URI, client modes, неизвестные и повторяющиеся
|
||||
параметры отклоняются. Custom Gecko packet sizes через URI не принимаются:
|
||||
официальная Hysteria2 URI schema их не переносит.
|
||||
|
||||
## Healthcheck
|
||||
|
||||
По умолчанию проверяется Cloudflare trace: HTTP 200 и маркер `ip=`. Это
|
||||
проверяет локальную post-activation связность шлюза, но не заменяет E2E с
|
||||
workload за `eth1`. `url: null` допустим только для изолированного стенда.
|
||||
|
||||
+79
-116
@@ -1,71 +1,72 @@
|
||||
# Установка и миграция
|
||||
# Чистая установка версии 0.2.0
|
||||
|
||||
Инструкция рассчитана на Debian 13 внутри `vpn-egress-gw`.
|
||||
## Поддерживаемая модель
|
||||
|
||||
## 1. Резервная копия
|
||||
In-place upgrade 0.1.0 → 0.2.0, автоматическая миграция policy/state и запуск
|
||||
старого config под новым ядром не поддерживаются. Новый package `preinst`
|
||||
отклоняет upgrade. Установка выполняется через удаление старого пакета,
|
||||
архивирование его данных и настройку 0.2.0 с нуля.
|
||||
|
||||
До установки сохранить:
|
||||
Описанные действия выполнять из console/maintenance-доступа. До успешного E2E
|
||||
anti-leak guard должен оставаться в nftables.
|
||||
|
||||
## 1. Остановить автоматическое применение
|
||||
|
||||
```bash
|
||||
install -d -m 0700 /root/vpn-egress-migration
|
||||
cp -a /etc/sing-box/config.json /root/vpn-egress-migration/config.json.before
|
||||
cp -a /root/render-singbox-hy2.sh /root/vpn-egress-migration/
|
||||
cp -a /usr/local/sbin/vpn-egress-guard.sh /root/vpn-egress-migration/
|
||||
nft list ruleset > /root/vpn-egress-migration/nft.before.rules
|
||||
ip -4 rule show > /root/vpn-egress-migration/ip-rule.before.txt
|
||||
ip -4 route show table all > /root/vpn-egress-migration/ip-route.before.txt
|
||||
```
|
||||
|
||||
## 2. Обновление sing-box
|
||||
|
||||
Пакет имеет строгую зависимость `sing-box (= 1.13.19)`.
|
||||
|
||||
```bash
|
||||
apt-get update
|
||||
apt-get install sing-box=1.13.19
|
||||
sing-box version
|
||||
```
|
||||
|
||||
Если репозиторий SagerNet ещё не публикует 1.13.19, миграцию не продолжать и не
|
||||
обходить dependency/version gate.
|
||||
|
||||
## 3. Установка пакета
|
||||
|
||||
```bash
|
||||
dpkg -i vpn-egressctl_0.1.0_all.deb
|
||||
install -m 0600 -o root -g root \
|
||||
/usr/share/vpn-egressctl/policy.json \
|
||||
/etc/vpn-egress/policy.json
|
||||
systemctl daemon-reload
|
||||
```
|
||||
|
||||
Проверить локальные значения policy до первого применения.
|
||||
|
||||
## 4. Guard до VPN
|
||||
|
||||
Старый unit находится в `/etc/systemd/system` и перекрывает package unit из
|
||||
`/usr/lib`. Сначала обратимо убрать старое определение:
|
||||
|
||||
```bash
|
||||
systemctl stop vpn-egress-guard.service
|
||||
mv /etc/systemd/system/vpn-egress-guard.service \
|
||||
/root/vpn-egress-migration/vpn-egress-guard.service.disabled
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now vpn-egress-guard.service
|
||||
systemctl status vpn-egress-guard.service --no-pager
|
||||
systemctl disable --now vpn-egress-sync.path || true
|
||||
systemctl stop sing-box.service
|
||||
systemctl mask --runtime sing-box.service
|
||||
nft list table inet vpn_egress_guard
|
||||
```
|
||||
|
||||
Новый 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`.
|
||||
Если guard отсутствует, сначала восстановить защиту. Не продолжать переход с
|
||||
работающим прямым forwarding `eth1 -> eth0`.
|
||||
|
||||
## 5. Dry run и первый import
|
||||
## 2. Удалить старый package и архивировать данные
|
||||
|
||||
Для первого `check` URI ещё должен существовать. Создать файл без попадания
|
||||
secret в argv:
|
||||
```bash
|
||||
apt-get remove vpn-egressctl
|
||||
install -d -m 0700 /root/vpn-egress-0.1-archive
|
||||
|
||||
test ! -e /etc/vpn-egress || \
|
||||
mv /etc/vpn-egress /root/vpn-egress-0.1-archive/etc-vpn-egress
|
||||
test ! -e /var/lib/vpn-egress || \
|
||||
mv /var/lib/vpn-egress /root/vpn-egress-0.1-archive/var-lib-vpn-egress
|
||||
test ! -e /etc/sing-box/config.json || \
|
||||
mv /etc/sing-box/config.json /root/vpn-egress-0.1-archive/config.json
|
||||
```
|
||||
|
||||
Архив содержит secrets и должен оставаться `root:root 0700/0600`. Не копировать
|
||||
старые `policy.json`, `state.json` или `last-good.json` обратно в 0.2.0.
|
||||
|
||||
## 3. Установить exact sing-box 1.14.0
|
||||
|
||||
```bash
|
||||
apt-get update
|
||||
apt-get install sing-box=1.14.0
|
||||
sing-box version
|
||||
```
|
||||
|
||||
Вывод должен содержать exact `1.14.0`, Linux environment, `with_quic` и
|
||||
`with_gvisor`. Runtime mask не снимать: старый config уже архивирован, но новая
|
||||
policy ещё не настроена.
|
||||
|
||||
## 4. Установить vpn-egressctl 0.2.0
|
||||
|
||||
```bash
|
||||
dpkg -i vpn-egressctl_0.2.0_all.deb
|
||||
systemctl daemon-reload
|
||||
```
|
||||
|
||||
Если postinst сообщает о legacy state или unmanaged config, не обходить
|
||||
проверку: архивировать указанный объект и повторить `dpkg --configure
|
||||
vpn-egressctl`.
|
||||
|
||||
## 5. Настроить новую policy и Gecko URI
|
||||
|
||||
Редактировать новый `/etc/vpn-egress/policy.json` вручную. Из старой policy
|
||||
можно перенести осознанно проверенные локальные значения, но нельзя заменять ею
|
||||
новый файл целиком.
|
||||
|
||||
```bash
|
||||
install -m 0600 -o root -g root /dev/null /etc/vpn-egress/hysteria2.uri
|
||||
@@ -75,75 +76,37 @@ unset URI
|
||||
|
||||
vpn-egressctl check
|
||||
vpn-egressctl diff
|
||||
vpn-egressctl sync
|
||||
```
|
||||
|
||||
Более простой вариант для интерактивного применения:
|
||||
Проверить, что URI содержит `obfs=gecko`. `check` не запускает service.
|
||||
|
||||
## 6. Активировать защиту и config
|
||||
|
||||
```bash
|
||||
vpn-egressctl import --stdin
|
||||
systemctl enable --now vpn-egress-guard.service
|
||||
nft list table inet vpn_egress_guard
|
||||
systemctl unmask --runtime sing-box.service
|
||||
vpn-egressctl sync
|
||||
vpn-egressctl doctor
|
||||
```
|
||||
|
||||
Команда сама запросит URI без echo, выполнит check/apply/healthcheck и при
|
||||
неуспехе восстановит старые URI и config.
|
||||
После первого успешного sync `last-good` ещё отсутствует — это ожидаемо.
|
||||
|
||||
## 6. Включение watcher
|
||||
## 7. Acceptance и watcher
|
||||
|
||||
С workload за `eth1` проверить DNS, TCP, UDP, внешний VPN IP и отсутствие
|
||||
прямого WAN при остановленном sing-box. Серверный Gecko E2E выполняется отдельно
|
||||
владельцем HY2XS.
|
||||
|
||||
```bash
|
||||
systemctl enable --now vpn-egress-sync.path
|
||||
vpn-egressctl status --json
|
||||
vpn-egressctl doctor
|
||||
```
|
||||
|
||||
## 7. Вывод старого renderer из эксплуатации
|
||||
## Возврат к 0.1.0
|
||||
|
||||
После успешного canary и rollback-теста:
|
||||
|
||||
```bash
|
||||
mv /root/render-singbox-hy2.sh \
|
||||
/root/vpn-egress-migration/render-singbox-hy2.sh.disabled
|
||||
```
|
||||
|
||||
Удалять старый файл в день миграции не нужно: перемещение остаётся обратимым.
|
||||
Старый `/usr/local/sbin/vpn-egress-guard.sh` можно архивировать после проверки,
|
||||
что package-managed unit использует `vpn-egressctl guard-apply`.
|
||||
|
||||
## 8. Acceptance
|
||||
|
||||
Обязательные проверки:
|
||||
|
||||
```bash
|
||||
vpn-egressctl status
|
||||
vpn-egressctl doctor
|
||||
sing-box check -c /etc/sing-box/config.json
|
||||
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
|
||||
```
|
||||
0.2.0 не выполняет downgrade. Возврат — отдельная ручная чистая установка:
|
||||
удалить 0.2.0, архивировать её состояние, установить exact старые packages и
|
||||
только после этого восстановить согласованный snapshot 0.1.0. Нельзя запускать
|
||||
config одной версии под бинарником другой.
|
||||
|
||||
+23
-38
@@ -1,21 +1,19 @@
|
||||
# Эксплуатация
|
||||
|
||||
## Ротация credential
|
||||
|
||||
Интерактивно:
|
||||
## Ротация URI
|
||||
|
||||
```bash
|
||||
sudo vpn-egressctl import --stdin
|
||||
```
|
||||
|
||||
Из защищённого файла:
|
||||
Либо из защищённого файла режима 0600:
|
||||
|
||||
```bash
|
||||
sudo vpn-egressctl import --file /run/credentials/new-hysteria2.uri
|
||||
```
|
||||
|
||||
Файл должен иметь режим `0600`. Не используйте positional argument, environment
|
||||
variable, shell history или URL в тикете/логе.
|
||||
Production URI HY2XS должен использовать Gecko. Не передавать URI через argv,
|
||||
environment, shell history, тикет или журнал.
|
||||
|
||||
## Изменение policy
|
||||
|
||||
@@ -26,52 +24,39 @@ vpn-egressctl diff
|
||||
vpn-egressctl sync
|
||||
```
|
||||
|
||||
Path unit следит только за URI. Policy применяется явным `sync`, чтобы случайное
|
||||
редактирование инфраструктурных параметров не вызвало неожиданный restart.
|
||||
Path unit следит только за URI; policy применяется явным `sync`.
|
||||
|
||||
## Состояние
|
||||
## State и rollback
|
||||
|
||||
```bash
|
||||
vpn-egressctl status
|
||||
vpn-egressctl status --json
|
||||
```
|
||||
|
||||
State содержит только hashes, endpoint без auth, версию, время и результат.
|
||||
|
||||
## Rollback
|
||||
|
||||
```bash
|
||||
vpn-egressctl rollback
|
||||
```
|
||||
|
||||
Команда проверяет last-good реальным sing-box, атомарно меняет конфиги местами,
|
||||
перезапускает сервис и выполняет тот же healthcheck. Повторный rollback возвращает
|
||||
конфигурацию, которая была активна до первого rollback.
|
||||
Rollback относится только к конфигам, успешно управлявшимся этой же версией
|
||||
0.2.0 с sing-box 1.14.0. `last-good.meta.json` обязан совпадать по controller
|
||||
version, engine version и SHA-256. Legacy backup отклоняется.
|
||||
|
||||
После ручного rollback desired URI остаётся прежним. `status` показывает
|
||||
`status=rolled_back`, а `doctor` сообщает `config-drift`, потому что production
|
||||
config отличается от заново отрендеренного desired state. Перед новым `sync`
|
||||
нужно либо исправить URI, либо осознанно вернуть desired state.
|
||||
Первое clean apply не имеет предыдущего конфига и не создаёт `last-good`.
|
||||
Последующие успешные изменения сохраняют backup. Ручной rollback меняет current
|
||||
и last-good местами, поэтому повторная команда возвращает предыдущий current.
|
||||
|
||||
## Реакция systemd.path
|
||||
После rollback desired URI не меняется; `doctor` показывает config drift. До
|
||||
следующего `sync` нужно исправить URI либо осознанно вернуть desired state.
|
||||
|
||||
`PathChanged` запускает oneshot `vpn-egress-sync.service`. Одновременный ручной
|
||||
import сериализуется lock-файлом. Если новый URI некорректен, production config
|
||||
не меняется, а unit завершается с ошибкой, видимой в journal.
|
||||
## Watcher
|
||||
|
||||
`PathChanged` запускает `vpn-egress-sync.service`. Все операции сериализованы
|
||||
lock-файлом. Некорректный URI не меняет production config.
|
||||
|
||||
```bash
|
||||
journalctl -u vpn-egress-sync.service -n 100 --no-pager
|
||||
systemctl reset-failed vpn-egress-sync.service
|
||||
```
|
||||
|
||||
## Плановое обновление
|
||||
## Обновления package/engine
|
||||
|
||||
Обновление policy или пакета внутри 1.13.19:
|
||||
|
||||
1. `vpn-egressctl check`;
|
||||
2. `vpn-egressctl diff`;
|
||||
3. backup артефакта пакета;
|
||||
4. обновление;
|
||||
5. `vpn-egressctl sync`;
|
||||
6. `vpn-egressctl doctor`;
|
||||
7. контролируемый rollback-тест на staging.
|
||||
0.2.0 не объявляет совместимость с другими версиями приложения или sing-box.
|
||||
Любой следующий переход выполняется по отдельному release runbook как clean
|
||||
install. `apt upgrade` не считается допустимой процедурой обновления этого
|
||||
control plane.
|
||||
|
||||
+36
-40
@@ -2,57 +2,53 @@
|
||||
|
||||
## Секреты
|
||||
|
||||
Секретами считаются Hysteria authentication, obfs password, URI целиком,
|
||||
generated config и все backups.
|
||||
Секретами считаются auth, obfs password, URI целиком, generated config и все
|
||||
backup-файлы.
|
||||
|
||||
- CLI не принимает URI в argv.
|
||||
- Dataclass скрывает credentials из `repr()`.
|
||||
- Ошибки parser не включают исходное значение.
|
||||
- diff заменяет secret values на `<REDACTED>`.
|
||||
- state хранит только SHA-256 source/config и безопасный endpoint label.
|
||||
- Policy/URI/config/state/backups имеют `root:root 0600`, каталоги —
|
||||
`root:root 0700`; `doctor` проверяет этот ограниченный набор объектов.
|
||||
- subprocess вызывается массивом аргументов без shell.
|
||||
- CLI не принимает URI позиционным аргументом;
|
||||
- dataclass скрывает credentials из `repr()`;
|
||||
- parser errors не содержат исходный URI;
|
||||
- diff заменяет secret values на `<REDACTED>`;
|
||||
- state хранит hashes, версию и endpoint без auth;
|
||||
- URI/config/backups имеют `0600`, каталоги — `0700`;
|
||||
- subprocess запускается массивом аргументов без shell.
|
||||
|
||||
После попадания действующего URI в чат, issue, shell history или journal оба
|
||||
credential следует перевыпустить.
|
||||
При попадании URI в чат, issue, history или journal следует заменить оба
|
||||
credential.
|
||||
|
||||
## Fail closed
|
||||
|
||||
До изменения production проверяются:
|
||||
До изменения production проверяются strict policy, URI, exact 1.14.0,
|
||||
`with_quic`, `with_gvisor`, deterministic candidate и настоящий `sing-box
|
||||
check`. Неизвестные параметры не игнорируются.
|
||||
|
||||
- policy schema;
|
||||
- URI syntax и поддерживаемые параметры;
|
||||
- точная версия, `with_quic` и `with_gvisor` для `stack=mixed`;
|
||||
- deterministic candidate;
|
||||
- `sing-box check`.
|
||||
In-place package upgrade запрещён. Legacy state/config требует ручного
|
||||
архивирования; автоматической миграции или cross-version rollback нет.
|
||||
|
||||
Неизвестные параметры никогда не игнорируются. Production bypass для другой
|
||||
версии отсутствует.
|
||||
## Same-release backup provenance
|
||||
|
||||
Текущий config можно сохранить как last-good только если его SHA и engine
|
||||
version подтверждены state версии 0.2.0. Каждый автоматический backup имеет
|
||||
соседний `.meta`, а last-good — `last-good.meta.json`. Несовпадение checksum или
|
||||
версии блокирует rollback до изменения production.
|
||||
|
||||
Metadata защищает от операционной ошибки, но не от атакующего с root-доступом.
|
||||
|
||||
## Anti-leak
|
||||
|
||||
Отдельная таблица `inet vpn_egress_guard` отклоняет forwarding с `eth1` напрямую
|
||||
на `eth0`. Она не принадлежит sing-box и остаётся отдельной от динамической
|
||||
таблицы `inet sing-box`.
|
||||
Отдельная таблица `inet vpn_egress_guard` отклоняет forwarding `eth1 -> eth0`.
|
||||
Она не принадлежит sing-box и остаётся независимой от таблицы `inet sing-box`.
|
||||
Package drop-in требует успешного запуска guard перед sing-box.
|
||||
|
||||
Guard запускается до sing-box. Drop-in `sing-box.service` одновременно задаёт
|
||||
requirement и ordering dependency: ошибка запуска guard блокирует sing-box.
|
||||
Остановка, удаление guard unit или ручное изменение его nftables-таблицы является
|
||||
security-sensitive операцией и не выполняется CLI автоматически.
|
||||
Systemd dependency не является watchdog для ручного удаления nftables table;
|
||||
runtime-состояние проверяет `vpn-egressctl doctor`.
|
||||
|
||||
Имена интерфейсов поступают из уже провалидированного policy, subprocess не
|
||||
использует shell, а замена таблицы выполняется одной nft batch-транзакцией.
|
||||
## DNS и TLS
|
||||
|
||||
## Healthcheck
|
||||
`dns_mode=disabled` запрещает новому TUN-механизму 1.14 менять native DNS и
|
||||
platform interception. DNS контролируется явной route rule. Chrome QUIC
|
||||
parroting включён (`disable_chrome_parrot=false`); production acceptance обязан
|
||||
подтвердить совместимый RSA/ECDSA/ACME certificate, поскольку Ed25519 с этим
|
||||
режимом несовместим.
|
||||
|
||||
HTTPS healthcheck подтверждает локальную post-activation связность шлюза, но не
|
||||
весь forwarded path `eth1 -> TUN -> HY2`. Он раскрывает проверочному endpoint факт
|
||||
обращения с VPN egress IP. URL можно заменить внутренним контролируемым endpoint.
|
||||
Отключение URL ослабляет проверку до состояния systemd.
|
||||
|
||||
## Ограничения URI 1.13.19
|
||||
|
||||
`pinSHA256` не преобразуется в `certificate_public_key_sha256`, потому что это
|
||||
разные fingerprint semantics. `ech` не преобразуется без проверенного PEM/config
|
||||
list adapter. Silent downgrade TLS запрещён.
|
||||
`pinSHA256` и `ech` отклоняются: молчаливое ослабление TLS запрещено.
|
||||
|
||||
+47
-11
@@ -1,15 +1,51 @@
|
||||
# Почему sing-box 1.14 не поддерживается
|
||||
# Контракт sing-box 1.14.0
|
||||
|
||||
Ветка 1.14 меняет конфигурационный контракт TUN, TLS/QUIC и Hysteria2. Проект не
|
||||
пытается угадать совместимость и не содержит `--allow-unsupported`.
|
||||
Проект поддерживает ровно `sing-box 1.14.0`. RC, alpha, другие patch-релизы и
|
||||
будущие версии отклоняются до записи файлов.
|
||||
|
||||
Отдельная миграция на 1.14 потребует:
|
||||
## Принятые изменения 1.14
|
||||
|
||||
- аудита release notes и tagged schema;
|
||||
- нового renderer с отдельными golden fixtures;
|
||||
- проверки удалённых/deprecated полей;
|
||||
- повторного TUN/nftables/strict-route canary;
|
||||
- тестов Hysteria2 reconnect, DNS и rollback;
|
||||
- отдельного package release и runbook.
|
||||
### Gecko
|
||||
|
||||
До завершения этой работы любой 1.14.x отклоняется до записи файлов.
|
||||
```json
|
||||
{
|
||||
"type": "gecko",
|
||||
"password": "...",
|
||||
"min_packet_size": 512,
|
||||
"max_packet_size": 1200
|
||||
}
|
||||
```
|
||||
|
||||
512/1200 совпадает с profile HY2XS и текущими upstream defaults, но рендерится
|
||||
явно. Hysteria отмечает Gecko как experimental; проект осознанно принимает его
|
||||
как единственный production obfs сервера HY2XS.
|
||||
|
||||
### TUN DNS
|
||||
|
||||
Новый default `dns_mode=hijack` не используется. Renderer задаёт `disabled`,
|
||||
потому что проект уже имеет явную DNS route rule и отдельные bootstrap/DoH
|
||||
маршруты. Это решение должно подтверждаться privileged Linux acceptance.
|
||||
|
||||
### Chrome QUIC parroting
|
||||
|
||||
Renderer явно задаёт `disable_chrome_parrot=false`. Серверный certificate должен
|
||||
быть RSA/ECDSA/ACME, не Ed25519.
|
||||
|
||||
### Congestion control
|
||||
|
||||
Policy задаёт `up_mbps/down_mbps`, поэтому используется Hysteria CC. Поле
|
||||
`bbr_profile` не добавляется: оно предназначено для режима BBR при пустых
|
||||
bandwidth values.
|
||||
|
||||
## Не включено
|
||||
|
||||
Realm, mimic, ECH, certificate pinning, random hop interval, TLS spoof, custom
|
||||
Gecko sizes и новые UDP NAT knobs не входят в контракт 0.2.0.
|
||||
|
||||
## Официальные источники
|
||||
|
||||
- <https://github.com/SagerNet/sing-box/releases/tag/v1.14.0>
|
||||
- <https://sing-box.sagernet.org/configuration/outbound/hysteria2/>
|
||||
- <https://sing-box.sagernet.org/configuration/inbound/tun/>
|
||||
- <https://v2.hysteria.network/docs/developers/URI-Scheme/>
|
||||
- <https://v2.hysteria.network/docs/advanced/Full-Client-Config/>
|
||||
|
||||
+32
-36
@@ -2,55 +2,51 @@
|
||||
|
||||
## Уровни
|
||||
|
||||
1. Unit: URI, policy, version, renderer, redaction.
|
||||
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 connectivity healthcheck.
|
||||
5. Canary: DNS A change без endpoint CIDR и credential rotation.
|
||||
6. Reboot: guard ordering, persisted config и path watcher.
|
||||
1. Unit: URI, policy, version, renderer, redaction и metadata.
|
||||
2. Failure injection: candidate rejection, restart failure, URI/config restore,
|
||||
unmanaged config, corrupted/legacy last-good.
|
||||
3. Packaging: MPL-2.0, exact dependency, upgrade rejection и postinst guard.
|
||||
4. Real binary: официальный sing-box 1.14.0 для Gecko и Salamander configs.
|
||||
5. Privileged Linux: TUN, systemd, nftables, routing и DNS.
|
||||
6. HY2XS acceptance: Gecko TCP/UDP/DNS, failures, reconnect и reboot.
|
||||
|
||||
## Unit tests
|
||||
|
||||
На Linux:
|
||||
|
||||
```bash
|
||||
make check
|
||||
```
|
||||
|
||||
Тест с реальным бинарником включается явно:
|
||||
На Windows с isolated Python:
|
||||
|
||||
```bash
|
||||
SING_BOX_1_13_19=/usr/bin/sing-box make test
|
||||
```powershell
|
||||
E:\python-31312\python.exe -c "import sys,unittest; sys.path[:0]=[r'F:\projects\singbox_glue',r'F:\projects\singbox_glue\src']; s=unittest.defaultTestLoader.discover('tests',top_level_dir='.'); r=unittest.TextTestRunner(verbosity=2).run(s); raise SystemExit(not r.wasSuccessful())"
|
||||
```
|
||||
|
||||
Он предварительно проверяет exact version, `with_quic` и `with_gvisor`.
|
||||
Windows-бинарник не может создать Linux auto-redirect и поэтому выполняет
|
||||
`format` полной схемы; обязательный `check` остаётся в Linux CI/Incus.
|
||||
## Real binary
|
||||
|
||||
## Privileged acceptance
|
||||
```bash
|
||||
SING_BOX_1_14_0=/usr/bin/sing-box make test
|
||||
```
|
||||
|
||||
В disposable Incus-контейнере с двумя NIC:
|
||||
Тест сначала проверяет exact version/tags. На Linux выполняется `check`; на
|
||||
Windows — schema decoding через `format`, поскольку Windows binary не может
|
||||
создать Linux auto-redirect.
|
||||
|
||||
1. установить sing-box 1.13.19 и пакет;
|
||||
2. применить test URI;
|
||||
3. проверить таблицу 2022 и marks;
|
||||
4. подтвердить, что `eth1 -> eth0` отклоняется без sing-box;
|
||||
5. подтвердить TCP/UDP/DNS через TUN при рабочем HY2;
|
||||
6. сломать credential и проверить автоматический rollback;
|
||||
7. изменить A-запись endpoint, не меняя URI/config;
|
||||
8. перезагрузить контейнер и повторить doctor.
|
||||
## Privileged Debian acceptance
|
||||
|
||||
Отдельный dependency failure test выполняется только в disposable Incus:
|
||||
В disposable Incus/VM с двумя NIC проверить:
|
||||
|
||||
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`.
|
||||
1. чистую установку и отказ in-place upgrade;
|
||||
2. Gecko config и отсутствие last-good после первого apply;
|
||||
3. table 2022, rule 9000/32768, marks и NFQUEUE 100;
|
||||
4. bootstrap DNS через `eth0`, DoH через `hy2-out`;
|
||||
5. TCP/UDP/DNS с workload за `eth1`;
|
||||
6. отсутствие прямого WAN при остановленном sing-box;
|
||||
7. credential failure и same-release rollback;
|
||||
8. отказ legacy/mismatched metadata;
|
||||
9. A-record change и reboot recovery.
|
||||
|
||||
Локальный HTTPS healthcheck не заменяет запросы DNS/TCP/UDP с реального workload
|
||||
за `eth1`. При остановленном sing-box такой workload не должен получить прямой
|
||||
доступ через `eth0`.
|
||||
|
||||
Production A-запись не используется для эксперимента: нужен staging hostname.
|
||||
Server-side Gecko E2E выполняет владелец HY2XS. Salamander проверяется только на
|
||||
disposable test server и не добавляется в production server configuration.
|
||||
|
||||
+28
-34
@@ -9,54 +9,48 @@ vpn-egressctl status --json
|
||||
|
||||
## Unsupported sing-box version
|
||||
|
||||
Установлена не `1.13.19`. Конфигурация не изменялась. Проверить:
|
||||
Установлена не exact `1.14.0` либо отсутствуют `with_quic`/`with_gvisor`.
|
||||
Конфигурация не изменялась. Не обходить version gate.
|
||||
|
||||
```bash
|
||||
sing-box version
|
||||
apt-cache policy sing-box
|
||||
```
|
||||
## In-place upgrades are not supported
|
||||
|
||||
Не использовать ручной обход version gate.
|
||||
Пакет 0.2.0 устанавливается поверх старого релиза. Старый package остаётся
|
||||
установленным; выполнить clean-install runbook из `docs/migration.md`.
|
||||
|
||||
## State from another release / unmanaged configuration
|
||||
|
||||
Обнаружены сохранённые файлы 0.1.0 или конфиг вне управления 0.2.0. Остановить
|
||||
sing-box, убедиться в наличии guard, переместить старые данные в защищённый архив
|
||||
и повторить `dpkg --configure vpn-egressctl`. Не редактировать старый state так,
|
||||
чтобы обойти проверку.
|
||||
|
||||
## sing-box rejected generated configuration
|
||||
|
||||
Candidate удалён, production не менялся. Проверить package version, policy и
|
||||
логи oneshot. Секреты в отчёт не прикладывать.
|
||||
Candidate удалён, production не менялся. Проверить exact package version, URI и
|
||||
policy. Не прикладывать URI/config к отчёту без удаления secrets.
|
||||
|
||||
## Apply failed, previous configuration restored
|
||||
|
||||
Автоматический rollback успешен. Проверить:
|
||||
Same-release rollback успешен. Проверить service/journal и выполнить doctor.
|
||||
|
||||
```bash
|
||||
systemctl status sing-box --no-pager
|
||||
journalctl -u sing-box -u vpn-egress-sync.service -n 200 --no-pager
|
||||
vpn-egressctl doctor
|
||||
```
|
||||
## Automatic rollback also failed
|
||||
|
||||
## Critical rollback failed
|
||||
Если это первое clean apply, предыдущего управляемого config нет — восстановить
|
||||
нечего. sing-box должен оставаться остановленным, а guard активным. При наличии
|
||||
last-good проверить его `.meta`; не запускать файл вручную при несовпадении
|
||||
версии или SHA.
|
||||
|
||||
Не выполнять новый sync. Использовать `/var/lib/vpn-egress/last-good.json` или
|
||||
timestamped backup из консоли контейнера, затем `sing-box check` и restart.
|
||||
## Configuration metadata checksum does not match
|
||||
|
||||
Backup и metadata рассогласованы или повреждены. Автоматический rollback
|
||||
правильно заблокирован. Использовать другой version-matched backup только через
|
||||
консоль и после `sing-box check`.
|
||||
|
||||
## Endpoint exclusion
|
||||
|
||||
Если doctor сообщает, что A-запись endpoint находится в TUN exclusions, удалить
|
||||
публичный CIDR из policy и выполнить `check`, `diff`, `sync`. Не заменять его на
|
||||
новый IP.
|
||||
Удалить публичный endpoint CIDR из policy. Не заменять его новым A-адресом.
|
||||
|
||||
## Healthcheck request failed
|
||||
|
||||
Проверить DNS, handshake Hysteria2, доступность health URL и nftables. Временно
|
||||
ставить `url: null` на production нельзя без отдельного решения: это скрывает
|
||||
неработающую локальную post-activation связность. Даже успешный healthcheck не
|
||||
заменяет отдельную проверку forwarded path с workload за `eth1`.
|
||||
|
||||
## Watcher failed
|
||||
|
||||
Некорректный файл URI не меняет рабочий config. Исправить его безопасным
|
||||
`vpn-egressctl import --stdin`, затем:
|
||||
|
||||
```bash
|
||||
systemctl reset-failed vpn-egress-sync.service
|
||||
systemctl status vpn-egress-sync.path --no-pager
|
||||
```
|
||||
Проверить DNS, Gecko password, auth, SNI, certificate, UDP path и nftables.
|
||||
Успешный локальный healthcheck не заменяет forwarding E2E с `eth1`.
|
||||
|
||||
Reference in New Issue
Block a user