Docker Stack Traefik: Unterschied zwischen den Versionen

Aus Xinux Wiki
Zur Navigation springen Zur Suche springen
Zeile 1: Zeile 1:
Dieser Artikel beschreibt einen Docker-Compose-Stack mit '''Traefik''' als Reverse-Proxy und den beiden Webanwendungen '''Nextcloud''' und '''MediaWiki''', die jeweils eine '''eigene, getrennte''' MariaDB erhalten. Traefik erkennt die Container automatisch über Labels und terminiert TLS mit einem statischen Wildcard-Zertifikat.
+
Dieser Artikel baut auf dem [[Docker-Stack-Traefik|Docker-Stack mit Traefik]] auf und integriert ihn in das SIEM. Der Wazuh-Agent läuft dabei auf dem '''Docker-Host''', nicht in den Containern. Drei Telemetriequellen werden eingebunden: Docker-Engine-Events, die Anwendungslogs und Datei-Integritätsüberwachung auf den Volumes.
  
 
==Konzept==
 
==Konzept==
  
; Traefik als Reverse-Proxy
+
; Agent auf dem Host
* Im Gegensatz zu HAProxy werden die Backends nicht von Hand konfiguriert. Traefik liest die Docker-Labels der Container und erzeugt Router und Services dynamisch.
+
* Der Wazuh-Agent wird auf dem Docker-Host installiert. Über das <code>docker-listener</code>-Modul liest er die Events der Docker-Engine direkt vom Socket.
  
; Statisches Zertifikat
+
; Was im SIEM ankommt
* Im Lab gibt es kein ACME/Let's-Encrypt nach außen. Das Wildcard-Zertifikat liegt unter <code>/etc/ssl/own.crt</code> und <code>/etc/ssl/own.key</code> und wird per File-Provider als Default registriert.
+
* Container start, stop und die
 +
* Image-Pulls
 +
* <code>docker exec</code> in laufende Container (jemand klettert in den Container)
 +
* Netzwerk- und Volume-Events
  
; Pro App eine eigene Datenbank
+
; Drei Quellen
* Jede Anwendung bekommt einen eigenen MariaDB-Container in einem eigenen Backend-Netz. Es gibt '''keine''' gemeinsame Datenbank. Eine kompromittierte Anwendung kann damit nicht über eine geteilte DB an die Daten der anderen Anwendung gelangen.
+
* Engine-Events (docker-listener), Anwendungslogs (Traefik, Nextcloud, MediaWiki) und FIM auf den Daten-Volumes.
  
==Netz-Trennung==
+
==Voraussetzung: Python-Modul docker==
  
; proxy
+
Das <code>docker-listener</code>-Modul benötigt das Python-<code>docker</code>-SDK in der '''Wazuh-eigenen''' Python-Umgebung. Ohne dieses Modul startet der Listener still nicht.
* Verbindet Traefik mit den Webanwendungen.
 
  
; nextcloud-back
+
; SDK in die Wazuh-Python-Umgebung installieren
* Verbindet '''nur''' Nextcloud mit seiner Datenbank.
+
* <code>/var/ossec/framework/python/bin/pip3 install docker</code>
  
; mediawiki-back
+
; Installation prüfen
* Verbindet '''nur''' MediaWiki mit seiner Datenbank.
+
* <code>/var/ossec/framework/python/bin/pip3 show docker</code>
  
Die beiden DB-Container hängen jeweils nur an ihrem eigenen Backend-Netz und sind weder von außen noch von der jeweils anderen Anwendung erreichbar.
+
==Agent-Konfiguration (ossec.conf)==
  
==Verzeichnisstruktur==
+
Der folgende Block kommt in die <code>/var/ossec/etc/ossec.conf</code> des Agents auf dem Docker-Host.
  
<pre>
+
===docker-listener aktivieren===
docker-stack/
 
├── docker-compose.yml
 
└── traefik/
 
    └── dynamic/
 
        └── tls.yml
 
</pre>
 
  
==docker-compose.yml==
+
<syntaxhighlight lang="xml">
 +
