Prometheus and Loki accept writes without authentication and were bound to 0.0.0.0; node-exporter likewise. That was fixed on CFGMON during the firewall work today and never came back to the repository, so this file still described three open ports and a firewall as the only thing in front of them. The consequence is worse than a stale comment. A pull would have reverted the binding and reopened all three, and nothing here or there would have said so. It did not happen only because git refused the pull over the local edit — the accident that saved it is not a control. Ports now match what actually runs: the private vSwitch address for the hosts that push, localhost for the host itself, and node-exporter on localhost alone.
261 lines
9.9 KiB
YAML
261 lines
9.9 KiB
YAML
services:
|
|
prometheus:
|
|
image: prom/prometheus:v3.14.0
|
|
container_name: prometheus
|
|
restart: unless-stopped
|
|
volumes:
|
|
# Verzeichnis- statt Einzeldatei-Mount: Docker haengt Einzeldatei-Mounts am
|
|
# Inode auf, und `git pull` ersetzt Dateien per Rename (neuer Inode) - der
|
|
# Container sah die Aenderung dann nie, waehrend SIGHUP brav Erfolg meldete.
|
|
# Verzeichnis-Mounts loesen bei jedem Zugriff ueber den Pfad auf.
|
|
# Config-Pfade bleiben unveraendert (--config.file zeigt weiter dorthin).
|
|
- ./prometheus:/etc/prometheus:ro
|
|
- prometheus_data:/prometheus
|
|
command:
|
|
- '--config.file=/etc/prometheus/prometheus.yml'
|
|
- '--storage.tsdb.path=/prometheus'
|
|
- '--storage.tsdb.retention.time=45d'
|
|
- '--storage.tsdb.retention.size=15GB'
|
|
# Remote-Write-Receiver: k3s-Cluster und Matrix-Server pushen hierher.
|
|
# ⚠️ Prometheus hat KEINE Authentisierung. Bis 2026-08-21 stand hier
|
|
# "9090:9090", also 0.0.0.0 - oeffentlich erreichbar, abgesichert allein
|
|
# durch die Hetzner-Firewall. Seither an Adressen gebunden: der private
|
|
# vSwitch fuer die pushenden Absender, localhost fuer den Host selbst.
|
|
# Wer das zurueckdreht, macht die Zeitreihen wieder oeffentlich lesbar
|
|
# UND beschreibbar.
|
|
- '--web.enable-remote-write-receiver'
|
|
ports:
|
|
- "10.0.0.3:9090:9090"
|
|
- "127.0.0.1:9090:9090"
|
|
networks:
|
|
- traefik
|
|
|
|
alertmanager:
|
|
image: prom/alertmanager:v0.34.0
|
|
container_name: alertmanager
|
|
restart: unless-stopped
|
|
volumes:
|
|
# Verzeichnis-Mount, gleicher Grund wie bei Prometheus. Die beiden .py aus
|
|
# diesem Ordner liegen dadurch mit unter /etc/alertmanager - ungenutzt und
|
|
# harmlos, alertmanager laedt ausschliesslich --config.file.
|
|
- ./alertmanager:/etc/alertmanager:ro
|
|
- alertmanager_data:/alertmanager
|
|
command:
|
|
- '--config.file=/etc/alertmanager/alertmanager.yml'
|
|
- '--storage.path=/alertmanager'
|
|
# bewusst kein oeffentlicher Port - nur Prometheus/Receiver im traefik-Netz
|
|
networks:
|
|
- traefik
|
|
|
|
# Alertmanager-Webhook -> Matrix (wartung-Raum), gleiche Machart wie
|
|
# maintenance-notify (gitops Issue #24). Secrets kommen aus .env.
|
|
matrix-alerts:
|
|
# 3.13-slim trug 4 CRITICAL (Debian-Basis, ohne Fix in der Fassung);
|
|
# 3.13-alpine gemessen am 2026-08-21: 0 CRITICAL, 2 HIGH. Alle drei
|
|
# Dienste hier laufen rein auf der stdlib, TLS gegen /etc/ssl/cert.pem
|
|
# im Image geprueft - kein Paket, das musl stoeren koennte.
|
|
image: python:3.13-alpine
|
|
container_name: matrix-alerts
|
|
restart: unless-stopped
|
|
environment:
|
|
- MATRIX_HOMESERVER=${MATRIX_ALERT_HOMESERVER}
|
|
- MATRIX_ROOM_ID=${MATRIX_ALERT_ROOM_ID}
|
|
- MATRIX_TOKEN=${MATRIX_ALERT_TOKEN}
|
|
# Raum-Routing (gitops#47): Alerts mit Label room=security -> Security-Raum
|
|
- MATRIX_ROOM_SECURITY=${MATRIX_RELEASE_ROOM_ID:-}
|
|
# Ohne das liegt der State unter /tmp im Writable Layer: der ueberlebt
|
|
# zwar ein "compose restart", aber kein "up -d", das den Container neu
|
|
# baut -- also genau jeden Deploy. Dann verlieren offene Alarme ihre
|
|
# Event-Zuordnung und loesen sich ueber den Fallback als separate
|
|
# Nachricht auf, statt die Firing-Nachricht abzuhaken.
|
|
- MATRIX_STATE_FILE=/state/matrix-alerts-state.json
|
|
volumes:
|
|
- ./alertmanager:/app:ro
|
|
- matrix_alerts_data:/state
|
|
command: python3 /app/matrix-alerts.py
|
|
networks:
|
|
- traefik
|
|
|
|
# Release-/Advisory-Watch (gitops#22): meldet neue Releases der Element-Stack-
|
|
# Upstreams in den Alerts-Raum (🚨 bei Security-Verdacht). Gleicher Bot/Raum
|
|
# wie matrix-alerts, eigener State (Erstlauf merkt nur, flutet nicht).
|
|
release-watch:
|
|
# 3.13-slim trug 4 CRITICAL (Debian-Basis, ohne Fix in der Fassung);
|
|
# 3.13-alpine gemessen am 2026-08-21: 0 CRITICAL, 2 HIGH. Alle drei
|
|
# Dienste hier laufen rein auf der stdlib, TLS gegen /etc/ssl/cert.pem
|
|
# im Image geprueft - kein Paket, das musl stoeren koennte.
|
|
image: python:3.13-alpine
|
|
container_name: release-watch
|
|
restart: unless-stopped
|
|
environment:
|
|
- MATRIX_HOMESERVER=${MATRIX_ALERT_HOMESERVER}
|
|
- MATRIX_ROOM_ID=${MATRIX_ALERT_ROOM_ID}
|
|
# CVE-/Release-Raum (gitops#47): leer lassen = Fallback Alerts-Raum
|
|
- MATRIX_RELEASE_ROOM_ID=${MATRIX_RELEASE_ROOM_ID:-}
|
|
- MATRIX_TOKEN=${MATRIX_ALERT_TOKEN}
|
|
volumes:
|
|
- ./alertmanager:/app:ro
|
|
- release_watch_data:/state
|
|
command: python3 /app/release-watch.py
|
|
networks:
|
|
- traefik
|
|
|
|
# CVE-Pipeline (gitops#47), Teil 1: Trivy scannt die real deployten Images
|
|
# (Ziele aus /targets/targets.txt - abgeleitet vom cve-exporter, NICHT von Hand
|
|
# pflegen: #0106 / ADR-0026)
|
|
cve-scan:
|
|
image: aquasec/trivy:0.74.0
|
|
container_name: cve-scan
|
|
restart: unless-stopped
|
|
entrypoint: ["/bin/sh", "/config/scan-loop.sh"]
|
|
volumes:
|
|
- ./cve:/config:ro
|
|
- cve_results:/results
|
|
- cve_trivy_cache:/root/.cache
|
|
# Ab Slice 3 die Quelle der Ziele; in Slice 1 nur eingehaengt, damit
|
|
# der Pfad existiert und beim Umschalten kein Rennen entsteht.
|
|
- cve_targets:/targets:ro
|
|
networks:
|
|
- traefik
|
|
|
|
# Teil 2: Reports -> Prometheus-Metriken (Schema + Pflichtfelder siehe gitops#47)
|
|
cve-exporter:
|
|
# 3.13-slim trug 4 CRITICAL (Debian-Basis, ohne Fix in der Fassung);
|
|
# 3.13-alpine gemessen am 2026-08-21: 0 CRITICAL, 2 HIGH. Alle drei
|
|
# Dienste hier laufen rein auf der stdlib, TLS gegen /etc/ssl/cert.pem
|
|
# im Image geprueft - kein Paket, das musl stoeren koennte.
|
|
image: python:3.13-alpine
|
|
container_name: cve-exporter
|
|
restart: unless-stopped
|
|
environment:
|
|
- RESULTS_DIR=/results
|
|
- STATE_FILE=/state/first-seen.json
|
|
# Soll-Menge der Scan-Ziele (#0106, ADR-0026): abgeleitet, nicht gepflegt.
|
|
# Der Exporter bildet sie, weil er fuer die Deckungsmetrik ohnehin BEIDE
|
|
# Mengen kennen muss - ein zweiter Ort waere eine zweite Wahrheit.
|
|
- TARGETS_FILE=/targets/targets.txt
|
|
- PROMETHEUS_URL=http://prometheus:9090
|
|
- REGISTRY_HOST=rohana.axion1337.de
|
|
# Reichweite (Entscheidung sorb 2026-08-21, Option C): Bestand plus die
|
|
# letzten drei Fassungen je Repo - so weit, wie ein Rollback zielt.
|
|
- REGISTRY_TAGS_JE_REPO=3
|
|
# Nicht je Scrape herleiten: bei 15 s waeren das ~21 000
|
|
# Registry-Anfragen am Tag; eine Runde kostet ~90.
|
|
- SOLL_INTERVALL_SEKUNDEN=3600
|
|
# Entschiedene CRITICAL-Befunde (#0051). Liegt im selben ./cve-Ordner,
|
|
# der hier als /app eingehaengt ist - ausdruecklich gesetzt, damit man
|
|
# die Datei findet, ohne den Vorgabewert im Quelltext zu suchen.
|
|
- ENTSCHEIDUNGEN_FILE=/app/entscheidungen.json
|
|
volumes:
|
|
- ./cve:/app:ro
|
|
- cve_results:/results:ro
|
|
- cve_exporter_state:/state
|
|
- cve_targets:/targets
|
|
command: python3 /app/cve-exporter.py
|
|
networks:
|
|
- traefik
|
|
|
|
loki:
|
|
image: grafana/loki:3.7.1
|
|
container_name: loki
|
|
restart: unless-stopped
|
|
volumes:
|
|
- ./loki:/etc/loki:ro
|
|
- loki_data:/loki
|
|
command: -config.file=/etc/loki/loki-config.yaml
|
|
# ⚠️ Externe Alloys pushen Logs hierher; Loki hat ebenfalls keine
|
|
# Authentisierung. Bis 2026-08-21 auf 0.0.0.0 gebunden, seither wie
|
|
# Prometheus: privater vSwitch fuer die Absender, localhost fuer den Host.
|
|
ports:
|
|
- "10.0.0.3:3100:3100"
|
|
- "127.0.0.1:3100:3100"
|
|
networks:
|
|
- traefik
|
|
|
|
alloy:
|
|
image: grafana/alloy:v1.16.0
|
|
container_name: alloy
|
|
restart: unless-stopped
|
|
volumes:
|
|
- ./alloy:/etc/alloy:ro
|
|
- /var/log:/var/log:ro
|
|
- /var/lib/docker/containers:/var/lib/docker/containers:ro
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
# Muss persistent sein, sonst ist die Positions-Datei nach jedem
|
|
# Recreate weg und Alloy liest alle Container-Logs von vorn. Loki
|
|
# weist die alten Eintraege dann ab (reject_old_samples, Default 7d).
|
|
- alloy_data:/var/lib/alloy
|
|
command: run --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
|
|
cap_add:
|
|
- DAC_READ_SEARCH
|
|
- NET_RAW
|
|
networks:
|
|
- traefik
|
|
depends_on:
|
|
- prometheus
|
|
- loki
|
|
|
|
grafana:
|
|
image: grafana/grafana:12.4.9
|
|
container_name: grafana
|
|
restart: unless-stopped
|
|
environment:
|
|
# Greift nur beim allerersten Start mit leerem Volume.
|
|
# Der Live-Admin ist davon unabhaengig (Passwortaenderungen in der UI).
|
|
- GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER}
|
|
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
|
|
volumes:
|
|
- grafana_data:/var/lib/grafana
|
|
- ./grafana/provisioning:/etc/grafana/provisioning:ro
|
|
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=traefik"
|
|
- "traefik.http.routers.grafana.rule=Host(`selendis.axion1337.de`)"
|
|
- "traefik.http.routers.grafana.entrypoints=websecure"
|
|
- "traefik.http.routers.grafana.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.grafana.loadbalancer.server.port=3000"
|
|
depends_on:
|
|
- prometheus
|
|
- loki
|
|
networks:
|
|
- traefik
|
|
|
|
node-exporter:
|
|
image: prom/node-exporter:v1.12.1
|
|
container_name: node-exporter
|
|
restart: unless-stopped
|
|
volumes:
|
|
- /proc:/host/proc:ro
|
|
- /sys:/host/sys:ro
|
|
- /:/rootfs:ro
|
|
command:
|
|
- '--path.procfs=/host/proc'
|
|
- '--path.sysfs=/host/sys'
|
|
- '--path.rootfs=/rootfs'
|
|
- '--web.listen-address=:9100'
|
|
- '--web.disable-exporter-metrics'
|
|
# Nur localhost: gescrapt wird aus demselben Host heraus, von aussen hat
|
|
# hier niemand etwas zu suchen. Bis 2026-08-21 stand auch das auf 0.0.0.0.
|
|
ports:
|
|
- "127.0.0.1:9100:9100"
|
|
networks:
|
|
- traefik
|
|
|
|
volumes:
|
|
prometheus_data:
|
|
alertmanager_data:
|
|
matrix_alerts_data:
|
|
release_watch_data:
|
|
cve_results:
|
|
cve_trivy_cache:
|
|
cve_exporter_state:
|
|
cve_targets:
|
|
grafana_data:
|
|
loki_data:
|
|
alloy_data:
|
|
|
|
networks:
|
|
traefik:
|
|
external: true
|