- firewall-check.sh (auch ./setup.sh firewall): prueft Listener, Container- Portweiterleitung (DNAT), ip_forward, firewalld, ufw/ufw-docker, iptables (INPUT, DOCKER-USER), natives nftables, CSF, Shorewall und lokalen HTTPS-Test - Legt fehlende Regeln an (Rueckfrage, --yes ohne, --check nur pruefen); Podman: podman network reload --all bei fehlender Weiterleitung - Erkennt "TCP offen, aber kein HTTPS" (conmon haelt Port nur reserviert) - setup.sh: kein firewall-cmd --reload mehr (loescht ggf. Container-Regeln), up ruft stattdessen den Firewall-Check auf - README: Abschnitt Firewall-Check Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
361 lines
15 KiB
Bash
Executable File
361 lines
15 KiB
Bash
Executable File
#!/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' </dev/urandom | head -c 24)Aa1."
|
|
}
|
|
|
|
set_env() { # set_env KEY VALUE
|
|
if grep -q "^$1=" .env; then sed -i "s|^$1=.*|$1=$2|" .env; else echo "$1=$2" >> .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 <<EOF
|
|
---
|
|
# Automatisch erzeugt von setup.sh. Passwoerter stehen in .env.
|
|
_meta:
|
|
type: "internalusers"
|
|
config_version: 2
|
|
|
|
admin:
|
|
hash: "$h_admin"
|
|
reserved: true
|
|
backend_roles:
|
|
- "admin"
|
|
description: "Admin user"
|
|
|
|
kibanaserver:
|
|
hash: "$h_kibana"
|
|
reserved: true
|
|
description: "Dashboard server user"
|
|
EOF
|
|
chmod 644 config/wazuh_indexer/internal_users.yml
|
|
fi
|
|
|
|
# --- TLS-Zertifikate ---------------------------------------------------
|
|
if [ ! -f config/wazuh_indexer_ssl_certs/root-ca.pem ]; then
|
|
info "Erzeuge TLS-Zertifikate"
|
|
mkdir -p config/wazuh_indexer_ssl_certs
|
|
engine run --rm "${PROXY_ARGS[@]}" \
|
|
-e CERT_TOOL_VERSION="${version%.*}" \
|
|
-v "$PWD/config/wazuh_indexer_ssl_certs/:/certificates/:z" \
|
|
-v "$PWD/config/certs.yml:/config/certs.yml:ro,z" \
|
|
docker.io/wazuh/wazuh-certs-generator:0.0.4
|
|
[ -f config/wazuh_indexer_ssl_certs/root-ca.pem ] || die "Zertifikate wurden nicht erzeugt"
|
|
fi
|
|
|
|
# --- SSH-Schluessel ----------------------------------------------------
|
|
if [ ! -f ssh/id_ed25519 ]; then
|
|
info "Erzeuge SSH-Schluessel ssh/id_ed25519"
|
|
ssh-keygen -q -t ed25519 -N "" -C "wazuh-sshpull@$(uname -n)" -f ssh/id_ed25519
|
|
fi
|
|
touch ssh/known_hosts
|
|
chmod 600 ssh/id_ed25519
|
|
|
|
[ -f hosts.yml ] || { cp hosts.yml.example hosts.yml; warn "hosts.yml aus Beispiel angelegt - bitte anpassen!"; }
|
|
|
|
info "Baue Images"
|
|
compose build
|
|
|
|
cat <<EOF
|
|
|
|
Fertig. Naechste Schritte:
|
|
1. hosts.yml anpassen
|
|
2. Zielhosts vorbereiten: ./setup.sh host-script | ssh admin@HOST sudo sh
|
|
3. Host-Keys holen: ./setup.sh keyscan (Fingerprints pruefen!)
|
|
4. Zugriff testen: ./setup.sh test
|
|
5. Starten: ./setup.sh up
|
|
6. Dashboard: https://<dieser-server>:$(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 <<EOF
|
|
#!/bin/sh
|
|
# Vorbereitung eines Hosts fuer Wazuh-SSH-Pull (als root ausfuehren)
|
|
set -e
|
|
U='$user'
|
|
if ! id "\$U" >/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 <<EOF
|
|
echo "\$U ALL=(root) NOPASSWD: ALL" > /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
|