| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- #!/usr/bin/env bash
- set -euo pipefail
- HOST="169.254.100.2"
- PORT="48888"
- PASSWORD="2026"
- INTERFACE=""
- usage() {
- cat <<'EOF'
- Usage:
- linux-negative-test.sh [options]
- Purpose:
- Safe negative checks for the Linux server.
- This script does not call /api/network/apply and does not modify netplan.
- Checks:
- - Wrong password should be rejected
- - Missing interface should fail validate
- - Invalid IP should fail validate
- - Gateway outside subnet should fail validate
- - Invalid DNS should fail validate
- Options:
- --host <ip> Server host, default: 169.254.100.2
- --port <port> Server port, default: 48888
- --password <value> Correct admin password, default: 2026
- --interface <name> Existing target interface, default: suggested_target_interface
- --help Show this help
- EOF
- }
- while [[ $# -gt 0 ]]; do
- case "$1" in
- --host)
- HOST="$2"
- shift 2
- ;;
- --port)
- PORT="$2"
- shift 2
- ;;
- --password)
- PASSWORD="$2"
- shift 2
- ;;
- --interface)
- INTERFACE="$2"
- shift 2
- ;;
- --help|-h)
- usage
- exit 0
- ;;
- *)
- echo "Unknown argument: $1" >&2
- usage >&2
- exit 2
- ;;
- esac
- done
- for cmd in curl python3; do
- if ! command -v "$cmd" >/dev/null 2>&1; then
- echo "Missing required command: $cmd" >&2
- exit 2
- fi
- done
- BASE_URL="http://${HOST}:${PORT}"
- TMP_DIR="$(mktemp -d)"
- trap 'rm -rf "$TMP_DIR"' EXIT
- log() {
- printf '[nettool-negative] %s\n' "$*"
- }
- request() {
- local method="$1"
- local path="$2"
- local password="$3"
- local payload="${4:-}"
- local body_file="$TMP_DIR/body.json"
- local http_code
- if [[ -n "$payload" ]]; then
- http_code="$(curl -sS -o "$body_file" -w "%{http_code}" -X "$method" \
- -H "X-Admin-Password: ${password}" \
- -H "Content-Type: application/json" \
- --data "$payload" \
- --connect-timeout 3 \
- --max-time 15 \
- "${BASE_URL}${path}")"
- else
- http_code="$(curl -sS -o "$body_file" -w "%{http_code}" -X "$method" \
- -H "X-Admin-Password: ${password}" \
- --connect-timeout 3 \
- --max-time 15 \
- "${BASE_URL}${path}")"
- fi
- printf '%s\n' "$http_code"
- cat "$body_file"
- }
- read_http_code() {
- sed -n '1p'
- }
- read_body() {
- sed '1d'
- }
- json_get() {
- local body="$1"
- local expr="$2"
- BODY_JSON="$body" python3 - "$expr" <<'PY'
- import json
- import os
- import sys
- expr = sys.argv[1]
- data = json.loads(os.environ["BODY_JSON"])
- value = data
- for part in expr.split('.'):
- if part:
- value = value[part]
- if isinstance(value, list):
- import json as _json
- print(_json.dumps(value, ensure_ascii=False))
- else:
- print(value)
- PY
- }
- expect_failure() {
- local name="$1"
- local expected_http="$2"
- local expected_code="$3"
- local result="$4"
- local http_code body actual_code
- http_code="$(printf '%s\n' "$result" | read_http_code)"
- body="$(printf '%s\n' "$result" | read_body)"
- actual_code="$(json_get "$body" "code")"
- if [[ "$http_code" != "$expected_http" ]]; then
- echo "${name} failed: expected http=${expected_http}, actual=${http_code}, body=${body}" >&2
- exit 1
- fi
- if [[ "$actual_code" != "$expected_code" ]]; then
- echo "${name} failed: expected code=${expected_code}, actual=${actual_code}, body=${body}" >&2
- exit 1
- fi
- log "${name} -> expected failure confirmed (http=${http_code}, code=${actual_code})"
- }
- build_payload() {
- python3 - "$1" "$2" "$3" "$4" "$5" <<'PY'
- import json
- import sys
- interface, ip, prefix, gateway, dns_csv = sys.argv[1:6]
- dns = [item.strip() for item in dns_csv.split(',') if item.strip()]
- payload = {
- "interface": interface,
- "ip": ip,
- "prefix": int(prefix),
- "gateway": gateway,
- "dns": dns,
- }
- print(json.dumps(payload, ensure_ascii=False))
- PY
- }
- log "discover interface"
- result="$(request GET "/api/network/interfaces" "$PASSWORD")"
- http_code="$(printf '%s\n' "$result" | read_http_code)"
- body="$(printf '%s\n' "$result" | read_body)"
- if [[ "$http_code" != "200" ]]; then
- echo "Failed to query interfaces: http=${http_code} body=${body}" >&2
- exit 1
- fi
- if [[ -z "$INTERFACE" ]]; then
- INTERFACE="$(json_get "$body" "data.suggested_target_interface")"
- fi
- if [[ -z "$INTERFACE" || "$INTERFACE" == "None" ]]; then
- echo "Could not determine target interface. Pass --interface explicitly." >&2
- exit 1
- fi
- log "target interface=${INTERFACE}"
- log "wrong password should be rejected"
- result="$(request GET "/api/health" "wrong-password")"
- expect_failure "wrong password health" "401" "1002" "$result"
- log "missing interface should fail validate"
- payload="$(build_payload "not-a-real-iface" "192.168.10.20" "24" "192.168.10.1" "8.8.8.8")"
- result="$(request POST "/api/network/validate" "$PASSWORD" "$payload")"
- expect_failure "invalid interface validate" "400" "3001" "$result"
- log "invalid ip should fail validate"
- payload="$(build_payload "$INTERFACE" "999.999.1.1" "24" "192.168.10.1" "8.8.8.8")"
- result="$(request POST "/api/network/validate" "$PASSWORD" "$payload")"
- expect_failure "invalid ip validate" "400" "3001" "$result"
- log "gateway outside subnet should fail validate"
- payload="$(build_payload "$INTERFACE" "192.168.10.20" "24" "10.0.0.1" "8.8.8.8")"
- result="$(request POST "/api/network/validate" "$PASSWORD" "$payload")"
- expect_failure "gateway subnet validate" "400" "3001" "$result"
- log "invalid dns should fail validate"
- payload="$(build_payload "$INTERFACE" "192.168.10.20" "24" "192.168.10.1" "8.8.8.8,not-an-ip")"
- result="$(request POST "/api/network/validate" "$PASSWORD" "$payload")"
- expect_failure "invalid dns validate" "400" "3001" "$result"
- log "negative checks completed"
|