<wodle name="docker-listener">
 +
  <interval>10m</interval>
 +
  <attempts>5</attempts>
 +
  <run_on_start>yes</run_on_start>
 +
  <disabled>no</disabled>
 +
</wodle>
 +
</syntaxhighlight>
 +
 
 +
; interval / attempts
 +
* Der Listener verbindet sich beim Start mit dem Docker-Socket. <code>attempts</code> regelt, wie oft er es bei Nichterreichbarkeit erneut versucht.
  
<syntaxhighlight lang="yaml">
+
; run_on_start
services:
+
* Startet das Modul direkt beim Hochfahren des Agents, nicht erst nach dem ersten Intervall.
  
  # Traefik - Reverse Proxy (statisches Wildcard-Cert)
+
===Anwendungslogs einsammeln===
  traefik:
 
    image: traefik:v3.3
 
    container_name: traefik
 
    restart: unless-stopped
 
    command:
 
      - "--providers.docker=true"
 
      - "--providers.docker.exposedbydefault=false"
 
      - "--providers.file.directory=/etc/traefik/dynamic"
 
      - "--providers.file.watch=true"
 
      - "--entrypoints.web.address=:80"
 
      - "--entrypoints.websecure.address=:443"
 
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
 
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
 
    ports:
 
      - "80:80"
 
      - "443:443"
 
    volumes:
 
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
 
      - "./traefik/dynamic:/etc/traefik/dynamic:ro"
 
      - "/etc/ssl/own.crt:/etc/ssl/own.crt:ro"
 
      - "/etc/ssl/own.key:/etc/ssl/own.key:ro"
 
    networks:
 
      - proxy
 
  
  # Nextcloud + eigene DB
+
Die Container loggen auf <code>stdout</code>. Bei json-file-Logtreiber liegen die Logs auf dem Host und können direkt als Datei eingelesen werden. Einfacher und stabiler ist es, die relevanten Anwendungslogs auf ein gemountetes Verzeichnis zu schreiben und dieses zu überwachen.
  nextcloud-db:
 
    image: mariadb:11
 
    container_name: nextcloud-db
 
    restart: unless-stopped
 
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
 
    environment:
 
      MARIADB_ROOT_PASSWORD: nc-rootpass
 
      MARIADB_DATABASE: nextcloud
 
      MARIADB_USER: nextcloud
 
      MARIADB_PASSWORD: nextcloudpass
 
      MARIADB_AUTO_UPGRADE: "1"
 
    volumes:
 
      - nextcloud_db:/var/lib/mysql
 
    networks:
 
      - nextcloud-back
 
  
  nextcloud:
+
<syntaxhighlight lang="xml">
    image: nextcloud:30-apache
+
<localfile>
    container_name: nextcloud
+
  <log_format>json</log_format>
    restart: unless-stopped
