espo-deploy/rmm-tasks/ESPO-02-Docker.sh
Gordon Zimm dcd99c3ba9 Zustandsriegel: die drei Einrichtungsskripte laufen nur bei espo_zustand = NEU
Statt lokaler Merkmale steuert das Custom Field espo_zustand zentral, was laufen
darf. Jedes der drei Skripte nimmt den Wert als Argument und bricht ohne
Aenderung ab, wenn er nicht NEU ist.

In ESPO-03 bleibt die Pruefung auf eine vorhandene Instanz als Rueckfall
erhalten: Der Zustand im RMM ist von Hand aenderbar, eine laufende Instanz ist
es nicht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 15:10:15 +02:00

106 lines
4.8 KiB
Bash
Executable File

#!/bin/bash
# ESPO-02-Docker - installiert Docker in gepinnter Version, setzt die Daemon-Grundlagen und
# beschraenkt den Zugang zu veroeffentlichten Container-Ports auf das eigene Subnetz.
# Aufruf: bash ESPO-02-Docker.sh "<Zustand>" (Zustand = Wert des Custom Field espo_zustand)
# Laeuft als root ueber einen RMM-Task, idempotent.
set -u
ZUSTAND="${1:-}"
FEHLER=0
# 0. Zustandsriegel: Die Einrichtung laeuft nur an einer Instanz, die im RMM als NEU gefuehrt wird.
# Das Custom Field espo_zustand steuert damit zentral, was laufen darf.
if [ "$ZUSTAND" != "NEU" ]; then
echo "FEHLER: espo_zustand ist '${ZUSTAND:-leer}', erwartet NEU - Abbruch ohne Aenderung"
echo "ESPO02_ERGEBNIS: FEHLER"; exit 1
fi
# Gepinnte Staende - hier wird der Flottenstand gesetzt, nicht auf der VM entschieden.
DOCKER_VERSION="5:29.8.1-1~ubuntu.24.04~noble"
CONTAINERD_VERSION="2.3.5-1~ubuntu.24.04~noble"
COMPOSE_VERSION="5.5.1-1~ubuntu.24.04~noble"
BUILDX_VERSION="0.37.1-1~ubuntu.24.04~noble"
# 1. Paketquelle von Docker einrichten (Schluessel und deb822-Quelle), falls noch nicht vorhanden.
install -m 0755 -d /etc/apt/keyrings
if [ ! -s /etc/apt/keyrings/docker.asc ]; then
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc || { echo "FEHLER: Schluessel nicht ladbar"; exit 1; }
chmod a+r /etc/apt/keyrings/docker.asc
fi
cat > /etc/apt/sources.list.d/docker.sources <<CONF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
CONF
apt-get update -qq || { echo "FEHLER: apt-get update"; exit 1; }
# 2. Genau diese Versionen installieren und auf Hold setzen, damit nichts unbemerkt nachzieht.
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
docker-ce="$DOCKER_VERSION" docker-ce-cli="$DOCKER_VERSION" containerd.io="$CONTAINERD_VERSION" \
docker-compose-plugin="$COMPOSE_VERSION" docker-buildx-plugin="$BUILDX_VERSION" || { echo "FEHLER: Installation"; exit 1; }
apt-mark hold docker-ce docker-ce-cli containerd.io docker-compose-plugin docker-buildx-plugin >/dev/null
# 3. Daemon-Grundlagen: Logrotation, weil der Standardtreiber nicht rotiert.
# Keine abweichende Bindeadresse: die Ports muessen fuer den Reverse Proxy erreichbar sein,
# die Beschraenkung uebernimmt Schritt 4.
install -d -m 0755 /etc/docker
cat > /etc/docker/daemon.json <<'CONF'
{
"log-driver": "local",
"log-opts": { "max-size": "20m", "max-file": "5" },
"live-restore": true
}
CONF
systemctl enable --now docker >/dev/null 2>&1
systemctl restart docker || { echo "FEHLER: docker startet nicht"; exit 1; }
# 4. Firewall: Docker setzt seine Weiterleitungsregeln vor die INPUT-Kette und umgeht damit ufw.
# DOCKER-USER ist die dafuer vorgesehene Stelle. Das Subnetz ermittelt die VM selbst,
# es ist also kein kundenspezifischer Wert noetig.
command -v iptables >/dev/null || { echo "FEHLER: iptables fehlt"; exit 1; }
cat > /usr/local/sbin/espo-docker-firewall <<'CONF'
#!/bin/bash
# Laesst an veroeffentlichte Container-Ports nur das eigene Subnetz heran, verwirft den Rest.
set -u
IFACE="$(ip -4 route show default | awk '{print $5; exit}')"
SUBNETZ="$(ip -4 route show dev "$IFACE" proto kernel scope link | awk '{print $1; exit}')"
[ -n "$IFACE" ] && [ -n "$SUBNETZ" ] || { echo "FEHLER: Schnittstelle oder Subnetz nicht ermittelbar"; exit 1; }
iptables -F DOCKER-USER
iptables -A DOCKER-USER -i "$IFACE" -m conntrack --ctstate RELATED,ESTABLISHED -j RETURN
iptables -A DOCKER-USER -i "$IFACE" -s "$SUBNETZ" -j RETURN
iptables -A DOCKER-USER -i "$IFACE" -j DROP
iptables -A DOCKER-USER -j RETURN
echo "DOCKER-USER: nur $SUBNETZ ueber $IFACE"
CONF
chmod 0755 /usr/local/sbin/espo-docker-firewall
cat > /etc/systemd/system/espo-docker-firewall.service <<'CONF'
[Unit]
Description=Zugang zu veroeffentlichten Docker-Ports auf das eigene Subnetz beschraenken
After=docker.service
Requires=docker.service
PartOf=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/espo-docker-firewall
[Install]
WantedBy=multi-user.target
CONF
systemctl daemon-reload
systemctl enable espo-docker-firewall.service >/dev/null 2>&1
systemctl restart espo-docker-firewall.service || { echo "FEHLER: Firewallregeln nicht gesetzt"; FEHLER=1; }
# 5. Ergebnis zeigen und pruefen.
docker --version
docker compose version
docker info --format 'Logtreiber: {{.LoggingDriver}} Live-Restore: {{.LiveRestoreEnabled}}'
iptables -S DOCKER-USER
[ "$(docker info --format '{{.LoggingDriver}}')" = "local" ] || { echo "FEHLER: Logtreiber nicht gesetzt"; FEHLER=1; }
iptables -S DOCKER-USER | grep -q -- '-j DROP' || { echo "FEHLER: DOCKER-USER ohne DROP-Regel"; FEHLER=1; }
if [ "$FEHLER" -eq 0 ]; then echo "ESPO02_ERGEBNIS: OK"; exit 0; else echo "ESPO02_ERGEBNIS: FEHLER"; exit 1; fi