diff --git a/PLANS.md b/PLANS.md index 4d94506..2f3855e 100644 --- a/PLANS.md +++ b/PLANS.md @@ -258,20 +258,20 @@ These decisions are fixed for the MVP unless the user explicitly changes them: Allowed status values: `NOT STARTED`, `IN PROGRESS`, `BLOCKED`, `DONE`. -| Milestone | Status | Outcome | -| --------- | ----------- | ---------------------------------------------------- | -| M0 | DONE | Test harness and safe project skeleton | -| M1 | DONE | Runtime foundation, CLI shell, and config loading | -| M2 | DONE | WireGuard parser, client model, `status`, and `list` | -| M3 | DONE | Locking, backups, atomic apply, and rollback engine | -| M4 | DONE | Safe client creation | -| M5 | DONE | Client detail, config export, and QR display | -| M6 | DONE | Safe client removal and archival | -| M7 | DONE | Diagnostics engine and report | -| M8 | DONE | Backup listing/restoration and audit log UX | -| M9 | DONE | TUI foundation, dashboard, and read-only screens | -| M10 | DONE | Complete TUI mutation workflows | -| M11 | DONE | Hardening, documentation, and MVP acceptance | +| Milestone | Status | Outcome | +| --------- | ------ | ---------------------------------------------------- | +| M0 | DONE | Test harness and safe project skeleton | +| M1 | DONE | Runtime foundation, CLI shell, and config loading | +| M2 | DONE | WireGuard parser, client model, `status`, and `list` | +| M3 | DONE | Locking, backups, atomic apply, and rollback engine | +| M4 | DONE | Safe client creation | +| M5 | DONE | Client detail, config export, and QR display | +| M6 | DONE | Safe client removal and archival | +| M7 | DONE | Diagnostics engine and report | +| M8 | DONE | Backup listing/restoration and audit log UX | +| M9 | DONE | TUI foundation, dashboard, and read-only screens | +| M10 | DONE | Complete TUI mutation workflows | +| M11 | DONE | Hardening, documentation, and MVP acceptance | --- diff --git a/tests/fixtures/m2/runtime.dump b/tests/fixtures/m2/runtime.dump index 9b9809d..70e9d88 100644 --- a/tests/fixtures/m2/runtime.dump +++ b/tests/fixtures/m2/runtime.dump @@ -1,2 +1,2 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= 51820 0 -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB= (none) vpn.example.invalid:51820 192.0.2.2/32 1700000100 100 200 25 +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB= (none) vpn.example.invalid:51820 192.0.2.2/32 1700000100 100 200 off diff --git a/tests/run.sh b/tests/run.sh index 3ab52df..c8e6cff 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -1441,12 +1441,23 @@ run_m2_tests() { assert_equal $'wg show wg0 dump\n' "$runtime_log" 'точный argv wg' assert_equal '' "$(printf '%s' "$runtime_log" | grep -E 'systemctl|ip|gum' || true)" 'runtime без лишних команд' begin_test 'runtime unavailable tokens normalize safely' - parse_runtime_dump $'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\tAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\t51820\t0\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\t(none)\t(none)\t(none)\t0\t0\t0\t0' + parse_runtime_dump $'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\tAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\t51820\toff\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\t(none)\t(none)\t(none)\t0\t0\t0\toff' assert_equal - "${RUNTIME_ENDPOINT[BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=]}" 'endpoint none' assert_equal - "${RUNTIME_ALLOWED[BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=]}" 'allowed none' - parse_runtime_dump $'(none)\t(none)\t51820\t0' + parse_runtime_dump $'(none)\t(none)\t51820\toff' assert_equal 51820 "$RUNTIME_LISTEN_PORT" 'interface none tokens' set +e + output=$(parse_runtime_dump $'(none)\t(none)\t51820\toff\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\t(none)\t(none)\t(none)\t0\t0\t0\tdisabled' 2>&1) + status=$? + set -e + assert_equal "$EXIT_VALIDATION" "$status" 'runtime keepalive accepts off but rejects unknown tokens' + assert_contains 'Некорректные числа' "$output" 'runtime keepalive diagnostic' + set +e + output=$(parse_runtime_dump $'(none)\t(none)\t51820\toff\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\t(none)\t(none)\t(none)\t0\t0\t0\t65536' 2>&1) + status=$? + set -e + assert_equal "$EXIT_VALIDATION" "$status" 'runtime keepalive rejects values above WireGuard range' + set +e output=$(parse_runtime_dump $'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\tAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\t51820\t0\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\t(none)\tvpn.example.invalid\t192.0.2.2/32\t0\t0\t0\t0' 2>&1) status=$? set -e diff --git a/wg-manager.sh b/wg-manager.sh index 00d3e91..6ec0a9f 100755 --- a/wg-manager.sh +++ b/wg-manager.sh @@ -504,6 +504,12 @@ parse_metadata() { for file in "$METADATA_DIR"/*; do [[ -e "$file" ]] || continue; parse_metadata_file "$file" || return $?; done } +validate_dump_keepalive() { + local value=$1 + [[ $value == off ]] && return 0 + is_uint "$value" && (( 10#$value <= 65535 )) +} + parse_runtime_dump() { local line fields_count line_number=0 key endpoint allowed handshake rx tx keepalive local output=$1 @@ -525,7 +531,7 @@ parse_runtime_dump() { [[ $endpoint == '-' || -z $endpoint ]] || validate_peer_endpoint_value "$endpoint" || { die "$EXIT_VALIDATION" 'Некорректный Endpoint в wg dump'; return $?; } [[ -z ${RUNTIME_HANDSHAKE[$key]+x} ]] || { die "$EXIT_VALIDATION" 'Дублирующийся PublicKey в wg dump'; return $?; } if [[ $allowed != '-' ]]; then normalize_allowed_ips "$allowed" >/dev/null || { die "$EXIT_VALIDATION" 'Некорректные AllowedIPs в wg dump'; return $?; }; fi - is_uint "$handshake" && is_uint "$rx" && is_uint "$tx" && is_uint "$keepalive" || { die "$EXIT_VALIDATION" 'Некорректные числа в wg dump'; return $?; } + is_uint "$handshake" && is_uint "$rx" && is_uint "$tx" && validate_dump_keepalive "$keepalive" || { die "$EXIT_VALIDATION" 'Некорректные числа в wg dump'; return $?; } RUNTIME_ENDPOINT["$key"]=${endpoint:--}; if [[ $allowed == '-' ]]; then RUNTIME_ALLOWED["$key"]='-'; else RUNTIME_ALLOWED["$key"]=$(normalize_allowed_ips "$allowed"); fi; RUNTIME_HANDSHAKE["$key"]=$handshake; RUNTIME_RX["$key"]=$rx; RUNTIME_TX["$key"]=$tx done <<<"$output" }