diff --git a/.gitignore b/.gitignore index 974eaa0..f4bedf6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ config/wazuh_indexer_ssl_certs/ config/wazuh_indexer/internal_users.yml __pycache__/ *.pyc + +# Testwebserver: .env enthaelt nur Port/Optionen, keine Geheimnisse +!testwebserver/.env +testwebserver/certs/ diff --git a/README.md b/README.md index 739f1c2..370f63d 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,24 @@ HTTPS“ und lädt die Podman-Regeln neu. außerhalb des Servers (Cloud-Firewall, Proxmox-Firewall, Netzwerk-Firewall) kann er nicht sehen. Dafür von einem anderen Rechner testen: `curl -kI https://:443` +### Testwebserver (`testwebserver/`) + +Ein kleiner Webserver, um die Erreichbarkeit eines Ports unabhängig von Wazuh zu testen. Er ist +eigenständig, du kannst das Verzeichnis auch allein auf einen anderen Rechner kopieren. + +```bash +cd testwebserver +nano .env # PORT=8080, TLS=no|yes, MESSAGE=... +./start.sh # erkennt Docker/Podman, startet und testet lokal +./start.sh logs # jede Anfrage mit Client-IP: kommt der Zugriff von außen überhaupt an? +sudo ./start.sh firewall # Firewall-Check für diesen Port (legt fehlende Regeln an) +./start.sh down +``` + +Es wird kein eigenes Image gebaut, nur das offizielle `python:3.12-alpine` geladen. Bei +`TLS=yes` erzeugt `start.sh` ein selbstsigniertes Zertifikat (braucht `openssl`). Ist der Port +schon belegt, meldet `start.sh` das vor dem Start. + ## Wo finde ich die Daten im Dashboard? Per SSH geholte Logs und Agentless-Ergebnisse laufen **über den Manager**. Im Dashboard @@ -214,6 +232,7 @@ Ports 1514 und 1515 sind nicht nach außen veröffentlicht. Nur der Collector er | `hosts.yml` | Die einzige Stelle für Hosts und Optionen | | `setup.sh` | Einrichtung und Betrieb (erkennt Docker/Podman) | | `firewall-check.sh` | Erreichbarkeit des Dashboard-Ports prüfen und reparieren | +| `testwebserver/` | Eigenständiger Testwebserver für Port-/Firewall-Tests | | `docker-compose.yml` | Stack. Nach außen geht nur das Dashboard (`DASHBOARD_PORT`) | | `.env` | Version, Zeitzone, Passwörter (von `setup.sh` erzeugt, nicht committen) | | `ssh/` | `id_ed25519` (privater Schlüssel, nicht committen), `known_hosts` | diff --git a/firewall-check.sh b/firewall-check.sh index 7324780..9eb2687 100755 --- a/firewall-check.sh +++ b/firewall-check.sh @@ -267,11 +267,13 @@ fi # --------------------------------------------------------------------------- head_ "5. Lokaler Verbindungstest" -probe() { # probe HOST -> "HTTP " | "TCP offen" | leer +probe() { # probe HOST -> "HTTPS " | "HTTP " | "TCP offen" | leer local code="" have curl && code=$(curl -sk --max-time 5 -o /dev/null -w '%{http_code}' "https://$1:$PORT/" 2>/dev/null) - if [ -n "$code" ] && [ "$code" != 000 ]; then echo "HTTP $code"; return; fi - timeout 5 bash -c "exec 3<>/dev/tcp/$1/$PORT" 2>/dev/null && echo "TCP offen, aber keine HTTPS-Antwort" + if [ -n "$code" ] && [ "$code" != 000 ]; then echo "HTTPS $code"; return; fi + have curl && code=$(curl -s --max-time 5 -o /dev/null -w '%{http_code}' "http://$1:$PORT/" 2>/dev/null) + if [ -n "$code" ] && [ "$code" != 000 ]; then echo "HTTP $code, ohne TLS"; return; fi + timeout 5 bash -c "exec 3<>/dev/tcp/$1/$PORT" 2>/dev/null && echo "TCP offen, aber keine HTTP(S)-Antwort" } MYIP=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i{html.escape(k)}{html.escape(v)}" + for k, v in self.headers.items()) + page = f""" +Testwebserver + +

{html.escape(MESSAGE)}

+

Container {html.escape(HOST)} · Port {html.escape(PUBLIC_PORT)} +({'HTTPS' if TLS else 'HTTP'}) · {now}

+

Deine Adresse (wie der Container sie sieht): {html.escape(self.client_address[0])}

+

Request-Header

{rows}
""" + self._send(200, page, "text/html") + + do_HEAD = do_GET + + def log_message(self, fmt, *args): + print(f"{datetime.now():%Y-%m-%d %H:%M:%S} {self.client_address[0]} {fmt % args}", flush=True) + + +def main(): + srv = ThreadingHTTPServer(("0.0.0.0", 8000), Handler) + if TLS: + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + try: + ctx.load_cert_chain("/certs/server.crt", "/certs/server.key") + except OSError as exc: + sys.exit(f"TLS=yes, aber Zertifikat fehlt ({exc}) - ./start.sh erzeugt es") + # Handshake erst im Request-Thread: ein haengender Client blockiert sonst alle anderen + srv.socket = ctx.wrap_socket(srv.socket, server_side=True, do_handshake_on_connect=False) + print(f"Testwebserver lauscht ({'HTTPS' if TLS else 'HTTP'}), von aussen Port {PUBLIC_PORT}", flush=True) + srv.serve_forever() + + +if __name__ == "__main__": + main() diff --git a/testwebserver/start.sh b/testwebserver/start.sh new file mode 100755 index 0000000..a591751 --- /dev/null +++ b/testwebserver/start.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# Testwebserver starten/stoppen - erkennt Docker oder Podman selbst (wie ../setup.sh). +# +# ./start.sh starten (= up) +# ./start.sh down stoppen und entfernen +# ./start.sh restart neu starten (z.B. nach Aenderung der .env) +# ./start.sh logs Zugriffe live ansehen (jede Anfrage mit Client-IP) +# ./start.sh ps Status +# ./start.sh test lokal aufrufen und Adresse fuer den Test von aussen anzeigen +# ./start.sh firewall ../firewall-check.sh fuer diesen Port (Optionen werden durchgereicht) +# +# Port, TLS und Text stehen in .env. Erkennung uebersteuern: CONTAINER_ENGINE=docker|podman +set -euo pipefail +cd "$(dirname "$0")" + +info() { echo -e "\e[1;32m==>\e[0m $*"; } +warn() { echo -e "\e[1;33m!!\e[0m $*" >&2; } +die() { echo -e "\e[1;31mFEHLER:\e[0m $*" >&2; exit 1; } +env_get() { grep "^$1=" .env 2>/dev/null | cut -d= -f2- || true; } + +PORT=$(env_get PORT); PORT=${PORT:-8080} +TLS=$(env_get TLS); TLS=${TLS:-no} +[[ $PORT =~ ^[0-9]+$ ]] || die "PORT in .env ist keine Zahl: $PORT" +SCHEME=http; [[ ${TLS,,} =~ ^(yes|true|1)$ ]] && SCHEME=https + +ENGINE="${CONTAINER_ENGINE:-}" +if [ -z "$ENGINE" ]; then + if command -v docker >/dev/null 2>&1 && ! docker --version 2>/dev/null | grep -qi podman; then ENGINE=docker + elif command -v podman >/dev/null 2>&1; then ENGINE=podman + else die "Weder Docker noch Podman gefunden"; fi +fi +case "$ENGINE" in + docker) + if docker compose version >/dev/null 2>&1; then DC=(docker compose) + elif command -v docker-compose >/dev/null 2>&1; then DC=(docker-compose) + else die "Docker Compose fehlt"; fi ;; + podman) + if command -v podman-compose >/dev/null 2>&1; then DC=(podman-compose) + elif podman compose version >/dev/null 2>&1; then DC=(podman compose) + else die "podman-compose fehlt (RHEL: EPEL aktivieren, dnf install podman-compose)"; fi + # Rootless Podman darf nur Ports ab ip_unprivileged_port_start (meist 1024) belegen + if [ "$(podman info --format '{{.Host.Security.Rootless}}' 2>/dev/null)" = true ]; then + min=$(cat /proc/sys/net/ipv4/ip_unprivileged_port_start 2>/dev/null || echo 1024) + [ "$PORT" -ge "$min" ] || die "Rootless Podman kann Port $PORT nicht belegen (erst ab $min). Hoeheren Port in .env oder: sudo ./start.sh" + fi ;; + *) die "CONTAINER_ENGINE muss docker oder podman sein" ;; +esac + +make_cert() { + [ "$SCHEME" = https ] || return 0 + [ -s certs/server.crt ] && [ -s certs/server.key ] && return 0 + command -v openssl >/dev/null || die "TLS=yes braucht openssl auf dem Host (RHEL: dnf install openssl)" + info "Erzeuge selbstsigniertes Zertifikat (certs/)" + mkdir -p certs + openssl req -x509 -newkey rsa:2048 -nodes -days 825 -subj "/CN=$(uname -n)" \ + -keyout certs/server.key -out certs/server.crt 2>/dev/null + chmod 644 certs/server.key # Container liest als anderer Benutzer; reiner Testschluessel +} + +my_ip() { ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i Firewall: sudo ./start.sh firewall" +} + +port_free() { + # Belegt, aber nicht von uns? Dann klar melden statt kryptischer Engine-Fehlermeldung. + local used + used=$(ss -Hltnp "sport = :$PORT" 2>/dev/null) || return 0 + [ -z "$used" ] && return 0 + # Unser eigener, schon laufender Container auf genau diesem Port ist kein Konflikt + "$ENGINE" ps --format '{{.Names}} {{.Ports}}' 2>/dev/null | grep testwebserver | grep -q ":$PORT->" && return 0 + local who; who=$(echo "$used" | grep -o 'users:(("[^"]*' | head -1 | cut -d'"' -f2 || true) + die "Port $PORT ist schon belegt${who:+ (Prozess: $who)}. Anderen PORT in .env eintragen." +} + +mkdir -p certs # wird immer gemountet, auch ohne TLS +case "${1:-up}" in + up) + port_free + make_cert + info "Starte Testwebserver mit $ENGINE (${DC[*]}) auf Port $PORT ($SCHEME)" + "${DC[@]}" up -d + sleep 2 + cmd_test ;; + down) "${DC[@]}" down ;; + restart) make_cert; "${DC[@]}" down; port_free; "${DC[@]}" up -d; sleep 2; cmd_test ;; + logs) "${DC[@]}" logs -f ;; + ps) "${DC[@]}" ps ;; + test) cmd_test ;; + firewall) + shift + [ -x ../firewall-check.sh ] || die "../firewall-check.sh nicht gefunden" + extra=(); [ "$SCHEME" = http ] && extra=(--tcp) + ../firewall-check.sh --port "$PORT" "${extra[@]}" "$@" ;; + *) sed -n '2,13p' "$0"; exit 2 ;; +esac