226 lines
14 KiB
Markdown
226 lines
14 KiB
Markdown
# wireguard-scripts
|
||
|
||
Безопасный копируемый Bash-инструмент для управления одним WireGuard-интерфейсом
|
||
на Linux. Скрипт не устанавливается в `PATH`, не запускает установщики и не
|
||
изменяет систему вне явно настроенных путей. Операции изменения выполняются под
|
||
`flock`, с резервной копией, атомарной заменой, проверкой runtime и откатом.
|
||
|
||
## Поддерживаемая среда и ограничения
|
||
|
||
Требуются Linux, Bash 4.3+, `wg`, `wg-quick`, `ip`, `flock`, стандартные
|
||
coreutils и util-linux. Минимальная заявленная архитектура — x86_64. Другие
|
||
архитектуры не считаются протестированными; `gum` и `qrencode` должны
|
||
соответствовать архитектуре самостоятельно установленного оператором бинарника.
|
||
|
||
Обязательные capabilities: `bash`, `wg`, `wg-quick`, `ip`, `flock`, `awk`,
|
||
`grep`, `sed`, `sort`, `stat`, `mktemp`, `sha256sum`, `cp`, `mv`, `chmod` и
|
||
прочие обычные coreutils, используемые системой. Необязательные: `gum` (TUI),
|
||
`qrencode` (QR), `systemctl`/`journalctl` (systemd), `ss` (UDP), `nft` и
|
||
`iptables` (наблюдение firewall). Отсутствие optional capability даёт
|
||
`SKIP`/fallback, а не скрытый вызов host-команды.
|
||
|
||
Проект можно копировать как каталог:
|
||
|
||
```text
|
||
wireguard-scripts/
|
||
├── wg-manager.sh
|
||
├── wg-manager.conf.example
|
||
├── add-user.sh
|
||
├── bin/gum # optional, если оператор его положил сюда
|
||
├── tests/
|
||
│ ├── fixtures/ # placeholder-only fixtures
|
||
│ └── mocks/bin/ # deny-by-default test commands
|
||
├── backups/ logs/ state/ # runtime paths, создаются при настройке
|
||
└── README.md
|
||
```
|
||
|
||
## Настройка
|
||
|
||
Скопируйте `wg-manager.conf.example` в `wg-manager.conf`, замените только
|
||
placeholder-значения и задайте владельца root и режим `0600`. Все пути должны
|
||
быть абсолютными, без symlink-компонентов, а каталоги client/metadata/backup/
|
||
archive/log — `0700`; конфигурации, metadata, key и log — `0600`.
|
||
|
||
| Ключ | Назначение и ограничения |
|
||
|---|---|
|
||
| `WG_INTERFACE` | имя интерфейса, например `wg0` |
|
||
| `WG_CONF` | абсолютный серверный конфиг |
|
||
| `CLIENT_DIR` | каталог клиентских `.conf/.key/.pub/.psk` |
|
||
| `SERVER_ENDPOINT` | проверяемое имя/IP сервера без порта |
|
||
| `SERVER_PORT` | UDP-порт 1..65535 |
|
||
| `SERVER_VPN_IP` | адрес сервера IPv4 |
|
||
| `CLIENT_DNS` | непустой comma-separated список IPv4/IPv6 |
|
||
| `VPN_SUBNET_PREFIX` | IPv4 prefix из трёх октетов, например `192.0.2` |
|
||
| `START_HOST` | начало пула host-частей |
|
||
| `END_HOST` | конец пула host-частей |
|
||
| `USE_IPV6` | `0` или `1` |
|
||
| `IPV6_PREFIX` | prefix, оканчивающийся `::`, если IPv6 включён |
|
||
| `DEFAULT_CLIENT_MODE` | `full-tunnel` или `split-tunnel` |
|
||
| `RECENT_HANDSHAKE_SECONDS` | неотрицательное окно active-state |
|
||
| `BACKUP_RETENTION` | bounded число automatic backups |
|
||
|
||
Окружение может изолированно переопределить расположение:
|
||
`WG_MANAGER_CONFIG`, `WG_MANAGER_BACKUP_DIR`, `WG_MANAGER_LOG_DIR`,
|
||
`WG_MANAGER_METADATA_DIR`, `WG_MANAGER_ARCHIVE_DIR`, `WG_MANAGER_LOCK_FILE`.
|
||
`WG_MANAGER_GUM` имеет приоритет над локальным `bin/gum`, затем `PATH`.
|
||
`WG_MANAGER_NOW` — только документированный deterministic clock override; перед
|
||
боевым запуском эти переменные следует убрать. Test-only hooks принимаются лишь
|
||
в confined `WG_MANAGER_TEST_MODE` под `TEST_ROOT` и не являются production API.
|
||
|
||
## Команды и коды
|
||
|
||
| Команда | Поведение, stdout и подтверждение |
|
||
|---|---|
|
||
| `help`, `version` | безопасная справка/версия, без mutation |
|
||
| `status` | disk/runtime summary, без secrets |
|
||
| `list` | tab-separated peer rows с `state`/`origin`, без secrets |
|
||
| `add NAME [--mode MODE] [--split-networks CIDRS] [--endpoint HOST]` | явный add под lock |
|
||
| `show NAME` | identity-safe details и safe path, если он доступен |
|
||
| `config NAME` | печатает только валидированный путь |
|
||
| `config NAME --show` | единственная форма полного client config |
|
||
| `qr NAME` | QR через stdin; без `qrencode` печатает путь и возвращает `69` |
|
||
| `remove NAME [--yes]` | managed peer, archive вместо purge; noninteractive требует `--yes` |
|
||
| `diagnose [--quiet]` | read-only checks; quiet даёт `id=STATUS` |
|
||
| `backup` | ручная backup текущего server config под lock |
|
||
| `backups` | только валидные backups current interface, newest-first |
|
||
| `restore BACKUP [--yes]` | CLI: без `--yes` нужен обычный интерактивный `y/N`; без terminal команда отклоняется. В TUI требуется ввод полного backup ID; сначала создаётся current-state backup |
|
||
| `log [LIMIT]` | validated audit records newest-first, `LIMIT` 1..1000 |
|
||
| `tui` или запуск без аргументов | gum TUI, требует terminal stdin и stdout |
|
||
|
||
Коды: `0` — успех или безопасная отмена; `64` — usage/flags/arity; `65` —
|
||
validation или unsafe identity/path; `69` — отсутствующая optional capability;
|
||
`70` — operation/backend failure; `71` — critical rollback/recovery failure;
|
||
`77` — недостаточные права. Ошибка audit после mutation сообщается как partial
|
||
operation и не маскирует исходный status. `FAIL` в diagnose даёт `70`,
|
||
`WARN`/`SKIP` сами по себе не дают failure.
|
||
|
||
Примеры используют только документационные адреса и имена:
|
||
|
||
```bash
|
||
./wg-manager.sh status
|
||
./wg-manager.sh list
|
||
./wg-manager.sh add alice --mode full-tunnel
|
||
./wg-manager.sh add bob --mode split-tunnel --split-networks '192.0.2.0/24,2001:db8:40::/48' --endpoint vpn.example.invalid
|
||
./wg-manager.sh show alice
|
||
./wg-manager.sh config alice
|
||
./wg-manager.sh config alice --show
|
||
./wg-manager.sh qr alice
|
||
./wg-manager.sh remove alice --yes
|
||
./wg-manager.sh diagnose --quiet
|
||
./wg-manager.sh log 25
|
||
./wg-manager.sh backup
|
||
./wg-manager.sh backups
|
||
./wg-manager.sh restore 20231114T221500Z-wg0-001 --yes
|
||
./wg-manager.sh tui
|
||
./add-user.sh alice vpn.example.invalid
|
||
```
|
||
|
||
`add-user.sh` — source-safe compatibility wrapper: он только передаёт
|
||
`add NAME [--endpoint HOST]`, не исполняет аргументы и не содержит второй
|
||
реализации backend.
|
||
|
||
## Транзакции, backups, archive и audit
|
||
|
||
Каждая mutation повторно читает ресурсы после lock, валидирует staged config,
|
||
создаёт backup, делает atomic replace, выполняет `wg syncconf`, затем сравнивает
|
||
runtime dump. При ошибке восстанавливаются disk и runtime; если восстановление
|
||
не доказано, возвращается `71` и сообщение `КРИТИЧНО`. Add публикует client
|
||
artifacts только после успешной server transaction. Remove архивирует связанные
|
||
файлы в новый restrictive каталог и не удаляет архивы автоматически. Restore
|
||
перед применением создаёт backup текущего состояния, не восстанавливает client
|
||
metadata/key files и принимает только validated backup своего интерфейса.
|
||
|
||
Audit line содержит только UTC timestamp, actor UID, safe target, outcome и
|
||
короткую error category. PrivateKey, PresharedKey, полный config, команды и
|
||
окружение туда не попадают. `log` не source-ит записи и пропускает
|
||
malformed/hostile lines. Handshake — текущее значение dump, не история.
|
||
`firewall=OK` означает только видимое ожидаемое NAT-свидетельство, не полную
|
||
проверку политики firewall.
|
||
|
||
## TUI
|
||
|
||
TUI требует пригодный `gum` и оба terminal descriptors. Main screen показывает
|
||
interface/runtime/managed/active/warning counts; Escape возвращает на предыдущий
|
||
экран, отмена mutation ничего не меняет. Клиент выбирается по стабильной
|
||
trusted identity, а не по decorated name. Config/QR требуют отдельного действия;
|
||
missing `qrencode` — безопасный fallback. Critical rollback немедленно выходит
|
||
с `71`; обычная ошибка сохраняет диагностический текст и возвращается в меню
|
||
ограниченное число раз. Backend functions не вызывают gum напрямую.
|
||
|
||
## Offline-проверка
|
||
|
||
Тесты используют только temporary `TEST_ROOT`, deny-by-default mocks и placeholder
|
||
keys. Они не читают реальный WireGuard/network/service/firewall/sysctl state:
|
||
|
||
```bash
|
||
bash -n wg-manager.sh add-user.sh tests/run.sh tests/helpers.sh
|
||
bash tests/run.sh
|
||
bash tests/run.sh m11-e2e
|
||
bash tests/run.sh secret-scan
|
||
bash tests/run.sh no-host-writes
|
||
bash tests/run.sh no-host-command-bypass
|
||
bash tests/run.sh symlink-safety
|
||
git diff --check
|
||
```
|
||
|
||
Тестовые mocks не заменяют kernel WireGuard, distribution-specific systemd,
|
||
firewall policy или архитектурный smoke-test на disposable interface.
|
||
|
||
## Troubleshooting
|
||
|
||
- `65`: проверьте root-owned `0600` config, абсолютные пути, отсутствие symlink
|
||
и соответствие интерфейса.
|
||
- `69` для TUI/QR: установите совместимый gum/qrencode самостоятельно или
|
||
используйте CLI без capability; TUI не скачивает бинарники.
|
||
- `70`: проверьте stderr, lock и доступность `wg-quick`/`wg`; исходный config
|
||
должен оставаться восстановленным.
|
||
- `71 КРИТИЧНО`: остановите mutation, сохраните backup/archive и вручную
|
||
проверьте disk/runtime перед повтором.
|
||
- `diagnose` `SKIP`: capability отсутствует или systemd не является active init.
|
||
- Для gum проверьте архитектуру, `WG_MANAGER_GUM`, локальный `bin/gum` и `PATH`.
|
||
|
||
## M11 evidence matrix
|
||
|
||
| Требование | Evidence |
|
||
|---|---|
|
||
| status / list | `status`, `list`, `m11-e2e` — машинный вывод и смешанные origins |
|
||
| add и allocation | `add-client`, `tui-add`, `m11-e2e` — полная/выборочная сеть и rollback |
|
||
| show / config export | `show-client`, `config-export`, `m11-e2e` |
|
||
| QR и fallback | `qr`, `tui-client-actions`, `secret-scan` |
|
||
| remove / archive | `remove-client`, `archive-client`, `tui-remove`, `m11-e2e` |
|
||
| diagnose | `diagnostics`, `diagnostics-exit-codes`, `m11-e2e` |
|
||
| backup / backups | `backup-list`, `runtime-file-modes`, `tui-backup` |
|
||
| restore | `restore`, `restore-rollback`, `tui-restore`, `m11-e2e` |
|
||
| audit / log | `audit`, `audit-no-secrets`, `secret-scan` |
|
||
| public-key identity | `parser`, `show-client`, `remove-client`, `tui-client-actions` |
|
||
| transaction, apply и rollback | `transaction`, `rollback`, `add-rollback`, `m11-e2e` |
|
||
| paths, symlinks и modes | `path-safety`, `symlink-safety`, `backup-path-safety`, `runtime-file-modes` |
|
||
| secret redaction | `no-secret-logs`, `secret-scan`, `tui-no-secret-args` |
|
||
| host write/command isolation | `no-host-writes`, `no-host-command-bypass` |
|
||
| exit/status semantics | `config`, `diagnostics-exit-codes`, `rollback`, CLI-focused groups |
|
||
| docs и deployment | этот README и disposable checklist; live-server deployment остаётся ручным шагом |
|
||
|
||
Это offline evidence, а не обещание, что live server уже настроен или что
|
||
deployment checklist выполнен.
|
||
|
||
## Disposable deployment checklist (не выполнен)
|
||
|
||
1. Создать отдельный non-production interface и disposable host.
|
||
2. Скопировать каталог проекта, не устанавливая его в `PATH`.
|
||
3. Проверить Bash, WireGuard tools, coreutils/util-linux и архитектуру gum.
|
||
4. Скопировать example config и заменить только placeholder values.
|
||
5. Проверить owner root, config `0600` и каталоги `0700`.
|
||
6. Выполнить `help`, `version`, `status` и `list`.
|
||
7. Выполнить `diagnose` и сохранить безопасный отчёт.
|
||
8. Добавить disposable client и проверить modes/metadata/audit.
|
||
9. Проверить `show`, safe `config` path и QR fallback.
|
||
10. Удалить client с явным подтверждением и проверить archive.
|
||
11. Создать backup и проверить newest-first listing.
|
||
12. Восстановить backup с typed confirmation на disposable config.
|
||
13. Проверить lock contention и rollback на тестовом runtime.
|
||
14. Сверить SSH/noninteractive, firewall visibility и systemd limitations.
|
||
15. Только после human review решить, допустим ли production rollout.
|
||
|
||
Этот список намеренно не выполняется агентом и не является разрешением менять
|
||
реальный host state без отдельной проверки оператором.
|