feat(diagnostics): add read-only WireGuard health checks
This commit is contained in:
+314
-1
@@ -11,7 +11,7 @@ source "${PROJECT_DIR}/wg-manager.sh"
|
||||
|
||||
case $# in
|
||||
0) ;;
|
||||
1) case $1 in config|parser|status|list|list-redirected|list-deterministic|transaction|rollback|locking|backups|mutation-preserves-source|secret-modes|add-client|address-allocation|add-collisions|add-rollback|no-secret-logs|add-golden-config|add-invalid-names|show-client|config-export|qr|path-safety|no-secret-default-output|remove-client|remove-cancel|remove-exact-block|remove-rollback|archive-client|archive-path-safety|remove-preserves-neighbors) ;; *) printf 'Ошибка: неизвестная группа тестов или лишние аргументы\n' >&2; exit 64 ;; esac ;;
|
||||
1) case $1 in config|parser|status|list|list-redirected|list-deterministic|transaction|rollback|locking|backups|mutation-preserves-source|secret-modes|add-client|address-allocation|add-collisions|add-rollback|no-secret-logs|add-golden-config|add-invalid-names|show-client|config-export|qr|path-safety|no-secret-default-output|remove-client|remove-cancel|remove-exact-block|remove-rollback|archive-client|archive-path-safety|remove-preserves-neighbors|diagnostics|diagnostics-systemd|diagnostics-firewall|diagnostics-forwarding|diagnostics-runtime-diff|diagnostics-read-only|diagnostics-exit-codes) ;; *) printf 'Ошибка: неизвестная группа тестов или лишние аргументы\n' >&2; exit 64 ;; esac ;;
|
||||
*) printf 'Ошибка: неизвестная группа тестов или лишние аргументы\n' >&2; exit 64 ;;
|
||||
esac
|
||||
test_group=${1-}
|
||||
@@ -2788,6 +2788,312 @@ EOF
|
||||
fi
|
||||
}
|
||||
|
||||
run_m7_tests() {
|
||||
local config_file=$TEST_ROOT/diag.conf fixture=$TEST_ROOT/diag-wg0.conf output status saved_test_root
|
||||
rm -rf -- "$TEST_ROOT/diag-meta" "$TEST_ROOT/diag-clients" "$TEST_ROOT/diag-backups" "$TEST_ROOT/diag-logs" "$TEST_ROOT/diag-archive" "$TEST_ROOT/diag.lock" "$TEST_ROOT/ipv4-forward" "$TEST_ROOT/runtime-diff.dump"
|
||||
: >"$TEST_MOCK_LOG"
|
||||
unset SYSTEMCTL_STATE DIAG_DISABLE_COMMANDS DIAG_IP_LINK_FAIL DIAG_IP_ADDR_FAIL DIAG_IP_ROUTE_FAIL DIAG_SS_FAIL DIAG_NFT_FAIL DIAG_IPTABLES_FAIL DIAG_JOURNAL_FAIL DIAG_IP_LINK_OUTPUT DIAG_IP_ADDR_OUTPUT DIAG_IP_ROUTE_OUTPUT DIAG_SS_OUTPUT DIAG_NFT_OUTPUT DIAG_IPTABLES_OUTPUT DIAG_JOURNAL_OUTPUT WG_MOCK_DUMP_SEQUENCE_DIR
|
||||
cp -- "$PROJECT_DIR/tests/fixtures/m3/source.conf" "$fixture"; chmod 600 -- "$fixture"
|
||||
sed -i 's/wg-manager-client: fixture/wg-manager-client: alice/' "$fixture"
|
||||
write_m2_config "$config_file" "$fixture"
|
||||
replace_config_line "$config_file" 'CLIENT_DIR=' "CLIENT_DIR=\"$TEST_ROOT/diag-clients\""
|
||||
export WG_MANAGER_TEST_MODE=1 SYSTEMCTL_INIT=running WG_MANAGER_CONFIG=$config_file WG_MANAGER_BACKUP_DIR=$TEST_ROOT/diag-backups WG_MANAGER_LOG_DIR=$TEST_ROOT/diag-logs WG_MANAGER_METADATA_DIR=$TEST_ROOT/diag-meta WG_MANAGER_ARCHIVE_DIR=$TEST_ROOT/diag-archive WG_MANAGER_LOCK_FILE=$TEST_ROOT/diag.lock
|
||||
set_runtime_paths; load_config
|
||||
mkdir -p -- "$TEST_ROOT/diag-meta" "$TEST_ROOT/diag-clients"; chmod 700 -- "$TEST_ROOT/diag-meta" "$TEST_ROOT/diag-clients"
|
||||
printf '0\n' >"$TEST_ROOT/ipv4-forward"
|
||||
export WG_MANAGER_IPV4_FORWARD_FILE=$TEST_ROOT/ipv4-forward WG_MOCK_INTERFACE=wg0 WG_MOCK_DUMP_FILE=$PROJECT_DIR/tests/fixtures/m3/runtime.dump
|
||||
begin_test "diagnostics $test_group"
|
||||
set +e; output=$(diagnose 2>&1); status=$?; set -e
|
||||
assert_failure "$status" 'критическая диагностика возвращает ошибку'
|
||||
assert_contains '[FAIL]' "$output" 'diagnostics FAIL status'
|
||||
assert_contains '[SKIP]' "$output" 'diagnostics SKIP status'
|
||||
assert_contains '[WARN]' "$output" 'diagnostics WARN status'
|
||||
case $test_group in
|
||||
diagnostics)
|
||||
export DIAG_IP_LINK_OUTPUT='4: wg0: <UP> mtu 1420' DIAG_IP_ADDR_OUTPUT='4: wg0 inet 192.0.2.1/24 scope global wg0' DIAG_IP_ROUTE_OUTPUT='192.0.2.0/24 dev wg0 scope link' DIAG_SS_OUTPUT='udp UNCONN 0 0 0.0.0.0:51820 0.0.0.0:*'
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'interface-exists=OK' "$output" 'interface existence uses exact mock argv'
|
||||
assert_contains 'interface-address=OK' "$output" 'exact IPv4 address is accepted'
|
||||
assert_contains 'routes=OK' "$output" 'exact route and device are accepted'
|
||||
export DIAG_IP_ADDR_OUTPUT=$'4: wg0 inet 198.51.100.1/24 scope global wg0\n4: wg0 inet 192.0.2.1/24 scope global wg0'
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'interface-address=OK' "$output" 'IPv4 address is found on a later ip output line'
|
||||
export DIAG_IP_ADDR_OUTPUT='4: wg0 inet 192x0x2x1/24 scope global wg0' DIAG_IP_ROUTE_OUTPUT='192.0.20.0/24 dev wg0 scope link'
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'interface-address=WARN' "$output" 'lookalike IPv4 address is rejected'
|
||||
assert_contains 'routes=WARN' "$output" 'lookalike IPv4 route is rejected'
|
||||
export DIAG_IP_LINK_FAIL=1
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'interface-exists=FAIL' "$output" 'missing interface is FAIL'
|
||||
assert_contains 'interface-runtime=OK' "$output" 'interface failure does not stop runtime check'
|
||||
unset DIAG_IP_LINK_FAIL
|
||||
export DIAG_DISABLE_COMMANDS=ip
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'command-ip=FAIL' "$output" 'missing required ip is FAIL'
|
||||
assert_contains 'interface-address=SKIP' "$output" 'missing ip makes address check SKIP'
|
||||
unset DIAG_DISABLE_COMMANDS
|
||||
export DIAG_SS_OUTPUT='udp UNCONN 0 0 [::]:151820 [::]:*'
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'udp-listener=WARN' "$output" 'false-positive UDP port is rejected'
|
||||
export DIAG_SS_OUTPUT='udp UNCONN 0 0 [::]:51820 [::]:*'
|
||||
: >"$TEST_MOCK_LOG"
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'udp-listener=OK' "$output" 'IPv6 wildcard UDP port is accepted'
|
||||
assert_equal 1 "$(grep -c '^ss -lun$' "$TEST_MOCK_LOG")" 'UDP probe uses one exact ss call'
|
||||
export DIAG_DISABLE_COMMANDS=ss
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'udp-listener=SKIP' "$output" 'missing ss is SKIP'
|
||||
unset DIAG_DISABLE_COMMANDS
|
||||
export DIAG_SS_FAIL=1 DIAG_JOURNAL_OUTPUT='Error: FAILED to activate peer'
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'udp-listener=WARN' "$output" 'UDP command failure is WARN'
|
||||
assert_contains 'journal=WARN' "$output" 'journal errors are case-insensitive WARN'
|
||||
unset DIAG_IP_LINK_OUTPUT DIAG_IP_ADDR_OUTPUT DIAG_IP_ROUTE_OUTPUT DIAG_SS_OUTPUT DIAG_SS_FAIL DIAG_JOURNAL_OUTPUT
|
||||
;;
|
||||
diagnostics-systemd)
|
||||
assert_contains 'systemctl is-system-running' "$(<"$TEST_MOCK_LOG")" 'systemd capability probe argv'
|
||||
assert_contains 'systemctl is-active -- wg-quick@wg0' "$(<"$TEST_MOCK_LOG")" 'systemd exact read-only argv'
|
||||
assert_contains 'systemd [WARN]' "$output" 'systemd uncertain state is WARN'
|
||||
export SYSTEMCTL_STATE=active
|
||||
set +e; output=$(diagnose 2>&1); status=$?; set -e
|
||||
assert_contains 'systemd [OK]' "$output" 'active systemd is OK'
|
||||
export SYSTEMCTL_STATE=failed
|
||||
set +e; output=$(diagnose 2>&1); status=$?; set -e
|
||||
assert_contains 'systemd [FAIL]' "$output" 'failed systemd is FAIL'
|
||||
export SYSTEMCTL_INIT=degraded SYSTEMCTL_STATE=active
|
||||
set +e; output=$(diagnose 2>&1); status=$?; set -e
|
||||
assert_contains 'systemd [OK]' "$output" 'degraded systemd still checks service state'
|
||||
export SYSTEMCTL_INIT=offline
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'systemd=SKIP' "$output" 'unsupported systemd is SKIP'
|
||||
assert_contains 'journal=SKIP' "$output" 'journal is SKIP without systemd'
|
||||
export SYSTEMCTL_INIT=running
|
||||
unset SYSTEMCTL_STATE
|
||||
;;
|
||||
diagnostics-firewall)
|
||||
assert_contains 'nft list ruleset' "$(<"$TEST_MOCK_LOG")" 'firewall exact read-only query'
|
||||
assert_contains 'firewall [WARN]' "$output" 'firewall uncertainty is WARN'
|
||||
assert_equal '' "$(grep -E ' (add|delete|insert|replace|flush| -w ) ' "$TEST_MOCK_LOG" || true)" 'firewall mock log has no mutation argv'
|
||||
export DIAG_NFT_OUTPUT='oifname "wg0" ip saddr 192.0.2.0/24 masquerade'
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'firewall=OK' "$output" 'nft NAT evidence is OK'
|
||||
export DIAG_NFT_FAIL=0 DIAG_NFT_OUTPUT=$'oifname "wg0" ip saddr 192.0.20.0/24 masquerade\nip saddr 192.0.2.0/24 accept'
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'firewall=WARN' "$output" 'separated unrelated firewall tokens are WARN'
|
||||
export DIAG_NFT_OUTPUT='oifname "wg0" ip saddr 192.0.2/24' DIAG_NFT_FAIL=1 DIAG_IPTABLES_OUTPUT='-A POSTROUTING -s 192.0.2.0/24 -o wg0 -j MASQUERADE'
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'firewall=OK' "$output" 'iptables fallback NAT evidence is OK'
|
||||
export DIAG_DISABLE_COMMANDS=nft,iptables
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'firewall=SKIP' "$output" 'both firewall tools absent are SKIP'
|
||||
unset DIAG_NFT_OUTPUT DIAG_NFT_FAIL DIAG_IPTABLES_OUTPUT DIAG_DISABLE_COMMANDS
|
||||
;;
|
||||
diagnostics-forwarding)
|
||||
printf '1\n' >"$TEST_ROOT/ipv4-forward"
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'forwarding-ipv4=OK' "$output" 'IPv4 forwarding 1 is OK'
|
||||
assert_contains 'forwarding-ipv6=SKIP' "$output" 'disabled IPv6 forwarding is SKIP'
|
||||
printf 'malformed\n' >"$TEST_ROOT/ipv4-forward"
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'forwarding-ipv4=FAIL' "$output" 'malformed forwarding fails'
|
||||
cp -- "$config_file" "$TEST_ROOT/ipv6-diag.conf"
|
||||
replace_config_line "$TEST_ROOT/ipv6-diag.conf" 'USE_IPV6=' 'USE_IPV6="1"'
|
||||
printf '1\n' >"$TEST_ROOT/ipv6-forward"
|
||||
export WG_MANAGER_CONFIG=$TEST_ROOT/ipv6-diag.conf WG_MANAGER_IPV6_FORWARD_FILE=$TEST_ROOT/ipv6-forward DIAG_IP_ADDR_OUTPUT='4: wg0 inet 192.0.2.1/24 scope global wg0 6: wg0 inet6 2001:db8:ffff::1/64 scope global' DIAG_IP_ROUTE_OUTPUT='192.0.2.0/24 dev wg0 scope link 2001:db8:ffff::/64 dev wg0 scope link'
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'forwarding-ipv6=OK' "$output" 'IPv6 forwarding 1 is OK'
|
||||
assert_contains 'interface-address-ipv6=OK' "$output" 'exact IPv6 address is accepted'
|
||||
assert_contains 'routes-ipv6=OK' "$output" 'exact IPv6 route is accepted'
|
||||
export DIAG_IP_ADDR_OUTPUT=$'4: wg0 inet 192.0.2.1/24 scope global wg0\n6: wg0 inet6 2001:db8:ffff::1/64 scope global'
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'interface-address-ipv6=OK' "$output" 'IPv6 address is found on a later ip output line'
|
||||
printf '0\n' >"$TEST_ROOT/ipv6-forward"
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'forwarding-ipv6=FAIL' "$output" 'disabled IPv6 forwarding is FAIL when enabled'
|
||||
printf '1\n' >"$TEST_ROOT/ipv6-forward"
|
||||
export DIAG_IP_ADDR_OUTPUT='4: wg0 inet 192.0.2.1/24 scope global wg0 6: wg0 inet6 2001:db8:ffff::10/64 scope global' DIAG_IP_ROUTE_OUTPUT='192.0.2.0/24 dev other scope link 2001:db8:ffff::/64 dev other scope link'
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'interface-address-ipv6=WARN' "$output" 'wrong IPv6 address is WARN'
|
||||
assert_contains 'routes-ipv6=WARN' "$output" 'wrong IPv6 route device is WARN'
|
||||
unset WG_MANAGER_IPV6_FORWARD_FILE DIAG_IP_ADDR_OUTPUT DIAG_IP_ROUTE_OUTPUT
|
||||
;;
|
||||
diagnostics-runtime-diff)
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_equal 1 "$(grep -c '^interface-exists=' <<<"$output")" 'interface existence has one record'
|
||||
assert_equal 1 "$(grep -c '^interface-runtime=' <<<"$output")" 'runtime dump has one record'
|
||||
assert_equal 1 "$(grep -c '^runtime-disk=' <<<"$output")" 'runtime-disk has one record'
|
||||
assert_contains 'runtime-disk=' "$output" 'runtime-disk is independently reported'
|
||||
for duplicate_fixture in duplicate-public.conf duplicate-allowed.conf overlap.conf overlap-v6.conf; do
|
||||
cp -- "$PROJECT_DIR/tests/fixtures/m2/$duplicate_fixture" "$fixture"
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'config-parse=FAIL' "$output" "${duplicate_fixture} parser failure is reported"
|
||||
assert_contains 'interface-exists=OK' "$output" "${duplicate_fixture} does not stop interface checks"
|
||||
assert_contains 'interface-runtime=OK' "$output" "${duplicate_fixture} does not stop runtime checks"
|
||||
case $duplicate_fixture in
|
||||
duplicate-public.conf) assert_contains 'duplicate-keys=FAIL' "$output" 'duplicate public key has a distinct result' ;;
|
||||
*) assert_contains 'allowedips-overlap=FAIL' "$output" "${duplicate_fixture} overlap has a distinct result" ;;
|
||||
esac
|
||||
done
|
||||
cp -- "$PROJECT_DIR/tests/fixtures/m3/runtime.dump" "$TEST_ROOT/runtime-diff.dump"
|
||||
sed -i 's/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=/' "$TEST_ROOT/runtime-diff.dump"
|
||||
export WG_MOCK_DUMP_FILE="$TEST_ROOT/runtime-diff.dump"
|
||||
cp -- "$PROJECT_DIR/tests/fixtures/m3/source.conf" "$fixture"
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'runtime-disk=WARN' "$output" 'runtime and disk peer mismatch is WARN'
|
||||
cp -- "$PROJECT_DIR/tests/fixtures/m3/runtime.dump" "$TEST_ROOT/runtime-same-key.dump"
|
||||
sed -i 's/192.0.2.2\/32/192.0.2.99\/32/' "$TEST_ROOT/runtime-same-key.dump"
|
||||
export WG_MOCK_DUMP_FILE="$TEST_ROOT/runtime-same-key.dump"
|
||||
same_key_ok=$(awk -F '\t' 'NR == 2 && $1 == "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" { print "yes" }' "$TEST_ROOT/runtime-same-key.dump")
|
||||
assert_equal yes "$same_key_ok" 'same-key fixture retains the original public key'
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'runtime-disk=WARN' "$output" 'same key with changed AllowedIPs is WARN'
|
||||
export WG_MOCK_DUMP_FAIL_ON_CALL=1 WG_MOCK_DUMP_FAIL_COUNTER_FILE="$TEST_ROOT/runtime-fail-counter"
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'interface-runtime=FAIL' "$output" 'nonzero runtime dump is FAIL'
|
||||
assert_contains 'runtime-disk=SKIP' "$output" 'runtime-disk skips unavailable runtime'
|
||||
unset WG_MOCK_DUMP_FAIL_ON_CALL WG_MOCK_DUMP_FAIL_COUNTER_FILE
|
||||
printf 'malformed runtime line\n' >"$TEST_ROOT/malformed-runtime.dump"; export WG_MOCK_DUMP_FILE="$TEST_ROOT/malformed-runtime.dump"
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'interface-runtime=FAIL' "$output" 'malformed runtime dump is FAIL'
|
||||
assert_contains 'runtime-disk=SKIP' "$output" 'malformed runtime skips comparison'
|
||||
export WG_MOCK_DUMP_FILE="$PROJECT_DIR/tests/fixtures/m3/runtime.dump"
|
||||
;;
|
||||
diagnostics-read-only)
|
||||
local diag_before diag_mode config_before forward_before keys_before invalid_result
|
||||
diag_before=$(sha256sum -- "$fixture"); diag_mode=$(stat -c '%a' -- "$fixture"); config_before=$(sha256sum -- "$config_file"); forward_before=$(sha256sum -- "$TEST_ROOT/ipv4-forward")
|
||||
assert_equal '' "$(grep -E 'syncconf|wg-quick (up|down|save)|ip (add|del|replace)|systemctl (start|stop|restart)|nft (add|delete|insert|flush)|iptables (-A|-D|-I|-F)|sysctl -w' "$TEST_MOCK_LOG" || true)" 'diagnostics has no mutation commands'
|
||||
assert_equal "$diag_before" "$(sha256sum -- "$fixture")" 'diagnostics does not modify config'
|
||||
assert_equal "$diag_mode" "$(stat -c '%a' -- "$fixture")" 'diagnostics preserves config mode'
|
||||
set +e; local quiet_output; quiet_output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_equal "$(printf '%s\n' "$quiet_output" | sed '/^$/d' | wc -l)" "$(printf '%s\n' "$quiet_output" | sed '/^$/d' | cut -d= -f1 | sort -u | wc -l)" 'quiet output has unique diagnostic ids'
|
||||
assert_equal 0 "$(grep -Ev '^[A-Za-z0-9_-]+=(OK|WARN|FAIL|SKIP)$' <<<"$quiet_output" | wc -l)" 'quiet output statuses are constrained'
|
||||
DIAG_IDS=(); DIAG_FAILS=0; DIAG_QUIET=1
|
||||
set +e; diag_result 'bad id' OK 'ignored' >/dev/null 2>&1; status=$?; set -e
|
||||
assert_failure "$status" 'invalid diagnostic id is rejected'
|
||||
set +e; diag_result valid-id BOGUS 'ignored' >/dev/null 2>&1; status=$?; set -e
|
||||
assert_failure "$status" 'invalid diagnostic status is rejected'
|
||||
assert_equal 0 "$(grep -c '^bad id=' <<<"$quiet_output")" 'invalid diagnostic id emits no record'
|
||||
diag_result valid-id OK 'first' >/dev/null
|
||||
set +e; diag_result valid-id OK 'duplicate' >/dev/null 2>&1; status=$?; set -e
|
||||
assert_failure "$status" 'duplicate diagnostic id is rejected'
|
||||
assert_equal 3 "$DIAG_FAILS" 'diagnostic engine counts invalid and duplicate records'
|
||||
export DIAG_DISABLE_COMMANDS=ss,nft,iptables,systemctl,journalctl
|
||||
set +e; quiet_output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'udp-listener=SKIP' "$quiet_output" 'missing optional ss is SKIP'
|
||||
assert_contains 'firewall=SKIP' "$quiet_output" 'missing optional firewall tools are SKIP'
|
||||
assert_contains 'systemd=SKIP' "$quiet_output" 'missing systemctl is SKIP'
|
||||
assert_contains 'journal=SKIP' "$quiet_output" 'missing journal is SKIP'
|
||||
export DIAG_DISABLE_COMMANDS=wg
|
||||
saved_test_root=$TEST_ROOT; TEST_ROOT=''
|
||||
set +e; diag_command_available wg; status=$?; set -e
|
||||
assert_equal 0 "$status" 'diagnostic disable override is ignored outside confined root'
|
||||
TEST_ROOT=$saved_test_root
|
||||
set +e; quiet_output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'command-wg=FAIL' "$quiet_output" 'missing required wg is FAIL'
|
||||
unset DIAG_DISABLE_COMMANDS
|
||||
assert_equal "$config_before" "$(sha256sum -- "$config_file")" 'manager config remains unchanged'
|
||||
assert_equal "$forward_before" "$(sha256sum -- "$TEST_ROOT/ipv4-forward")" 'forwarding fixture remains unchanged'
|
||||
printf 'KEY-PLACEHOLDER\n' >"$TEST_ROOT/diag-clients/alice.key"; chmod 600 -- "$TEST_ROOT/diag-clients/alice.key"
|
||||
printf 'PSK-PLACEHOLDER\n' >"$TEST_ROOT/diag-clients/alice.psk"; chmod 600 -- "$TEST_ROOT/diag-clients/alice.psk"
|
||||
keys_before=$(sha256sum -- "$TEST_ROOT/diag-clients/alice.key" "$TEST_ROOT/diag-clients/alice.psk")
|
||||
set +e; quiet_output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'key-permissions=OK' "$quiet_output" 'secure key artifacts are accepted'
|
||||
chmod 644 -- "$TEST_ROOT/diag-clients/alice.key"
|
||||
set +e; quiet_output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'key-permissions=FAIL' "$quiet_output" 'permissive key artifact is rejected'
|
||||
chmod 600 -- "$TEST_ROOT/diag-clients/alice.key"
|
||||
mv -- "$TEST_ROOT/diag-clients/alice.psk" "$TEST_ROOT/diag-clients/alice.psk.saved"
|
||||
ln -s -- "$TEST_ROOT/diag-clients/alice.psk.saved" "$TEST_ROOT/diag-clients/alice.psk"
|
||||
set +e; quiet_output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'key-permissions=FAIL' "$quiet_output" 'symlink key artifact is rejected'
|
||||
rm -f -- "$TEST_ROOT/diag-clients/alice.psk"; mv -- "$TEST_ROOT/diag-clients/alice.psk.saved" "$TEST_ROOT/diag-clients/alice.psk"
|
||||
ln -- "$TEST_ROOT/diag-clients/alice.key" "$TEST_ROOT/key-hardlink"
|
||||
set +e; quiet_output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'key-permissions=FAIL' "$quiet_output" 'hardlinked key artifact is rejected'
|
||||
rm -f -- "$TEST_ROOT/key-hardlink"
|
||||
chmod 644 -- "$config_file"; set +e; quiet_output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'config-permissions=FAIL' "$quiet_output" 'permissive manager config is reported'
|
||||
chmod 600 -- "$config_file"
|
||||
ln -- "$config_file" "$TEST_ROOT/config-hardlink"
|
||||
set +e; quiet_output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'config-permissions=FAIL' "$quiet_output" 'hardlinked manager config is rejected'
|
||||
rm -f -- "$TEST_ROOT/config-hardlink"
|
||||
mv -- "$config_file" "$TEST_ROOT/manager-real.conf"; ln -s -- "$TEST_ROOT/manager-real.conf" "$config_file"
|
||||
set +e; quiet_output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'config-readable=FAIL' "$quiet_output" 'symlinked manager config is rejected'
|
||||
assert_contains 'config-parse=FAIL' "$quiet_output" 'manager symlink does not get loaded'
|
||||
rm -f -- "$config_file"; mv -- "$TEST_ROOT/manager-real.conf" "$config_file"
|
||||
mv -- "$fixture" "$TEST_ROOT/wg-real.conf"; ln -s -- "$TEST_ROOT/wg-real.conf" "$fixture"
|
||||
set +e; quiet_output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'config-permissions=FAIL' "$quiet_output" 'symlinked server config is rejected'
|
||||
assert_contains 'config-parse=FAIL' "$quiet_output" 'server symlink target is not parsed'
|
||||
rm -f -- "$fixture"; mv -- "$TEST_ROOT/wg-real.conf" "$fixture"
|
||||
chmod 750 -- "$TEST_ROOT/diag-clients"
|
||||
set +e; quiet_output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'key-permissions=FAIL' "$quiet_output" 'group-accessible client directory is rejected'
|
||||
chmod 700 -- "$TEST_ROOT/diag-clients"
|
||||
mkdir -p -- "$TEST_ROOT/diag-outside"; printf 'outside-sentinel\n' >"$TEST_ROOT/diag-outside/sentinel"
|
||||
mv -- "$TEST_ROOT/diag-clients" "$TEST_ROOT/diag-clients-real"; ln -s -- "$TEST_ROOT/diag-outside" "$TEST_ROOT/diag-clients"
|
||||
set +e; quiet_output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_contains 'key-permissions=FAIL' "$quiet_output" 'symlinked client directory is rejected'
|
||||
assert_equal 'outside-sentinel' "$(<"$TEST_ROOT/diag-outside/sentinel")" 'symlink target remains unread and unchanged'
|
||||
rm -f -- "$TEST_ROOT/diag-clients"; mv -- "$TEST_ROOT/diag-clients-real" "$TEST_ROOT/diag-clients"
|
||||
assert_equal "$keys_before" "$(sha256sum -- "$TEST_ROOT/diag-clients/alice.key" "$TEST_ROOT/diag-clients/alice.psk")" 'key artifacts remain unchanged'
|
||||
invalid_result=$(grep -Ev '^(wg show [^ ]+ dump|ip link show dev [^ ]+|ip -o addr show dev [^ ]+|ip -o route show [0-9.]+/24 dev [^ ]+|ip -o route show [0-9A-Fa-f:]+/64 dev [^ ]+|systemctl is-system-running|systemctl is-active -- wg-quick@[^ ]+|ss -lun|nft list ruleset|iptables -t nat -S|journalctl -u wg-quick@[^ ]+ --since 24\\ hours\\ ago --no-pager)$' "$TEST_MOCK_LOG" || true)
|
||||
assert_equal '' "$invalid_result" 'diagnostic log contains only read-only allowlisted argv'
|
||||
assert_equal "$diag_before" "$(sha256sum -- "$fixture")" 'final server config snapshot is unchanged'
|
||||
assert_equal "$diag_mode" "$(stat -c '%a' -- "$fixture")" 'final server config mode is unchanged'
|
||||
assert_equal "$config_before" "$(sha256sum -- "$config_file")" 'final manager config snapshot is unchanged'
|
||||
assert_equal 600 "$(stat -c '%a' -- "$config_file")" 'final manager config mode is unchanged'
|
||||
assert_equal "$forward_before" "$(sha256sum -- "$TEST_ROOT/ipv4-forward")" 'final forwarding snapshot is unchanged'
|
||||
;;
|
||||
diagnostics-exit-codes)
|
||||
set +e; diagnose --bad >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal "$EXIT_USAGE" "$status" 'unknown diagnose flag is usage'
|
||||
set +e; diagnose extra >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal "$EXIT_USAGE" "$status" 'extra diagnose argument is usage'
|
||||
set +e; output=$(bash "$PROJECT_DIR/wg-manager.sh" diagnose --bad 2>&1); status=$?; set -e
|
||||
assert_equal "$EXIT_USAGE" "$status" 'CLI unknown diagnose flag is usage'
|
||||
set +e; output=$(bash "$PROJECT_DIR/wg-manager.sh" diagnose extra 2>&1); status=$?; set -e
|
||||
assert_equal "$EXIT_USAGE" "$status" 'CLI extra diagnose argument is usage'
|
||||
printf '1\n' >"$TEST_ROOT/ipv4-forward"
|
||||
export DIAG_IP_LINK_OUTPUT='4: wg0: <POINTOPOINT> mtu 1420' DIAG_IP_ADDR_OUTPUT='4: wg0 inet 192.0.2.1/24 scope global wg0' DIAG_IP_ROUTE_OUTPUT='192.0.2.0/24 dev wg0 scope link' DIAG_SS_OUTPUT='udp UNCONN 0 0 0.0.0.0:51820 0.0.0.0:*' DIAG_NFT_OUTPUT='oifname "wg0" ip saddr 192.0.2.0/24 masquerade' SYSTEMCTL_STATE=active
|
||||
set +e; output=$(diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_equal 0 "$status" 'healthy diagnostics returns zero'
|
||||
assert_contains 'firewall=OK' "$output" 'healthy firewall has NAT evidence'
|
||||
for expected_id in config-readable config-permissions config-parse duplicate-keys allowedips-overlap key-permissions command-wg command-wg-quick command-ip interface-exists interface-runtime systemd forwarding-ipv4 forwarding-ipv6 interface-address routes interface-address-ipv6 routes-ipv6 udp-listener firewall runtime-disk journal; do
|
||||
assert_equal 1 "$(grep -c "^${expected_id}=" <<<"$output")" "healthy output has one ${expected_id} record"
|
||||
done
|
||||
export WG_MANAGER_CONFIG=$config_file WG_MANAGER_IPV4_FORWARD_FILE=$TEST_ROOT/ipv4-forward WG_MOCK_INTERFACE=wg9
|
||||
cp -- "$config_file" "$TEST_ROOT/nondefault.conf"
|
||||
sed -i 's/WG_INTERFACE="wg0"/WG_INTERFACE="wg9"/' "$TEST_ROOT/nondefault.conf"
|
||||
export WG_MANAGER_CONFIG=$TEST_ROOT/nondefault.conf
|
||||
export DIAG_IP_LINK_OUTPUT='4: wg9: <UP> mtu 1420' DIAG_IP_ADDR_OUTPUT='4: wg9 inet 192.0.2.1/24 scope global wg9' DIAG_IP_ROUTE_OUTPUT='192.0.2.0/24 dev wg9 scope link' DIAG_SS_OUTPUT='udp UNCONN 0 0 0.0.0.0:51820 0.0.0.0:*' DIAG_NFT_OUTPUT='oifname "wg9" ip saddr 192.0.2.0/24 masquerade' SYSTEMCTL_INIT=running SYSTEMCTL_STATE=active
|
||||
: >"$TEST_MOCK_LOG"
|
||||
set +e; output=$(bash "$PROJECT_DIR/wg-manager.sh" diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_equal 0 "$status" 'fresh CLI healthy non-default config returns zero'
|
||||
assert_contains 'interface-exists=OK' "$output" 'fresh CLI loads non-default interface config'
|
||||
assert_contains 'interface-runtime=OK' "$output" 'fresh CLI uses non-default wg argv'
|
||||
assert_equal 1 "$(grep -c '^wg show wg9 dump$' "$TEST_MOCK_LOG")" 'fresh CLI emits one exact non-default wg query'
|
||||
assert_equal 1 "$(grep -c '^ip link show dev wg9$' "$TEST_MOCK_LOG")" 'fresh CLI emits one exact non-default ip query'
|
||||
assert_equal 1 "$(grep -c '^systemctl is-system-running$' "$TEST_MOCK_LOG")" 'fresh CLI emits one systemd capability query'
|
||||
assert_equal 1 "$(grep -c '^systemctl is-active -- wg-quick@wg9$' "$TEST_MOCK_LOG")" 'fresh CLI emits one service query'
|
||||
assert_equal 1 "$(grep -c '^journalctl -u wg-quick@wg9 --since 24\\ hours\\ ago --no-pager$' "$TEST_MOCK_LOG")" 'fresh CLI emits one bounded journal query'
|
||||
export DIAG_IP_LINK_FAIL=1
|
||||
: >"$TEST_MOCK_LOG"
|
||||
set +e; output=$(bash "$PROJECT_DIR/wg-manager.sh" diagnose --quiet 2>&1); status=$?; set -e
|
||||
assert_equal "$EXIT_OPERATION" "$status" 'fresh CLI critical failure returns operation status'
|
||||
assert_contains 'journal=' "$output" 'fresh CLI continues after an early interface failure'
|
||||
unset DIAG_IP_LINK_FAIL
|
||||
unset DIAG_IP_LINK_OUTPUT DIAG_IP_ADDR_OUTPUT DIAG_IP_ROUTE_OUTPUT DIAG_SS_OUTPUT DIAG_NFT_OUTPUT SYSTEMCTL_STATE
|
||||
;;
|
||||
esac
|
||||
if [[ $test_group == diagnostics-exit-codes ]]; then
|
||||
set +e; diagnose --bad >/dev/null 2>&1; status=$?; set -e
|
||||
assert_equal "$EXIT_USAGE" "$status" 'неизвестный флаг diagnose'
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ $test_group == config ]]; then
|
||||
run_config_tests
|
||||
elif [[ $test_group == show-client || $test_group == config-export || $test_group == qr || $test_group == path-safety || $test_group == no-secret-default-output ]]; then
|
||||
@@ -2801,6 +3107,8 @@ elif [[ $test_group == add-client || $test_group == address-allocation || $test_
|
||||
run_m4_tests
|
||||
elif [[ $test_group == remove-client || $test_group == remove-cancel || $test_group == remove-exact-block || $test_group == remove-rollback || $test_group == archive-client || $test_group == archive-path-safety || $test_group == remove-preserves-neighbors ]]; then
|
||||
run_m6_tests
|
||||
elif [[ $test_group == diagnostics || $test_group == diagnostics-systemd || $test_group == diagnostics-firewall || $test_group == diagnostics-forwarding || $test_group == diagnostics-runtime-diff || $test_group == diagnostics-read-only || $test_group == diagnostics-exit-codes ]]; then
|
||||
run_m7_tests
|
||||
else
|
||||
run_config_tests
|
||||
run_gum_tests
|
||||
@@ -2813,6 +3121,11 @@ else
|
||||
run_m5_tests
|
||||
test_group=''
|
||||
run_m6_tests
|
||||
for m7_group in diagnostics diagnostics-systemd diagnostics-firewall diagnostics-forwarding diagnostics-runtime-diff diagnostics-read-only diagnostics-exit-codes; do
|
||||
test_group=$m7_group
|
||||
run_m7_tests
|
||||
done
|
||||
unset m7_group
|
||||
test_group=''
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user