+
  <location>/var/lib/docker/containers/*/*-json.log</location>
    depends_on:
+
</localfile>
      - nextcloud-db
+
</syntaxhighlight>
    environment:
 
      MYSQL_HOST: nextcloud-db
 
      MYSQL_DATABASE: nextcloud
 
      MYSQL_USER: nextcloud
 
      MYSQL_PASSWORD: nextcloudpass
 
      NEXTCLOUD_ADMIN_USER: admin
 
      NEXTCLOUD_ADMIN_PASSWORD: adminpass
 
      NEXTCLOUD_TRUSTED_DOMAINS: cloud.it2XX.xinmen.de.int
 
      OVERWRITEPROTOCOL: https
 
    volumes:
 
      - nextcloud_data:/var/www/html
 
    networks:
 
      - proxy
 
      - nextcloud-back
 
    labels:
 
      - "traefik.enable=true"
 
      - "traefik.http.routers.nextcloud.rule=Host(`cloud.it2XX.xinmen.de.int`)"
 
      - "traefik.http.routers.nextcloud.entrypoints=websecure"
 
      - "traefik.http.routers.nextcloud.tls=true"
 
      - "traefik.http.services.nextcloud.loadbalancer.server.port=80"
 
      - "traefik.http.middlewares.nc-dav.redirectregex.regex=https://([^/]*)/.well-known/(card|cal)dav"
 
      - "traefik.http.middlewares.nc-dav.redirectregex.replacement=https://$${1}/remote.php/dav/"
 
      - "traefik.http.routers.nextcloud.middlewares=nc-dav"
 
  
  # MediaWiki + eigene DB
+
; json-Logtreiber
  mediawiki-db:
+
* Docker schreibt pro Container eine <code>*-json.log</code>. Wazuh kann diese als <code>json</code> einlesen und die Felder direkt auswerten.
    image: mariadb:11
 
    container_name: mediawiki-db
 
    restart: unless-stopped
 
    environment:
 
      MARIADB_ROOT_PASSWORD: mw-rootpass
 
      MARIADB_DATABASE: mediawiki
 
      MARIADB_USER: mediawiki
 
      MARIADB_PASSWORD: mediawikipass
 
      MARIADB_AUTO_UPGRADE: "1"
 
    volumes:
 
      - mediawiki_db:/var/lib/mysql
 
    networks:
 
      - mediawiki-back
 
  
  mediawiki:
+
==FIM auf den Volumes==
    image: mediawiki:1.43
 
    container_name: mediawiki
 
    restart: unless-stopped
 
    depends_on:
 
      - mediawiki-db
 
    volumes:
 
      - mediawiki_data:/var/www/html/images
 
      # LocalSettings.php nach dem Setup einhaengen:
 
      # - ./LocalSettings.php:/var/www/html/LocalSettings.php:ro
 
    networks:
 
      - proxy
 
      - mediawiki-back
 
    labels:
 
      - "traefik.enable=true"
 
      - "traefik.http.routers.mediawiki.rule=Host(`wiki.it2XX.xinmen.de.int`)"
 
      - "traefik.http.routers.mediawiki.entrypoints=websecure"
 
      - "traefik.http.routers.mediawiki.tls=true"
 
      - "traefik.http.services.mediawiki.loadbalancer.server.port=80"
 
  
networks:
+
Datei-Integritätsüberwachung auf den Daten-Volumes der Anwendungen. Veränderungen an hochgeladenen Dateien oder an der Wiki-Konfiguration lösen ein FIM-Event aus (Rule 554 bei neuen Dateien).
  proxy:
 
  nextcloud-back:
 
  mediawiki-back:
 
  
volumes:
+
<syntaxhighlight lang="xml">
  nextcloud_db:
+
<syscheck>
   nextcloud_data:
+
   <directories check_all="yes" realtime="yes">/var/lib/docker/volumes/docker-stack_nextcloud_data/_data/data</directories>
   mediawiki_db:
+
   <directories check_all="yes" realtime="yes">/var/lib/docker/volumes/docker-stack_mediawiki_data/_data</directories>
  mediawiki_data:
+
</syscheck>
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==Erklärung der Bausteine==
+
; realtime
 +
* <code>realtime="yes"</code> meldet Änderungen sofort über inotify, statt erst beim nächsten Scan-Durchlauf.
  
===Traefik===
+
; Volume-Pfade
 +
* Die genauen Pfade hängen vom Compose-Projektnamen ab (Präfix des Verzeichnisses). Mit dem folgenden Befehl ermitteln.
 +
* <code>docker volume inspect docker-stack_nextcloud_data</code>
  
; Docker-Provider mit Opt-in
+
==Kompletter ossec.conf-Block==
* <code>exposedbydefault=false</code> sorgt dafür, dass nur Container mit <code>traefik.enable=true</code> veröffentlicht werden. Alles andere bleibt unsichtbar.
 
  
; File-Provider für TLS
+
Alle drei Bausteine (docker-listener, Anwendungslogs, FIM) am Stück zum Einfügen in die <code>/var/ossec/etc/ossec.conf</code> des Agents auf dem Docker-Host. Die Volume-Pfade ggf. an den eigenen Compose-Projektnamen anpassen.
* Das statische Zertifikat wird nicht über Labels, sondern über eine Datei eingebunden (siehe <code>tls.yml</code>). <code>watch=true</code> lädt Änderungen ohne Neustart.
 
  
; HTTP→HTTPS-Redirect global
+
<syntaxhighlight lang="xml">
* Die beiden <code>redirections</code>-Zeilen leiten den gesamten Port-80-Verkehr auf HTTPS um. Das muss kein Container selbst regeln.
+
<wodle name="docker-listener">
 +
  <interval>10m</interval>
 +
  <attempts>5</attempts>
 +
  <run_on_start>yes</run_on_start>
 +
  <disabled>no</disabled>
 +
</wodle>
  
; Docker-Socket read-only
+
<localfile>
* Traefik liest die Container-Labels über den Docker-Socket. Der wird '''read-only''' gemountet.
+
  <log_format>json</log_format>
 +
  <location>/var/lib/docker/containers/*/*-json.log</location>
 +
