#!/bin/bash # Einrichtung und Betrieb des Wazuh-SSH-Pull-Stacks. # Erkennt selbst, ob Docker oder Podman vorhanden ist (RHEL: Podman), und das passende # Compose-Werkzeug (docker compose, docker-compose, podman-compose, podman compose). # # ./setup.sh Erstinstallation (Passwoerter, Zertifikate, SSH-Key, Images) # ./setup.sh up Stack starten (+ Kernel-Parameter, Firewall-Check, Autostart als root) # ./setup.sh down Stack stoppen # ./setup.sh ps | logs [dienst] # ./setup.sh host-script Skript zum Vorbereiten eines Zielhosts ausgeben # ./setup.sh host-script | ssh admin@host sudo sh # Optionen: --sudo (NOPASSWD-sudo), --tunnel (fuer agent_tunnel), # --user=NAME (Standard wazuh-pull) # ./setup.sh keyscan Host-Keys aller Hosts aus hosts.yml nach ssh/known_hosts # ./setup.sh test SSH-Zugriff und Rechte auf allen Hosts pruefen # ./setup.sh apply hosts.yml uebernehmen (Manager + Collector neu starten) # ./setup.sh passwords Zugangsdaten anzeigen # ./setup.sh info Erkannte Umgebung anzeigen # ./setup.sh firewall Erreichbarkeit des Dashboard-Ports pruefen, fehlende Regeln anlegen # (--check nur pruefen, --yes ohne Rueckfrage, --port N) # # Mit Podman bitte als root ausfuehren (sudo ./setup.sh ...), siehe check_rootless. # Erkennung uebersteuern: CONTAINER_ENGINE=docker|podman ./setup.sh ... # Kernel-Parameter nicht anfassen: WAZUH_SKIP_SYSCTL=1 ./setup.sh ... 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; } is_root() { [ "$(id -u)" -eq 0 ]; } ENGINE="" DC=() SELINUX=no PROXY_ARGS=() detect_runtime() { [ -n "$ENGINE" ] && return ENGINE="${CONTAINER_ENGINE:-}" if [ -z "$ENGINE" ]; then # Auf RHEL ist "docker" oft nur der podman-docker-Wrapper -> dann Podman nehmen 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. RHEL: sudo dnf install -y podman" fi fi case "$ENGINE" in docker) docker info >/dev/null 2>&1 || die "Docker-Daemon nicht erreichbar (laeuft dockerd? Benutzer in Gruppe 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 (Paket docker-compose-plugin)"; fi ;; podman) check_rootless 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 9: sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm sudo dnf install -y podman-compose oder: sudo pip3 install podman-compose" fi ;; *) die "CONTAINER_ENGINE muss docker oder podman sein" ;; esac if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce)" != "Disabled" ]; then SELINUX=yes # docker-compose.yml nutzt ":z" auf allen Bind-Mounts fi sync_env_keys load_proxy check_docker_proxy } check_rootless() { # Rootless Podman scheitert hier an: memlock/nofile-Ulimits der Wazuh-Container, # Port 443 (< 1024) und Zertifikaten, die im Container per chown an UIDs 999/1000 gehen. if [ "$(podman info --format '{{.Host.Security.Rootless}}' 2>/dev/null)" = "true" ]; then die "Podman laeuft rootless. Wazuh braucht erhoehte Ulimits und Port $(env_get DASHBOARD_PORT 2>/dev/null || echo 443). Bitte als root ausfuehren: sudo ./setup.sh ${CMD:-}" fi } # Docker oder Podman (gleiche CLI fuer run/images/info) engine() { "$ENGINE" "$@"; } compose() { "${DC[@]}" "$@"; } genpw() { # Erfuellt die Wazuh-API-Richtlinie (Gross, klein, Ziffer, Sonderzeichen) und # enthaelt keine Zeichen, die in den sed-Aufrufen der Wazuh-Images stoeren. echo "$(tr -dc 'A-Za-z0-9' > .env; fi } env_get() { grep "^$1=" .env 2>/dev/null | cut -d= -f2- || true; } INTERNAL_NO_PROXY="wazuh.manager,wazuh.indexer,wazuh.dashboard,sshpull.collector,localhost,127.0.0.1" sync_env_keys() { # Neue Schluessel aus .env.example in eine bestehende .env uebernehmen (leer bzw. Default) [ -f .env ] || return 0 local line key while IFS= read -r line; do [[ $line =~ ^([A-Z_]+)= ]] || continue key=${BASH_REMATCH[1]} grep -q "^$key=" .env || { echo "$line" >> .env; info ".env: $key ergaenzt"; } done < .env.example } load_proxy() { # Proxy aus .env in die Umgebung dieses Skripts: Podman nutzt ihn fuer Pull und Build, # "run" reicht ihn an das Zertifikat-Tool weiter. local hp hsp np hp=$(env_get HTTP_PROXY); hsp=$(env_get HTTPS_PROXY); np=$(env_get NO_PROXY) [ -n "$hp$hsp" ] || return 0 export HTTP_PROXY="$hp" http_proxy="$hp" HTTPS_PROXY="$hsp" https_proxy="$hsp" export NO_PROXY="$INTERNAL_NO_PROXY${np:+,$np}" no_proxy="$INTERNAL_NO_PROXY${np:+,$np}" PROXY_ARGS=(-e HTTP_PROXY -e HTTPS_PROXY -e NO_PROXY -e http_proxy -e https_proxy -e no_proxy) } check_docker_proxy() { # Docker laedt Images ueber den Daemon, nicht ueber die Umgebung dieses Skripts [ "$ENGINE" = docker ] && [ -n "${HTTPS_PROXY:-}${HTTP_PROXY:-}" ] || return 0 [ -n "$(docker info --format '{{.HTTPSProxy}}{{.HTTPProxy}}' 2>/dev/null)" ] && return 0 warn "Proxy in .env gesetzt, aber der Docker-Daemon hat keinen (Image-Pulls brauchen ihn)." warn "Als root /etc/systemd/system/docker.service.d/http-proxy.conf mit diesem Inhalt anlegen:" { echo " [Service]" echo " Environment=\"HTTP_PROXY=${HTTP_PROXY:-}\" \"HTTPS_PROXY=${HTTPS_PROXY:-}\" \"NO_PROXY=localhost,127.0.0.1\"" } >&2 warn "Dann: systemctl daemon-reload && systemctl restart docker (startet alle Container neu)" } tune_kernel() { local mmc mmc=$(cat /proc/sys/vm/max_map_count) [ "$mmc" -ge 262144 ] && return [ "${WAZUH_SKIP_SYSCTL:-0}" = 1 ] && { warn "vm.max_map_count=$mmc, Anpassung uebersprungen (WAZUH_SKIP_SYSCTL=1)"; return; } if is_root; then info "Setze vm.max_map_count=262144 (dauerhaft in /etc/sysctl.d/99-wazuh.conf)" echo 'vm.max_map_count=262144' > /etc/sysctl.d/99-wazuh.conf sysctl -q -w vm.max_map_count=262144 else warn "vm.max_map_count ist $mmc (benoetigt 262144). Als root setzen mit:" warn " echo 'vm.max_map_count=262144' | sudo tee /etc/sysctl.d/99-wazuh.conf && sudo sysctl --system" fi } check_firewall() { # Nach dem Start: Erreichbarkeit des Dashboard-Ports pruefen (firewalld, ufw, iptables, # nftables, Container-Portweiterleitung) und als root fehlende Regeln anlegen. # Wichtig: niemals "firewall-cmd --reload" - das loescht die Portregeln von Podman/Docker. if is_root; then ./firewall-check.sh --yes || warn "Firewall-Check meldet Probleme (siehe oben)" else ./firewall-check.sh --check || warn "Firewall-Check: fuer automatische Regeln 'sudo ./setup.sh firewall'"; fi } enable_autostart() { # Docker startet "restart: always"-Container selbst; Podman hat keinen Daemon, # dafuer gibt es podman-restart.service. [ "$ENGINE" = podman ] || return 0 command -v systemctl >/dev/null 2>&1 || return 0 if systemctl is-enabled podman-restart.service >/dev/null 2>&1; then return 0; fi if is_root; then info "Aktiviere podman-restart.service (Container starten nach Reboot)" systemctl enable -q podman-restart.service else warn "Autostart nach Reboot: sudo systemctl enable podman-restart.service" fi } cmd_info() { detect_runtime echo "Engine: $ENGINE ($("$ENGINE" --version | head -1))" echo "Compose: ${DC[*]}" echo "SELinux: $SELINUX" echo "root: $(is_root && echo ja || echo nein)" echo "max_map: $(cat /proc/sys/vm/max_map_count)" echo "Proxy: ${HTTPS_PROXY:-${HTTP_PROXY:-keiner}}${NO_PROXY:+ (NO_PROXY=$NO_PROXY)}" } cmd_init() { detect_runtime info "Container-Umgebung: $ENGINE mit ${DC[*]} (SELinux: $SELINUX)" command -v ssh-keygen >/dev/null || die "ssh-keygen fehlt (RHEL: dnf install openssh-clients)" tune_kernel # --- .env + Passwoerter ------------------------------------------------- if [ ! -f .env ]; then cp .env.example .env # Proxy aus der Shell uebernehmen (z.B. export HTTPS_PROXY=... vor dem ersten Setup) local k v for k in HTTP_PROXY HTTPS_PROXY NO_PROXY; do v=$(printenv "$k" || printenv "${k,,}" || true) if [ -n "$v" ]; then set_env "$k" "$v"; info ".env: $k aus der Umgebung uebernommen"; fi done fi sync_env_keys load_proxy check_docker_proxy for k in INDEXER_PASSWORD DASHBOARD_PASSWORD API_PASSWORD; do if [ -z "$(env_get $k)" ]; then set_env "$k" "$(genpw)"; info "$k erzeugt"; fi done chmod 600 .env local version version=$(env_get WAZUH_VERSION) # --- internal_users.yml mit Hashes ------------------------------------- if [ ! -f config/wazuh_indexer/internal_users.yml ]; then info "Erzeuge Indexer-Benutzer (Passwort-Hashes)" hash() { engine run --rm -e JAVA_HOME=/usr/share/wazuh-indexer/jdk --entrypoint bash \ "docker.io/wazuh/wazuh-indexer:$version" \ /usr/share/wazuh-indexer/plugins/opensearch-security/tools/hash.sh -p "$1" 2>/dev/null | tail -1 } local h_admin h_kibana h_admin=$(hash "$(env_get INDEXER_PASSWORD)") h_kibana=$(hash "$(env_get DASHBOARD_PASSWORD)") [[ $h_admin == \$2* && $h_kibana == \$2* ]] || die "Hash-Erzeugung fehlgeschlagen" cat > config/wazuh_indexer/internal_users.yml <:$(env_get DASHBOARD_PORT) (admin / siehe ./setup.sh passwords) EOF } cmd_host_script() { [ -f ssh/id_ed25519.pub ] || die "ssh/id_ed25519.pub fehlt - erst ./setup.sh ausfuehren" local pub user="wazuh-pull" sudo=no opts="restrict" pub=$(cat ssh/id_ed25519.pub) for a in "$@"; do case "$a" in --sudo) sudo=yes ;; --tunnel) opts='restrict,port-forwarding,permitlisten="127.0.0.1:1514",permitlisten="127.0.0.1:1515",permitopen="127.0.0.1:1"' ;; --user=*) user="${a#--user=}" ;; *) die "unbekannte Option $a" ;; esac done cat </dev/null 2>&1; then useradd --system --create-home --shell /bin/sh "\$U" 2>/dev/null || adduser -D -s /bin/sh "\$U" usermod -p '*' "\$U" 2>/dev/null || true # kein Passwort, aber nicht gesperrt (sonst lehnt sshd ab) fi # Leserechte auf Logs: journald (systemd-journal) und /var/log (adm) for g in systemd-journal adm; do getent group "\$g" >/dev/null && usermod -aG "\$g" "\$U" 2>/dev/null || true done H=\$(getent passwd "\$U" | cut -d: -f6) mkdir -p "\$H/.ssh" # restrict: kein PTY, kein Agent-/X11-/Port-Forwarding. # Mit --tunnel nur Reverse-Forwarding auf 127.0.0.1:1514/1515 (fuer agent_tunnel). KEY='$opts $pub' grep -qF '$pub' "\$H/.ssh/authorized_keys" 2>/dev/null || echo "\$KEY" >> "\$H/.ssh/authorized_keys" chown -R "\$U:" "\$H/.ssh"; chmod 700 "\$H/.ssh"; chmod 600 "\$H/.ssh/authorized_keys" EOF if [ "$sudo" = yes ]; then cat < /etc/sudoers.d/wazuh-pull chmod 440 /etc/sudoers.d/wazuh-pull visudo -cf /etc/sudoers.d/wazuh-pull EOF fi echo 'echo "Host vorbereitet: Benutzer $U ($(id $U))"' } cmd_up() { detect_runtime tune_kernel compose up -d enable_autostart sleep 5 # Portweiterleitung der Container steht erst nach dem Start check_firewall info "Gestartet ($ENGINE). Dashboard in 1-3 Minuten: https://$(uname -n):$(env_get DASHBOARD_PORT)" } CMD="${1:-init}" case "$CMD" in init) cmd_init ;; up) cmd_up ;; down) detect_runtime; compose down ;; ps) detect_runtime; compose ps ;; logs) shift; detect_runtime; compose logs -f --tail=100 "$@" ;; info) cmd_info ;; firewall) shift; ./firewall-check.sh "$@" ;; host-script) shift; cmd_host_script "$@" ;; keyscan) detect_runtime; compose run --rm --no-deps sshpull.collector keyscan ;; test) detect_runtime; compose run --rm --no-deps sshpull.collector test ;; apply) detect_runtime; compose up -d --force-recreate wazuh.manager sshpull.collector ;; passwords) echo "Dashboard: admin / $(env_get INDEXER_PASSWORD)" echo "Wazuh-API: wazuh-wui / $(env_get API_PASSWORD)" ;; *) sed -n '2,25p' "$0"; exit 2 ;; esac