Traefik Crowdsec Stack einrichten
Der traefik-crowdsec-stack soll einen sicheren Betrieb von Docker Services ermöglichen. Dabei wird als Reverse-Proxy und Load-Balancer die derzeit aktuellste Version 3.x.x von Traefik eingesetzt. Die Kommunikation der von Traefik gerouteten Services wird mit Crowdsec in eigenen Netzwerken abgesichert. Schließlich wird der Stack um die von Außen erreichbaren Dienste "Ralfs Blogs", "Bines Blog", "Bücherregal" und "Gitea-Server" erweitert. Alle hierzu benötigten Docker Services können bequem über eine einzelne Docker Compose Datei gesteuert werden.
Die Basis für die hier verwendeten Texte und den Code bildet der Artikel Traefik V3 Installation, Konfiguration und CrowdSec-Security von
Einrichten der grundlegenden Stack-Struktur
Bevor es richtig losgeht noch ein wichtiger Hinweis: Alle Shell-Befehle sind mit root Privilegien auszuführen. In den Skripten wird kein vorangestelltes sudo verwendet. Das Skript selbst ist mit sudo aufzurufen.
Um den Stack anzulegen, wird das Hauptverzeichnis traefik-crowdsec-stack unter /opt/containers/ erstellt und in dieses Verzeichnis gewechselt. Danach werden die erforderlichen Unterverzeichnisse und Dateien erzeugt, um den Stack mit crowdsec, socket-proxy und traefik zu konfigurieren. Schließlich werden noch die korrekten Berechtigungen für sensible Zertifikatsdateien gesetzt.
Anschließend werden die in der Struktur angelegten Konfigurations- und Environmentdateien mit Inhalten gefüllt. Dabei werden Parameter wie Passwörter oder API-Schlüssel mit einem in doppelt geschweiften Klammern versehenem Kommentar versehen. Diese Parameter sind im Nachgang von Hand anzupassen.
Um den Stack vorzubereiten, kann das nachfolgende Skript ausgeführt werden (zum Aufklappen auf den Namen klicken):
create_stack.sh
#!/bin/bash
#
# ensure that user has root privileges
#
if [ $(id -u) -ne 0 ]
then echo Please run this script as root or using sudo!
exit
fi
#
# create traefic-crowdsec-stack directory structure
#
mkdir -p /opt/containers/traefik-crowdsec-stack && cd /opt/containers/traefik-crowdsec-stack
mkdir -p compose data/{crowdsec/{config,data},socket-proxy,traefik/{certs,dynamic_conf},traefik-crowdsec-bouncer,service-blog/{bine,ralf},service-bookstack/{app,db},service-gitea}
#
# Create some files and change permissions of cert related files
#
touch .env docker-compose.yml \
compose/{crowdsec.yml,networks.yml,socket-proxy.yml,traefik-crowdsec-bouncer.yml,traefik.yml} \
data/{crowdsec/.env,socket-proxy/.env,traefik/{.env,.htpasswd,traefik.yml,certs/{acme_letsencrypt.json,tls_letsencrypt.json},dynamic_conf/{http.middlewares.default.yml,http.middlewares.default-security-headers.yml,http.middlewares.gzip.yml,http.middlewares.traefik-bouncer.yml,http.middlewares.traefik-dashboard-auth.yml,tls.yml}},traefik-crowdsec-bouncer/.env}
chmod 600 data/traefik/certs/{acme_letsencrypt.json,tls_letsencrypt.json}
#
# Fill master dotenv file
#
cat << 'EOF' > /opt/containers/traefik-crowdsec-stack/.env
ABSOLUTE_PATH=${PWD}
TZ=Europe/Berlin
SERVICES_TRAEFIK_LABELS_TRAEFIK_HOST=HOST(`traefik.rastef.de`)
EOF
#
# Fill master compose file
#
echo "include:
- compose/service-blog-bine.yml
- compose/service-blog-ralf.yml
- compose/service-bookstack.yml
- compose/service-gitea.yml
- compose/crowdsec.yml
- compose/socket-proxy.yml
- compose/traefik-crowdsec-bouncer.yml
- compose/traefik.yml
- compose/networks.yml" >> /opt/containers/traefik-crowdsec-stack/docker-compose.yml
#
# Fill Crowdsec compose file
#
cat <<'EOF' > /opt/containers/traefik-crowdsec-stack/compose/crowdsec.yml
services:
crowdsec:
container_name: ${SERVICES_CROWDSEC_CONTAINER_NAME:-crowdsec}
env_file: ${ABSOLUTE_PATH}/data/crowdsec/.env
hostname: ${SERVICES_CROWDSEC_HOSTNAME:-crowdsec}
healthcheck:
test: ["CMD", "cscli", "version"]
timeout: 2s
interval: 20s
retries: 5
start_period: 10s
image: ${SERVICES_CROWDSEC_IMAGE:-crowdsecurity/crowdsec}:${SERVICES_CROWDSEC_IMAGE_VERSION:-latest}
networks:
crowdsec:
ipv4_address: ${SERVICES_CROWDSEC_NETWORKS_CROWDSEC_IPV4:-172.31.127.254}
ipv6_address: ${SERVICES_CROWDSEC_NETWORKS_CROWDSEC_IPV6:-fd00:1:be:a:7001:0:3e:6fff}
socket_proxy:
ipv4_address: ${SERVICES_TRAEFIK_NETWORKS_SOCKET_PROXY_IPV4:-172.31.255.252}
ipv6_address: ${SERVICES_TRAEFIK_NETWORKS_SOCKET_PROXY_IPV6:-fd00:1:be:a:7001:0:3e:8ffd}
restart: unless-stopped
security_opt:
- no-new-privileges=true
volumes:
- ${ABSOLUTE_PATH}/data/crowdsec/config:/etc/crowdsec
- ${ABSOLUTE_PATH}/data/crowdsec/data:/var/lib/crowdsec/data
- /var/log/auth.log:/var/log/auth.log:ro
- /var/log/traefik:/var/log/traefik:ro
EOF
#
# Fill Socket-Proxy compose file
#
cat <<'EOF' > /opt/containers/traefik-crowdsec-stack/compose/socket-proxy.yml
services:
socket-proxy:
container_name: ${SERVICES_SOCKET_PROXY_CONTAINER_NAME:-socket-proxy}
env_file: ${ABSOLUTE_PATH}/data/socket-proxy/.env
hostname: ${SERVICES_SOCKET_PROXY_HOSTNAME:-socket-proxy}
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:2375/_ping || exit 1"]
timeout: 3s
interval: 20s
retries: 3
start_period: 10s
image: ${SERVICES_SOCKET_PROXY_IMAGE:-lscr.io/linuxserver/socket-proxy}:${SERVICES_SOCKET_PROXY_IMAGE_VERSION:-latest}
networks:
socket_proxy:
ipv4_address: ${SERVICES_SOCKET_PROXY_NETWORKS_SOCKET_PROXY_IPV4:-172.31.255.254}
ipv6_address: ${SERVICES_SOCKET_PROXY_NETWORKS_SOCKET_PROXY_IPV6:-fd00:1:be:a:7001:0:3e:8fff}
read_only: true
restart: unless-stopped
tmpfs:
- /run
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
EOF
#
# Fill Traefik Crowdsec Bouncer compose file
#
cat << 'EOF' > /opt/containers/traefik-crowdsec-stack/compose/traefik-crowdsec-bouncer.yml
services:
traefik_crowdsec_bouncer:
container_name: ${SERVICES_TRAEFIK_CROWDSEC_BOUNCER_CONTAINER_NAME:-traefik_crowdsec_bouncer}
depends_on:
crowdsec:
condition: service_healthy
env_file: ${ABSOLUTE_PATH}/data/traefik-crowdsec-bouncer/.env
hostname: ${SERVICES_TRAEFIK_CROWDSEC_BOUNCER_HOSTNAME:-traefik-crowdsec-bouncer}
image: ${SERVICES_TRAEFIK_CROWDSEC_BOUNCER_IMAGE:-ghcr.io/freifunkmuc/traefik-crowdsec-bouncer}:${SERVICES_TRAEFIK_CROWDSEC_BOUNCER_IMAGE_VERSION:-latest}
networks:
crowdsec:
ipv4_address: ${SERVICES_TRAEFIK_CROWDSEC_BOUNCER_NETWORKS_CROWDSEC_IPV4:-172.31.127.252}
ipv6_address: ${SERVICES_TRAEFIK_CROWDSEC_BOUNCER_NETWORKS_CROWDSEC_IPV6:-fd00:1:be:a:7001:0:3e:6ffd}
restart: unless-stopped
EOF
#
# Fill Traefik compose file
#
cat <<'EOF' > /opt/containers/traefik-crowdsec-stack/compose/traefik.yml
services:
traefik:
container_name: ${SERVICES_TRAEFIK_CONTAINER_NAME:-traefik}
depends_on:
crowdsec:
condition: service_healthy
socket-proxy:
condition: service_healthy
env_file: ${ABSOLUTE_PATH}/data/traefik/.env
hostname: ${SERVICES_TRAEFIK_HOSTNAME:-traefik}
healthcheck:
test: ["CMD", "traefik", "healthcheck", "--ping"]
timeout: 1s
interval: 10s
retries: 3
start_period: 10s
image: ${SERVICES_TRAEFIK_IMAGE:-traefik}:${SERVICES_TRAEFIK_IMAGE_VERSION:-latest}
labels:
traefik.enable: "true"
traefik.http.routers.traefik-dashboad.entrypoints: websecure
traefik.http.routers.traefik-dashboad.middlewares: traefik-dashboard-auth@file
traefik.http.routers.traefik-dashboad.rule: ${SERVICES_TRAEFIK_LABELS_TRAEFIK_HOST:-HOST(`traefik.yourdomain.com`)}
traefik.http.routers.traefik-dashboad.service: api@internal
traefik.http.routers.traefik-dashboad.tls: "true"
traefik.http.routers.traefik-dashboad.tls.certresolver: tls_resolver
traefik.http.services.traefik-dashboad.loadbalancer.sticky.cookie.httpOnly: "true"
traefik.http.services.traefik-dashboad.loadbalancer.sticky.cookie.secure: "true"
traefik.http.routers.pingweb.rule: PathPrefix(`/ping`)
traefik.http.routers.pingweb.service: ping@internal
traefik.http.routers.pingweb.entrypoints: websecure
networks:
crowdsec:
ipv4_address: ${SERVICES_TRAEFIK_NETWORKS_CROWDSEC_IPV4:-172.31.127.253}
ipv6_address: ${SERVICES_TRAEFIK_NETWORKS_CROWDSEC_IPV6:-fd00:1:be:a:7001:0:3e:6ffe}
proxy:
ipv4_address: ${SERVICES_TRAEFIK_NETWORKS_PROXY_IPV4:-172.31.191.254}
ipv6_address: ${SERVICES_TRAEFIK_NETWORKS_PROXY_IPV6:-fd00:1:be:a:7001:0:3e:7fff}
socket_proxy:
ipv4_address: ${SERVICES_TRAEFIK_NETWORKS_SOCKET_PROXY_IPV4:-172.31.255.253}
ipv6_address: ${SERVICES_TRAEFIK_NETWORKS_SOCKET_PROXY_IPV6:-fd00:1:be:a:7001:0:3e:8ffe}
ports:
- mode: host
target: 80
published: "80"
protocol: tcp
- mode: host
target: 443
published: "443"
protocol: tcp
restart: unless-stopped
security_opt:
- no-new-privileges:true
volumes:
- /etc/localtime:/etc/localtime:ro
- ${ABSOLUTE_PATH}/data/traefik/traefik.yml:/etc/traefik/traefik.yml
- ${ABSOLUTE_PATH}/data/traefik/.htpasswd:/etc/traefik/.htpasswd
- ${ABSOLUTE_PATH}/data/traefik/certs/acme_letsencrypt.json:/etc/traefik//acme_letsencrypt.json
- ${ABSOLUTE_PATH}/data/traefik/certs/tls_letsencrypt.json:/etc/traefik/tls_letsencrypt.json
- ${ABSOLUTE_PATH}/data/traefik/dynamic_conf:/etc/traefik/dynamic_conf:ro
- /var/log/traefik/:/var/log/traefik/
EOF
#
# Fill Crowdsec compose file
#
cat <<'EOF' > /opt/containers/traefik-crowdsec-stack/compose/networks.yml
networks:
crowdsec:
name: ${NETWORKS_CROWDSEC_NAME:-crowdsec}
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: ${NETWORKS_CROWDSEC_SUBNET_IPV4:-172.31.64.0/18}
- subnet: ${NETWORKS_CROWDSEC_SUBNET_IPV6:-fd00:1:be:a:7001:0:3e:6000/116}
attachable: true
proxy:
name: ${NETWORKS_PROXY_NAME:-proxy}
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: ${NETWORKS_PROXY_SUBNET_IPV4:-172.31.128.0/18}
- subnet: ${NETWORKS_PROXY_SUBNET_IPV6:-fd00:1:be:a:7001:0:3e:7000/116}
attachable: true
socket_proxy:
name: ${NETWORKS_SOCKET_PROXY_NAME:-socket_proxy}
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: ${NETWORKS_SOCKET_PROXY_SUBNET_IPV4:-172.31.192.0/18}
- subnet: ${NETWORKS_SOCKET_PROXY_SUBNET_IPV6:-fd00:1:be:a:7001:0:3e:8000/116}
attachable: true
internal: true
EOF
#
# Fill Service Blog Bine compose file
#
cat <<'EOF' > /opt/containers/traefik-crowdsec-stack/compose/service-blog-bine.yml
services:
bine:
container_name: ${SERVICES_BINE_CONTAINER_NAME:-service-blog-bine}
image: nginx:latest
labels:
traefik.docker.network: proxy
traefik.enable: true
traefik.http.routers.bine-secure.entrypoints: websecure
traefik.http.routers.bine-secure.rule: Host(`bine.rastef.de`)
traefik.http.routers.bine-secure.service: service-blog-bine
traefik.http.routers.bine-secure.tls: true
traefik.http.routers.bine-secure.tls.certresolver: tls_resolver
traefik.http.routers.bine.entrypoints: web
traefik.http.routers.bine.rule: Host(`bine.rastef.de`)
traefik.http.services.bine.loadbalancer.server.port: "80"
networks:
default: null
proxy: null
restart: unless-stopped
volumes:
- ${ABSOLUTE_PATH}/data/service-blog/bine:/usr/share/nginx/html:ro
EOF
#
# Fill Service Blog Ralf compose file
#
cat <<'EOF' > /opt/containers/traefik-crowdsec-stack/compose/service-blog-ralf.yml
services:
blog:
container_name: ${SERVICES_BLOG_CONTAINER_NAME:-service-blog-ralf}
image: nginx:latest
labels:
traefik.docker.network: proxy
traefik.enable: true
traefik.http.routers.blog-secure.entrypoints: websecure
traefik.http.routers.blog-secure.rule: Host(`rastef.de`) || Host(`www.rastef.de`)
traefik.http.routers.blog-secure.service: service-blog-ralf
traefik.http.routers.blog-secure.tls: true
traefik.http.routers.blog-secure.tls.certresolver: tls_resolver
traefik.http.routers.blog.entrypoints: web
traefik.http.routers.blog.rule: Host(`rastef.de`) || Host(`www.rastef.de`)
traefik.http.services.blog.loadbalancer.server.port: "80"
networks:
default: null
proxy: null
restart: unless-stopped
volumes:
- ${ABSOLUTE_PATH}/data/service-blog/ralf:/usr/share/nginx/html:ro
EOF
#
# Fill Service Bookstack compose file
#
cat <<'EOF' > /opt/containers/traefik-crowdsec-stack/compose/service-bookstack.yml
services:
books:
container_name: bookstack
image: linuxserver/bookstack:latest
environment:
- TZ=Europe/Berlin
- APP_URL=https://buecher.rastef.de
- APP_KEY={{get it from Bookstack with: docker run -it --rm --entrypoint /bin/bash lscr.io/linuxserver/bookstack:latest appkey}}
- DB_HOST=bookstackdb
- DB_DATABASE=bookstack
- DB_USERNAME=bookstack
- DB_PASSWORD={{--Das Bookstack DB Password--}}
labels:
traefik.docker.network: proxy
traefik.enable: true
traefik.http.routers.books-secure.entrypoints: websecure
traefik.http.routers.books-secure.rule: Host(`buecher.rastef.de`)
traefik.http.routers.books-secure.service: bookstack
traefik.http.routers.books-secure.tls: true
traefik.http.routers.books-secure.tls.certresolver: tls_resolver
traefik.http.routers.books.entrypoints: web
traefik.http.routers.books.rule: Host(`buecher.rastef.de`)
traefik.http.services.books.loadbalancer.server.port: "80"
networks:
default: null
proxy: null
restart: unless-stopped
volumes:
- ${ABSOLUTE_PATH}/data/bookstack/app:/config
bookstackdb:
container_name: booksdb
image: linuxserver/mariadb:latest
environment:
- TZ=Europe/Berlin
- MYSQL_ROOT_PASSWORD={{--Das MySQL Root Password--}}
- MYSQL_DATABASE=bookstack
- MYSQL_USER=bookstack
- MYSQL_PASSWORD={{--Das Bookstack DB Password--}}
networks:
default: null
restart: unless-stopped
volumes:
- ${ABSOLUTE_PATH}/data/bookstack/db:/config
EOF
#
# Fill Service Gitea compose file
#
cat <<'EOF' > /opt/containers/traefik-crowdsec-stack/compose/service-gitea.yml
services:
gitea:
container_name: ${SERVICES_GITEA_CONTAINER_NAME:-service-gitea}
image: docker.gitea.com/gitea:latest-rootless
labels:
traefik.docker.network: proxy
traefik.enable: true
traefik.http.routers.gitea-secure.entrypoints: websecure
traefik.http.routers.gitea-secure.rule: Host(`gitea.rastef.de`)
traefik.http.routers.gitea-secure.service: service-gitea
traefik.http.routers.gitea-secure.tls: true
traefik.http.routers.gitea-secure.tls.certresolver: tls_resolver
traefik.http.routers.gitea.entrypoints: web
traefik.http.routers.gitea.rule: Host(`gitea.rastef.de`)
traefik.http.services.gitea.loadbalancer.server.port: "3000"
networks:
default: null
proxy: null
restart: unless-stopped
volumes:
- ${ABSOLUTE_PATH}/data/gitea/repos:/var/lib/gitea
- ${ABSOLUTE_PATH}/data/gitea/config:/etc/gitea
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
EOF
Mit dem tree Kommando lässt sich die Verzeichnisstruktur verifizieren. Wenn das tree Kommando fehlt, kann es schnell mit sudo apt install tree installiert werden.
$ tree -L 4 -a /opt/containers/traefik-crowdsec-stack/
/opt/containers/traefik-crowdsec-stack/
├── compose
│ ├── crowdsec.yml
│ ├── networks.yml
│ ├── service-blog-bine.yml
│ ├── service-blog-ralf.yml
│ ├── service-bookstack.yml
│ ├── service-gitea.yml
│ ├── socket-proxy.yml
│ ├── traefik-crowdsec-bouncer.yml
│ └── traefik.yml
├── data
│ ├── crowdsec
│ │ ├── config
│ │ ├── data
│ │ └── .env
│ ├── service-blog
│ │ ├── bine
│ │ └── ralf
│ ├── service-bookstack
│ │ ├── app
│ │ └── db
│ ├── service-gitea
│ ├── socket-proxy
│ │ └── .env
│ ├── traefik
│ │ ├── certs
│ │ │ ├── acme_letsencrypt.json
│ │ │ └── tls_letsencrypt.json
│ │ ├── dynamic_conf
│ │ │ ├── http.middlewares.default-security-headers.yml
│ │ │ ├── http.middlewares.default.yml
│ │ │ ├── http.middlewares.gzip.yml
│ │ │ ├── http.middlewares.traefik-bouncer.yml
│ │ │ ├── http.middlewares.traefik-dashboard-auth.yml
│ │ │ └── tls.yml
│ │ ├── .env
│ │ ├── .htpasswd
│ │ └── traefik.yml
│ └── traefik-crowdsec-bouncer
│ └── .env
├── docker-compose.yml
└── .env
18 directories, 25 files
Bitte beachten, dass in diversen Dateien noch manuelle Korrekturen vorgenommen werden müssen. Daher bitte noch kein docker compose ausführen. Lesen sie dazu auf der nächsten Seite weiter.