</localfile>
  
===Datenbanken===
+
<syscheck>
 +
  <directories check_all="yes" realtime="yes">/var/lib/docker/volumes/docker-stack_nextcloud_data/_data/data</directories>
 +
  <directories check_all="yes" realtime="yes">/var/lib/docker/volumes/docker-stack_mediawiki_data/_data</directories>
 +
</syscheck>
 +
</syntaxhighlight>
  
; Anlage über Umgebungsvariablen
+
==Agent neu starten==
* Jeder MariaDB-Container legt Datenbank und Benutzer beim ersten Start selbst an, gesteuert über <code>MARIADB_DATABASE</code>, <code>MARIADB_USER</code> und <code>MARIADB_PASSWORD</code>. Ein separates Init-Skript ist nicht nötig.
 
  
; Getrennte Volumes
+
<syntaxhighlight lang="bash">
* <code>nextcloud_db</code> und <code>mediawiki_db</code> sind voneinander unabhängige Volumes. Jede DB hat ihren eigenen Datenbestand.
+
systemctl restart wazuh-agent
 +
</syntaxhighlight>
  
==Traefik Dynamic Config (tls.yml)==
+
==Funktionsprüfung==
  
Pfad im Container: <code>/etc/traefik/dynamic/tls.yml</code>
+
===Modul-Status im Agent-Log===
 
 
<syntaxhighlight lang="yaml">
 
tls:
 
  certificates:
 
    - certFile: /etc/ssl/own.crt
 
      keyFile: /etc/ssl/own.key
 
  stores:
 
    default:
 
      defaultCertificate:
 
        certFile: /etc/ssl/own.crt
 
        keyFile: /etc/ssl/own.key
 
</syntaxhighlight>
 
  
; Default-Zertifikat
+
; ossec.log auf dem Agent prüfen
* Durch das <code>default</code>-Store reicht an jedem Router ein einfaches <code>tls=true</code>. Traefik greift automatisch auf dieses Zertifikat zurück, ein eigener Cert-Verweis pro Router entfällt.
+
* <code>tail -f /var/ossec/logs/ossec.log</code>
  
==Inbetriebnahme==
+
; Auf docker-listener-Meldungen achten
 +
* <code>grep docker-listener /var/ossec/logs/ossec.log</code>
  
; Zertifikat ablegen
+
Erscheint hier eine Meldung über ein fehlendes Python-Modul, wurde das <code>docker</code>-SDK nicht in die Wazuh-Python-Umgebung installiert (siehe oben).
* Das Wildcard-Zertifikat zur Domain mit dem vorhandenen Skript ziehen, sodass <code>/etc/ssl/own.crt</code> und <code>/etc/ssl/own.key</code> vorhanden sind.
 
* <code>./get-cert.sh</code>
 
  
; Stack starten
+
===Event auslösen===
* <code>docker compose up -d</code>
 
  
; Status prüfen
+
; Test-Container starten und wieder entfernen
* <code>docker compose ps</code>
+
* <code>docker run --rm hello-world</code>
  
; Logs verfolgen
+
; In einen laufenden Container klettern
* <code>docker compose logs -f traefik</code>
+
* <code>docker exec -it nextcloud sh</code>
  
==DNS==
+
===Im Manager prüfen===
  
Die beiden Hostnamen müssen auf den Docker-Host zeigen:
+
; archives.json mitlesen (alle Events)
 +
* <code>tail -f /var/ossec/logs/archives/archives.json</code>
  
; Nextcloud
+
; alerts.json mitlesen (nur Alerts)
* <code>cloud.it2XX.xinmen.de.int</code>
+
* <code>tail -f /var/ossec/logs/alerts/alerts.json</code>
  
; MediaWiki
+
Die Docker-Events erscheinen mit dem Decoder <code>docker</code>. Das <code>docker exec</code> ist didaktisch der interessanteste Fall: Es zeigt, dass ein Eindringen in einen laufenden Container im SIEM sichtbar wird.
* <code>wiki.it2XX.xinmen.de.int</code>
 
  
==MediaWiki-Setup abschließen==
+
==Hinweis zum Datenfluss==
  
; Erstaufruf im Browser
+
; Engine-Events vs. Anwendungslogs
* MediaWiki startet ohne <code>LocalSettings.php</code> mit dem Web-Installer. Als Datenbank-Host <code>mediawiki-db</code>, Datenbank <code>mediawiki</code>, Benutzer <code>mediawiki</code> angeben.
+
* Der docker-listener meldet, '''dass''' ein Container gestartet oder betreten wurde – nicht, was innerhalb der Anwendung passiert. Für Angriffe auf die Webanwendung (z.B. gegen MediaWiki oder Nextcloud) liefern die Anwendungslogs und FIM die Telemetrie.
  
; LocalSettings.php einhängen
+
; Zusammenspiel
* Nach dem Setup wird die generierte <code>LocalSettings.php</code> heruntergeladen, neben das Compose-File gelegt und der auskommentierte Volume-Mount im <code>mediawiki</code>-Service aktiviert. Danach Container neu starten.
+
* Erst die Kombination macht das Bild vollständig: Engine-Ebene (Container-Lifecycle), Anwendungs-Ebene (Logs) und Datei-Ebene (FIM).
* <code>docker compose up -d mediawiki</code>
 

Version vom 27. Juni 2026, 16:21 Uhr

Dieser Artikel baut auf dem Docker-Stack mit Traefik auf und integriert ihn in das SIEM. Der Wazuh-Agent läuft dabei auf dem Docker-Host, nicht in den Containern. Drei Telemetriequellen werden eingebunden: Docker-Engine-Events, die Anwendungslogs und Datei-Integritätsüberwachung auf den Volumes.

Konzept

Agent auf dem Host
  • Der Wazuh-Agent wird auf dem Docker-Host installiert. Über das docker-listener-Modul liest er die Events der Docker-Engine direkt vom Socket.
Was im SIEM ankommt
  • Container start, stop und die
  • Image-Pulls
  • docker exec in laufende Container (jemand klettert in den Container)
  • Netzwerk- und Volume-Events
Drei Quellen
  • Engine-Events (docker-listener), Anwendungslogs (Traefik, Nextcloud, MediaWiki) und FIM auf den Daten-Volumes.

Voraussetzung: Python-Modul docker

Das docker-listener-Modul benötigt das Python-docker-SDK in der Wazuh-eigenen Python-Umgebung. Ohne dieses Modul startet der Listener still nicht.

SDK in die Wazuh-Python-Umgebung installieren
  • /var/ossec/framework/python/bin/pip3 install docker
Installation prüfen
  • /var/ossec/framework/python/bin/pip3 show docker

Agent-Konfiguration (ossec.conf)

Der folgende Block kommt in die /var/ossec/etc/ossec.conf des Agents auf dem Docker-Host.

docker-listener aktivieren

<wodle name="docker-listener">
  <interval>10m</interval>
  <attempts>5</attempts>
  <run_on_start>yes</run_on_start>
  <disabled>no</disabled>
</wodle>
interval / attempts
  • Der Listener verbindet sich beim Start mit dem Docker-Socket. attempts regelt, wie oft er es bei Nichterreichbarkeit erneut versucht.
run_on_start
  • Startet das Modul direkt beim Hochfahren des Agents, nicht erst nach dem ersten Intervall.

Anwendungslogs einsammeln

