From 3175a0d125da2d7bd26a338f468fbda2fc53b472 Mon Sep 17 00:00:00 2001 From: Elvio Petillo Date: Sun, 27 Feb 2022 23:58:02 +0100 Subject: [PATCH] Add advanced traefik example --- examples/traefik-advanced/.env | 3 + examples/traefik-advanced/README.md | 32 +++ examples/traefik-advanced/anonaddy.env | 43 ++++ examples/traefik-advanced/config/ciphers.yaml | 16 ++ examples/traefik-advanced/config/dynamic.yaml | 65 +++++ examples/traefik-advanced/docker-compose.yml | 239 ++++++++++++++++++ 6 files changed, 398 insertions(+) create mode 100644 examples/traefik-advanced/.env create mode 100644 examples/traefik-advanced/README.md create mode 100644 examples/traefik-advanced/anonaddy.env create mode 100644 examples/traefik-advanced/config/ciphers.yaml create mode 100644 examples/traefik-advanced/config/dynamic.yaml create mode 100644 examples/traefik-advanced/docker-compose.yml diff --git a/examples/traefik-advanced/.env b/examples/traefik-advanced/.env new file mode 100644 index 0000000..8637e7d --- /dev/null +++ b/examples/traefik-advanced/.env @@ -0,0 +1,3 @@ +MYSQL_DATABASE=anonaddy +MYSQL_USER=anonaddy +MYSQL_PASSWORD=anonaddy diff --git a/examples/traefik-advanced/README.md b/examples/traefik-advanced/README.md new file mode 100644 index 0000000..13c120c --- /dev/null +++ b/examples/traefik-advanced/README.md @@ -0,0 +1,32 @@ +This is a strongly opinionated AnonAddy Docker + Traefik config template that provides *some* production quality features. +**Note** that you must further tweak the configuration and then run Docker in Swarm mode to ensure e.g. encrypted network traffic and scaling for *serious* production usage. +You should also use something like Hashicorp Vault to protect any secrets as Docker secret files are still stored in plain text on the filesystem as well as disable root user access in containers. + +## Features + - Automatic creation of ACME SSL Wildcard Certificates using DNS Challenge resolver + - [Tecnativa's Docker Socket Proxy](https://github.com/Tecnativa/docker-socket-proxy) (reduce risk of Docker socket exposure) + - Automatic Postfix TLS management using [traefik-certs-dumper](https://github.com/kereis/traefik-certs-dumper) + - Auto-dumping of Let's Encrypt certificates to Postfix cert directory + - Watch & restart AnonAddy container on certificate renewal + - Hardened TLS cipher configuration + - [Watchtower](https://github.com/containrrr/watchtower) for automatic AnonAddy container updates upon new release + - CrowdSec with Traefik bouncer for SPAM detection and mitigation. Please refer to the + [CrowdSec documentation](https://docs.crowdsec.net/docs/getting_started/install_crowdsec) for initial setup instructions. + - Enabled Rspamd and exposed Web UI (also covered by CrowdSec bouncer) at [https://**spam**.example.com](https://spam.example.com) + +**Note**: This configuration does not ensure true Zero Downtime re-deploys! + +## Usage + +Make sure you have followed the steps described [here](https://github.com/anonaddy/docker#generate-dkim-privatepublic-keypair) to generate a DKIM keypair. +Use these files for full SMTP(D) TLS/ DKIM/ DMARC/ PGP signing functionalities. + +```bash +mkdir letsencrypt +touch letsencrypt/acme.json +chmod 600 letsencrypt/acme.json +docker-compose up -d +docker-compose logs -f +``` + +You will also need to create secret files containing the DNS Challenge provider credentials. For more information, please refer to the [Traefik Docs](https://doc.traefik.io/traefik/https/acme/#providers). diff --git a/examples/traefik-advanced/anonaddy.env b/examples/traefik-advanced/anonaddy.env new file mode 100644 index 0000000..5e87368 --- /dev/null +++ b/examples/traefik-advanced/anonaddy.env @@ -0,0 +1,43 @@ +TZ=Europe/Paris +PUID=1000 +PGID=1000 + +MEMORY_LIMIT=256M +UPLOAD_MAX_SIZE=16M +OPCACHE_MEM_SIZE=128 +REAL_IP_FROM=0.0.0.0/32 +REAL_IP_HEADER=X-Forwarded-For +LOG_IP_VAR=http_x_forwarded_for +#LISTEN_IPV6=false + +APP_KEY= +APP_DEBUG=false +APP_URL=https://anonaddy.example.com + +ANONADDY_RETURN_PATH=bounces@example.com +ANONADDY_ADMIN_USERNAME=anonaddy +ANONADDY_ENABLE_REGISTRATION=true +ANONADDY_DOMAIN=example.com +ANONADDY_ALL_DOMAINS=example.com +ANONADDY_HOSTNAME=anonaddy.example.com +ANONADDY_DNS_RESOLVER=127.0.0.1 +ANONADDY_SECRET= +ANONADDY_LIMIT=200 +ANONADDY_BANDWIDTH_LIMIT=104857600 +ANONADDY_NEW_ALIAS_LIMIT=10 +ANONADDY_ADDITIONAL_USERNAME_LIMIT=3 +# See [Generate GPG key](https://github.com/anonaddy/docker#generate-gpg-key) +#ANONADDY_SIGNING_KEY_FINGERPRINT= + +MAIL_FROM_NAME=AnonAddy +MAIL_FROM_ADDRESS=anonaddy@example.com + +# See [Generate DKIM private/public keypair](https://github.com/anonaddy/docker#generate-dkim-privatepublic-keypair) +RSPAMD_ENABLE=true +RSPAMD_WEB_PASSWORD= + +POSTFIX_DEBUG=false +POSTFIX_SMTPD_TLS=true +POSTFIX_SMTPD_TLS_CERT_FILE=/data/output/mydomain.com/cert.pem +POSTFIX_SMTPD_TLS_KEY_FILE=/data/output/mydomain.com/key.pem +POSTFIX_SMTP_TLS=true diff --git a/examples/traefik-advanced/config/ciphers.yaml b/examples/traefik-advanced/config/ciphers.yaml new file mode 100644 index 0000000..0999b51 --- /dev/null +++ b/examples/traefik-advanced/config/ciphers.yaml @@ -0,0 +1,16 @@ +tls: + options: + default: + minVersion: VersionTLS12 + sniStrict: true + cipherSuites: + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 + + mintls13: + minVersion: VersionTLS13 + sniStrict: true diff --git a/examples/traefik-advanced/config/dynamic.yaml b/examples/traefik-advanced/config/dynamic.yaml new file mode 100644 index 0000000..c2bb1ba --- /dev/null +++ b/examples/traefik-advanced/config/dynamic.yaml @@ -0,0 +1,65 @@ +http: + routers: + anonaddy: + service: anonaddy + entrypoints: + - https + rule: "Host(`anonaddy.example.com`)" + middlewares: + - crowdsec-bouncer + tls: + certResolver: dnschallenge + domains: + - main: "example.com" + sans: + - "example.com" + - "anonaddy.example.com" + - "www.example.com" + rspamd: + service: rspamd + entrypoints: + - https + rule: "Host(`spam.example.com`)" + middlewares: + - crowdsec-bouncer + tls: + certResolver: dnschallenge + domains: + - main: "spam.example.com" + sans: + - "spam.example.com" + middlewares: + crowdsec-bouncer: + forwardAuth: + address: "http://bouncer:8080/api/v1/forwardAuth" + redirect-https: + redirectScheme: + scheme: https + permanent: true + default-middlewares: + chain: + middlewares: + - default-headers-https@file + - default-compress@file + default-headers-https: + headers: + customBrowserXSSValue: "0" + contentTypeNosniff: true + customResponseHeaders: + Server: "" + forceSTSHeader: true + frameDeny: true + stsSeconds: 31536000 + stsPreload: true + stsIncludeSubdomains: true + default-compress: + compress: {} + services: + anonaddy: + loadbalancer: + servers: + - url: http://172.21.0.8:8000 + rspamd: + loadbalancer: + servers: + - url: http://172.21.0.8:11334 diff --git a/examples/traefik-advanced/docker-compose.yml b/examples/traefik-advanced/docker-compose.yml new file mode 100644 index 0000000..6d8356d --- /dev/null +++ b/examples/traefik-advanced/docker-compose.yml @@ -0,0 +1,239 @@ +version: '3.5' +services: + dockerproxy: + image: tecnativa/docker-socket-proxy:latest + container_name: dockerproxy + environment: + # Feel free to try with less + - "CONTAINERS=1" + - "CONTAINERS_CREATE=1" + - "CONTAINERS_START=1" + - "CONTAINERS_UPDATE=1" + - "CONTAINERS_DELETE=1" + - "IMAGES=1" + - "IMAGES_DELETE=1" + - "POST=1" + - "DELETE=1" + - "ALLOW_RESTARTS=1" + - "EXEC=1" + - "NETWORKS=1" + - "INFO=1" + ports: + - '2375:2375' + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + networks: + - dockerproxy + restart: always + + traefik: + image: traefik:2.6 + container_name: traefik + command: + - "--global.checknewversion=false" + - "--global.sendanonymoususage=false" + - "--log=true" + - "--log.level=INFO" + - "--entrypoints.http=true" + - "--entrypoints.http.address=:80" + - "--entrypoints.http.http.redirections.entrypoint.to=https" + - "--entrypoints.http.http.redirections.entrypoint.scheme=https" + - "--entrypoints.https=true" + - "--entrypoints.https.address=:443" + #- "--certificatesresolvers.dnschallenge.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory" # <-- You might want to use this at first + - "--certificatesresolvers.dnschallenge.acme.dnschallenge=true" + - "--certificatesresolvers.dnschallenge.acme.storage=acme.json" + - "--certificatesresolvers.dnschallenge.acme.keyType=EC384" + - "--certificatesresolvers.dnschallenge.acme.email=postmaster@example.com" # <-- REPLACE + - "--certificatesresolvers.dnschallenge.acme.dnschallenge.provider=namedotcom" # <-- REPLACE, see https://doc.traefik.io/traefik/https/acme/#providers + - "--providers.file.directory=/config" + - "--providers.file.watch=true" + - "--providers.docker" + - "--providers.docker.watch=true" + - "--providers.docker.exposedbydefault=false" + - "--providers.docker.endpoint=tcp://dockerproxy:2375" + - "--providers.docker.constraints=Label(`traefik.constraint-label`, `cloud-public`)" + labels: + - "traefik.enable=true" + - "traefik.docker.network=anonaddy_cloud-public" + - "traefik.constraint-label=cloud-public" + environment: + - "NAMECOM_USERNAME_FILE=/run/secrets/namedotcom_username" # <-- REPLACE, depends on your chosen DNS Challenge provider + - "NAMECOM_API_TOKEN_FILE=/run/secrets/namedotcom_api_token" # <-- REPLACE, depends on your chosen DNS Challenge provider + ports: + - target: 80 + published: 80 + protocol: tcp + mode: host + - target: 443 + published: 443 + protocol: tcp + mode: host + networks: + cloud-public: + ipv4_address: 172.21.0.5 # <-- Customize as desired + cloud-edge: + dockerproxy: + depends_on: + - dockerproxy + secrets: + # Change these to suit your DNS provider + - + source: exampledotcom_NAMEDOTCOM_USERNAME + target: /run/secrets/namedotcom_username + - + source: exampledotcom_NAMEDOTCOM_API_TOKEN + target: /run/secrets/namedotcom_api_token + volumes: + - "./letsencrypt/acme.json:/acme.json" + - "/home/docker/anonaddy/config/:/config" # <-- REPLACE, point to the correct directory + restart: always + + watchtower: + image: containrrr/watchtower:latest + depends_on: + - dockerproxy + - traefik + labels: + - "traefik.constraint-label=cloud-public" + environment: + - "DOCKER_HOST=tcp://dockerproxy:2375" + - "WATCHTOWER_LABEL_ENABLE=true" + - "WATCHTOWER_CLEANUP=true" + - "WATCHTOWER_INCLUDE_RESTARTING=true" + - "WATCHTOWER_POLL_INTERVAL=6400" # <-- Set this to the value you need + - "WATCHTOWER_NOTIFICATIONS=gotify" # <-- This example uses gotify, but there are other options + - "WATCHTOWER_NOTIFICATION_GOTIFY_URL=https://push.domain.com/" # <-- REPLACE, must point to a gotify server + - "WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN=" # <-- INSERT + networks: + dockerproxy: + cloud-public: + ipv4_address: 172.21.0.7 # <-- Customize as desired + restart: unless-stopped + + certdumper: + image: humenius/traefik-certs-dumper:latest + depends_on: + - dockerproxy + - traefik + command: --restart-containers anonaddy + volumes: + - "./letsencrypt:/traefik:ro" + - "./data/output:/output:rw" + environment: + - "DOCKER_HOST=dockerproxy:2375" + - "DOMAIN=mail.example.com" # <-- REPLACE + - "OVERRIDE_UID=1000" + - "OVERRIDE_GID=1000" + networks: + - dockerproxy + restart: always + + anonaddy: + image: anonaddy/anonaddy:latest + depends_on: + - db + - redis + container_name: anonaddy + labels: + - "traefik.enable=true" + - "traefik.http.middlewares.crowdsec-bouncer.forwardauth.address=http://bouncer:8080/api/v1/forwardAuth" + #- "traefik.http.middlewares.crowdsec-bouncer.forwardauth.trustForwardHeader=true" + + - "com.centurylinklabs.watchtower.enable=true" + - "traefik.constraint-label=cloud-public" + env_file: + - "./anonaddy.env" + environment: + - "DB_HOST=db" + - "DB_DATABASE=${MYSQL_DATABASE}" + - "DB_USERNAME=${MYSQL_USER}" + - "DB_PASSWORD=${MYSQL_PASSWORD}" + - "REDIS_HOST=redis" + ports: + - '25:25' + networks: + cloud-public: + ipv4_address: 172.21.0.8 # <-- Change as desired + volumes: + - "./data:/data" + restart: unless-stopped + + crowdsec: + image: crowdsecurity/crowdsec:v1.3.0 + container_name: "crowdsec-anonaddy" + labels: + - "com.centurylinklabs.watchtower.enable=true" + - "traefik.constraint-label=cloud-public" + environment: + COLLECTIONS: "crowdsecurity/nginx crowdsecurity/postfix" + GID: "${GID-1000}" + #DOCKER_API_VERSION: "1.41" + depends_on: + - traefik + - dockerproxy + volumes: + - crowdsec-db-anonaddy:/var/lib/crowdsec/data/ + - crowdsec-config-anonaddy:/etc/crowdsec/ + networks: + dockerproxy: + cloud-public: + ipv4_address: 172.21.0.6 # <-- Change as desired + priority: 100 + + bouncer: + image: fbonalair/traefik-crowdsec-bouncer:0.1.1 + container_name: "bouncer-anonaddy" + labels: + - "com.centurylinklabs.watchtower.enable=true" + - "traefik.constraint-label=cloud-public" + environment: + - "CROWDSEC_BOUNCER_API_KEY=" # <-- INSERT + - "CROWDSEC_AGENT_HOST=crowdsec:8080" + - "GIN_MODE=release" + networks: + cloud-public: + ipv4_address: 172.21.0.3 # <-- Change as desired + + db: + image: mariadb:10.5 + container_name: anonaddy_db + environment: + - "MYSQL_ALLOW_EMPTY_PASSWORD=yes" + - "MYSQL_DATABASE" + - "MYSQL_PASSWORD" + - "MYSQL_USER" + labels: + - "traefik.constraint-label=cloud-public" + networks: + cloud-public: + ipv4_address: 172.21.0.2 # <-- Change as desired + volumes: + - "./db:/var/lib/mysql" + restart: unless-stopped + + redis: + image: redis:4.0-alpine + container_name: anonaddy_redis + labels: + - "traefik.constraint-label=cloud-public" + networks: + cloud-public: + ipv4_address: 172.21.0.4 # <-- Change as desired + restart: unless-stopped + +secrets: + exampledotcom_NAMEDOTCOM_USERNAME: # <-- REPLACE + file: exampledotcom_namedotcom_username.txt # <-- REPLACE and CREATE FILE + exampledotcom_NAMEDOTCOM_API_TOKEN: # <-- REPLACE + file: exampledotcom_namedotcom_api_token.txt # <-- REPLACE and CREATE FILE +networks: + dockerproxy: + cloud-public: + ipam: + config: + - subnet: 172.21.0.0/16 # <-- Change as desired + cloud-edge: +volumes: + crowdsec-db-anonaddy: + crowdsec-config-anonaddy: