Testwebserver fuer Port- und Firewall-Tests hinzufuegen
- testwebserver/: eigenstaendiger Python-Webserver (python:3.12-alpine, kein Build), Port/TLS/Text in .env, zeigt Client-IP und loggt jeden Zugriff - start.sh: erkennt Docker/Podman (inkl. rootless Port-Grenze), prueft Port-Konflikte, erzeugt bei TLS=yes ein selbstsigniertes Zertifikat, Befehle up/down/restart/logs/ps/test/firewall - firewall-check.sh: erkennt auch HTTP-Dienste ohne TLS als erreichbar - .gitignore: testwebserver/.env versionieren, testwebserver/certs/ ausschliessen - README: Abschnitt Testwebserver Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
174c10505a
commit
2962e4a3d7
@@ -6,3 +6,7 @@ config/wazuh_indexer_ssl_certs/
|
|||||||
config/wazuh_indexer/internal_users.yml
|
config/wazuh_indexer/internal_users.yml
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
|
# Testwebserver: .env enthaelt nur Port/Optionen, keine Geheimnisse
|
||||||
|
!testwebserver/.env
|
||||||
|
testwebserver/certs/
|
||||||
|
|||||||
@@ -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
|
außerhalb des Servers (Cloud-Firewall, Proxmox-Firewall, Netzwerk-Firewall) kann er nicht
|
||||||
sehen. Dafür von einem anderen Rechner testen: `curl -kI https://<server>:443`
|
sehen. Dafür von einem anderen Rechner testen: `curl -kI https://<server>: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?
|
## Wo finde ich die Daten im Dashboard?
|
||||||
|
|
||||||
Per SSH geholte Logs und Agentless-Ergebnisse laufen **über den Manager**. 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 |
|
| `hosts.yml` | Die einzige Stelle für Hosts und Optionen |
|
||||||
| `setup.sh` | Einrichtung und Betrieb (erkennt Docker/Podman) |
|
| `setup.sh` | Einrichtung und Betrieb (erkennt Docker/Podman) |
|
||||||
| `firewall-check.sh` | Erreichbarkeit des Dashboard-Ports prüfen und reparieren |
|
| `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`) |
|
| `docker-compose.yml` | Stack. Nach außen geht nur das Dashboard (`DASHBOARD_PORT`) |
|
||||||
| `.env` | Version, Zeitzone, Passwörter (von `setup.sh` erzeugt, nicht committen) |
|
| `.env` | Version, Zeitzone, Passwörter (von `setup.sh` erzeugt, nicht committen) |
|
||||||
| `ssh/` | `id_ed25519` (privater Schlüssel, nicht committen), `known_hosts` |
|
| `ssh/` | `id_ed25519` (privater Schlüssel, nicht committen), `known_hosts` |
|
||||||
|
|||||||
+6
-4
@@ -267,11 +267,13 @@ fi
|
|||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
head_ "5. Lokaler Verbindungstest"
|
head_ "5. Lokaler Verbindungstest"
|
||||||
probe() { # probe HOST -> "HTTP <code>" | "TCP offen" | leer
|
probe() { # probe HOST -> "HTTPS <code>" | "HTTP <code>" | "TCP offen" | leer
|
||||||
local code=""
|
local code=""
|
||||||
have curl && code=$(curl -sk --max-time 5 -o /dev/null -w '%{http_code}' "https://$1:$PORT/" 2>/dev/null)
|
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
|
if [ -n "$code" ] && [ "$code" != 000 ]; then echo "HTTPS $code"; return; fi
|
||||||
timeout 5 bash -c "exec 3<>/dev/tcp/$1/$PORT" 2>/dev/null && echo "TCP offen, aber keine HTTPS-Antwort"
|
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<NF;i++) if($i=="src") print $(i+1)}' | head -1)
|
MYIP=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<NF;i++) if($i=="src") print $(i+1)}' | head -1)
|
||||||
for h in 127.0.0.1 $MYIP; do
|
for h in 127.0.0.1 $MYIP; do
|
||||||
@@ -280,7 +282,7 @@ for h in 127.0.0.1 $MYIP; do
|
|||||||
problem "$h:$PORT nicht erreichbar"
|
problem "$h:$PORT nicht erreichbar"
|
||||||
elif [[ $res == TCP* ]] && [ "$TCP_ONLY" = 0 ]; then
|
elif [[ $res == TCP* ]] && [ "$TCP_ONLY" = 0 ]; then
|
||||||
# conmon (Podman) bzw. docker-proxy halten den Port offen, auch wenn dahinter nichts ankommt
|
# conmon (Podman) bzw. docker-proxy halten den Port offen, auch wenn dahinter nichts ankommt
|
||||||
problem "$h:$PORT nimmt Verbindungen an, liefert aber kein HTTPS - Port nur reserviert (Weiterleitung kaputt?) oder Dashboard startet noch"
|
problem "$h:$PORT nimmt Verbindungen an, liefert aber keine HTTP(S)-Antwort - Port nur reserviert (Weiterleitung kaputt?) oder Dashboard startet noch"
|
||||||
else
|
else
|
||||||
ok "$h:$PORT erreichbar ($res)"
|
ok "$h:$PORT erreichbar ($res)"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Port, auf dem der Testwebserver von aussen erreichbar ist
|
||||||
|
PORT=8080
|
||||||
|
# yes = HTTPS mit selbstsigniertem Zertifikat (wird von start.sh erzeugt), no = HTTP
|
||||||
|
TLS=no
|
||||||
|
# Text, der auf der Seite angezeigt wird
|
||||||
|
MESSAGE=Testwebserver laeuft
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Kleiner Testwebserver, um die Erreichbarkeit eines Ports zu pruefen (Firewall, NAT, ...).
|
||||||
|
# Start: ./start.sh (erkennt Docker/Podman). Port und TLS stehen in .env.
|
||||||
|
services:
|
||||||
|
testwebserver:
|
||||||
|
image: docker.io/library/python:3.12-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
command: ["python", "-u", "/app/server.py"]
|
||||||
|
environment:
|
||||||
|
- TLS=${TLS}
|
||||||
|
- MESSAGE=${MESSAGE}
|
||||||
|
- PUBLIC_PORT=${PORT}
|
||||||
|
ports:
|
||||||
|
- "${PORT}:8000"
|
||||||
|
volumes:
|
||||||
|
- ./server.py:/app/server.py:ro,z
|
||||||
|
- ./certs:/certs:ro,z
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
"""Minimaler Testwebserver: zeigt Hostname, Zeit und Client-Adresse und loggt jeden Zugriff.
|
||||||
|
|
||||||
|
GET / HTML-Seite
|
||||||
|
GET /health "ok" (fuer Skripte)
|
||||||
|
TLS=yes HTTPS mit /certs/server.crt + /certs/server.key
|
||||||
|
"""
|
||||||
|
|
||||||
|
import html
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import ssl
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||||
|
|
||||||
|
TLS = os.environ.get("TLS", "no").lower() in ("yes", "true", "1")
|
||||||
|
MESSAGE = os.environ.get("MESSAGE", "Testwebserver laeuft")
|
||||||
|
PUBLIC_PORT = os.environ.get("PUBLIC_PORT", "?")
|
||||||
|
HOST = socket.gethostname()
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(BaseHTTPRequestHandler):
|
||||||
|
server_version = "testwebserver/1.0"
|
||||||
|
|
||||||
|
def _send(self, code, body, ctype):
|
||||||
|
data = body.encode()
|
||||||
|
self.send_response(code)
|
||||||
|
self.send_header("Content-Type", f"{ctype}; charset=utf-8")
|
||||||
|
self.send_header("Content-Length", str(len(data)))
|
||||||
|
self.end_headers()
|
||||||
|
if self.command != "HEAD":
|
||||||
|
self.wfile.write(data)
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
if self.path == "/health":
|
||||||
|
return self._send(200, "ok\n", "text/plain")
|
||||||
|
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
rows = "".join(f"<tr><th>{html.escape(k)}</th><td>{html.escape(v)}</td></tr>"
|
||||||
|
for k, v in self.headers.items())
|
||||||
|
page = f"""<!doctype html><html lang="de"><head><meta charset="utf-8">
|
||||||
|
<title>Testwebserver</title>
|
||||||
|
<style>body{{font-family:sans-serif;max-width:46rem;margin:2rem auto;padding:0 1rem}}
|
||||||
|
th{{text-align:left;padding-right:1rem;vertical-align:top}}h1{{color:#2a7d2a}}</style></head>
|
||||||
|
<body><h1>{html.escape(MESSAGE)}</h1>
|
||||||
|
<p>Container <b>{html.escape(HOST)}</b> · Port {html.escape(PUBLIC_PORT)}
|
||||||
|
({'HTTPS' if TLS else 'HTTP'}) · {now}</p>
|
||||||
|
<p>Deine Adresse (wie der Container sie sieht): <b>{html.escape(self.client_address[0])}</b></p>
|
||||||
|
<h2>Request-Header</h2><table>{rows}</table></body></html>"""
|
||||||
|
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()
|
||||||
Executable
+102
@@ -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<NF;i++) if($i=="src") print $(i+1)}' | head -1; }
|
||||||
|
|
||||||
|
cmd_test() {
|
||||||
|
local code
|
||||||
|
code=$(curl -sk --max-time 5 -o /dev/null -w '%{http_code}' "$SCHEME://127.0.0.1:$PORT/health" || true)
|
||||||
|
if [ "$code" = 200 ]; then info "Lokal OK: $SCHEME://127.0.0.1:$PORT"
|
||||||
|
else warn "Lokal keine Antwort ($SCHEME://127.0.0.1:$PORT, Code ${code:-000}) - laeuft er? ./start.sh ps / logs"; fi
|
||||||
|
echo "Von einem anderen Rechner testen: curl -k $SCHEME://$(my_ip):$PORT/ (oder im Browser)"
|
||||||
|
echo "Kommt nichts an, zeigt './start.sh logs' keinen Zugriff -> 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
|
||||||
Reference in New Issue
Block a user