Die Container loggen auf stdout. Bei json-file-Logtreiber liegen die Logs auf dem Host und können direkt als Datei eingelesen werden. Einfacher und stabiler ist es, die relevanten Anwendungslogs auf ein gemountetes Verzeichnis zu schreiben und dieses zu überwachen.

<localfile>
  <log_format>json</log_format>
  <location>/var/lib/docker/containers/*/*-json.log</location>
</localfile>
json-Logtreiber
  • Docker schreibt pro Container eine *-json.log. Wazuh kann diese als json einlesen und die Felder direkt auswerten.

FIM auf den Volumes

Datei-Integritätsüberwachung auf den Daten-Volumes der Anwendungen. Veränderungen an hochgeladenen Dateien oder an der Wiki-Konfiguration lösen ein FIM-Event aus (Rule 554 bei neuen Dateien).

<syscheck>
  <directories check_all="yes" realtime="yes">/var/lib/docker/volumes/docker-stack_nextcloud_data/_data/data</directories>
  <directories check_all="yes" realtime="yes">/var/lib/docker/volumes/docker-stack_mediawiki_data/_data</directories>
</syscheck>
realtime
  • realtime="yes" meldet Änderungen sofort über inotify, statt erst beim nächsten Scan-Durchlauf.
Volume-Pfade
  • Die genauen Pfade hängen vom Compose-Projektnamen ab (Präfix des Verzeichnisses). Mit dem folgenden Befehl ermitteln.
  • docker volume inspect docker-stack_nextcloud_data

Kompletter ossec.conf-Block

Alle drei Bausteine (docker-listener, Anwendungslogs, FIM) am Stück zum Einfügen in die /var/ossec/etc/ossec.conf des Agents auf dem Docker-Host. Die Volume-Pfade ggf. an den eigenen Compose-Projektnamen anpassen.

<wodle name="docker-listener">
  <interval>10m</interval>
  <attempts>5</attempts>
  <run_on_start>yes</run_on_start>
  <disabled>no</disabled>
</wodle>

<localfile>
  <log_format>json</log_format>
  <location>/var/lib/docker/containers/*/*-json.log</location>
</localfile>

<syscheck>
  <directories check_all="yes" realtime="yes">/var/lib/docker/volumes/docker-stack_nextcloud_data/_data/data</directories>
  <directories check_all="yes" realtime="yes">/var/lib/docker/volumes/docker-stack_mediawiki_data/_data</directories>
</syscheck>

Agent neu starten

systemctl restart wazuh-agent

Funktionsprüfung

Modul-Status im Agent-Log

ossec.log auf dem Agent prüfen
  • tail -f /var/ossec/logs/ossec.log
Auf docker-listener-Meldungen achten
  • grep docker-listener /var/ossec/logs/ossec.log

Erscheint hier eine Meldung über ein fehlendes Python-Modul, wurde das docker-SDK nicht in die Wazuh-Python-Umgebung installiert (siehe oben).

Event auslösen

Test-Container starten und wieder entfernen
  • docker run --rm hello-world
In einen laufenden Container klettern
  • docker exec -it nextcloud sh

Im Manager prüfen

archives.json mitlesen (alle Events)
  • tail -f /var/ossec/logs/archives/archives.json
alerts.json mitlesen (nur Alerts)
  • tail -f /var/ossec/logs/alerts/alerts.json

Die Docker-Events erscheinen mit dem Decoder docker. Das docker exec ist didaktisch der interessanteste Fall: Es zeigt, dass ein Eindringen in einen laufenden Container im SIEM sichtbar wird.

Hinweis zum Datenfluss

Engine-Events vs. Anwendungslogs
  • Der docker-listener meldet, dass ein Container gestartet oder betreten wurde – nicht, was innerhalb der Anwendung passiert. Für Angriffe auf die Webanwendung (z.B. gegen MediaWiki oder Nextcloud) liefern die Anwendungslogs und FIM die Telemetrie.
Zusammenspiel
  • Erst die Kombination macht das Bild vollständig: Engine-Ebene (Container-Lifecycle), Anwendungs-Ebene (Logs) und Datei-Ebene (FIM).