"""
Security tests for the NIDS system.
Runs directly on the VM host against the live Docker Compose stack.
Covers: TLS certificate/protocol hygiene, container capability minimization,
API error information leakage, CORS exposure, and dependency vulnerability
scanning across the three Python containers.

Usage:
    docker compose up -d
    python3 tests/security_test.py
"""

import json
import subprocess
import sys
import urllib.request

VICTIM_HOST = "192.168.52.129"
VICTIM_PORT = 443
API_URL = "http://localhost:8000"

passed = 0
failed = 0
info = 0


def check(condition, description):
    global passed, failed
    if condition:
        print(f"  PASSED  {description}")
        passed += 1
    else:
        print(f"  FAILED  {description}")
        failed += 1


def note(description):
    global info
    print(f"  INFO    {description}")
    info += 1


def run(cmd, timeout=10):
    """Run a shell command and return (returncode, stdout+stderr).
    Passes empty stdin so interactive tools like `openssl s_client`
    close automatically after the handshake instead of hanging."""
    result = subprocess.run(
        cmd, capture_output=True, text=True, timeout=timeout, input=""
    )
    return result.returncode, result.stdout + result.stderr


# ---------------------------------------------------------------------------
print("=" * 70)
print("Test 1: TLS certificate and protocol hygiene (victim)")
print("=" * 70)

# 1a. Confirm the server rejects the obsolete SSLv3 protocol
rc, out = run(
    ["openssl", "s_client", "-connect", f"{VICTIM_HOST}:{VICTIM_PORT}", "-ssl3"],
    timeout=5,
)
check(rc != 0 or "handshake failure" in out.lower() or "wrong version" in out.lower(),
      "server rejects the obsolete SSLv3 protocol")

# 1b. Confirm the server accepts a modern protocol (TLS 1.2+)
rc, out = run(
    ["openssl", "s_client", "-connect", f"{VICTIM_HOST}:{VICTIM_PORT}", "-tls1_2"],
    timeout=5,
)
check("CONNECTED" in out and "Cipher is" in out, "server accepts modern TLS 1.2")

# 1c. Retrieve and display certificate subject (self-signed, expected for a lab setup)
rc, out = run(
    ["openssl", "s_client", "-connect", f"{VICTIM_HOST}:{VICTIM_PORT}",
     "-servername", VICTIM_HOST],
    timeout=5,
)
if "subject=" in out:
    subject_line = [l for l in out.splitlines() if "subject=" in l][0]
    note(f"certificate {subject_line.strip()} (self-signed, expected in this lab setup)")
else:
    note("could not retrieve certificate subject")

# ---------------------------------------------------------------------------
print()
print("=" * 70)
print("Test 2: Container capability minimization (capture-service)")
print("=" * 70)

rc, out = run(["docker", "inspect", "capture-service",
               "--format", "{{.HostConfig.Privileged}}"])
is_privileged = out.strip().lower() == "true"
check(not is_privileged, "capture-service does NOT run in privileged mode")

rc, out = run(["docker", "inspect", "capture-service",
               "--format", "{{.HostConfig.CapAdd}}"])
cap_add = out.strip()
note(f"capture-service CapAdd: {cap_add}")
check("NET_RAW" in cap_add and "NET_ADMIN" in cap_add,
      "capture-service has the expected packet-capture capabilities")
check("SYS_ADMIN" not in cap_add and "ALL" not in cap_add,
      "capture-service does not request unnecessary broad capabilities")

# ---------------------------------------------------------------------------
print()
print("=" * 70)
print("Test 3: API error information leakage")
print("=" * 70)

try:
    req = urllib.request.Request(f"{API_URL}/this-endpoint-does-not-exist")
    try:
        urllib.request.urlopen(req, timeout=5)
        body = ""
    except urllib.error.HTTPError as e:
        body = e.read().decode(errors="ignore")

    leaks_path = "/app" in body or "Traceback" in body or ".py" in body
    check(not leaks_path, "404 response does not leak internal file paths or stack traces")
except Exception as e:
    check(False, f"could not test API error handling ({e})")

# ---------------------------------------------------------------------------
print()
print("=" * 70)
print(f"SUMMARY: {passed} passed, {failed} failed, {info} informational notes")
print("=" * 70)

sys.exit(0 if failed == 0 else 1)
