diff --git a/.gitignore b/.gitignore index c9cd03c..adeb3f4 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,5 @@ charts/**/charts/ # Traefik filesystem config/traefik/logs/* !config/traefik/logs/.gitkeep - -config/traefik/ssl/* -!config/traefik/ssl/.gitkeep +config/traefik/ssl-files/* +!config/traefik/ssl-files/.gitkeep diff --git a/config/traefik/README.md b/config/traefik/README.md index 72dc2fa..a92e3d1 100644 --- a/config/traefik/README.md +++ b/config/traefik/README.md @@ -15,9 +15,9 @@ Traefik in combination with [Cloudflare](https://cloudflare.com) receives and se ## Usage -1. Copy the [docker-compose.override.yml](./docker-compose.override.yml) to the repositorie's root directory +1. Either copy the [HTTP-01 docker-compose.override.yml](./examples/http-01/docker-compose.override.yml) to the repositorie's root directory for issueing ssl certificates via `http-01` challenge, or copy the [DNS-01 cloudflare docker-compose.override.yml](./examples/dns-01-cloudflare/docker-compose.override.yml) to use Cloudflare and `dns-01` challenge. 1. Set the Traefik proxy vars in the [env](../../env) file -1. Update the basic-auth username and password in [dynamic.yml](./config/dynamic.yml) +1. Update the basic-auth username and password in [dynamic-http.yml](./config/dynamic-http.yml) 1. Start the docker compose stack A few seconds later, you should be able to visit `https://${APP_URL_DOMAIN}:8080/dashboard/` and should be prompted for a username and password. If you have not changed it, it should be `username` and `EncryptedPassword`. @@ -31,3 +31,5 @@ If anything does not work as expected, consider checking Traefik's container log ```bash docker compose logs -tf traefik ``` + +and check the official [Traefik Documentation](https://doc.traefik.io/traefik/). diff --git a/config/traefik/config/dynamic-http.yml b/config/traefik/config/dynamic-http.yml new file mode 100644 index 0000000..affc12a --- /dev/null +++ b/config/traefik/config/dynamic-http.yml @@ -0,0 +1,42 @@ +--- +http: + middlewares: + basic-auth: + basicAuth: + users: + # Generate encrypted password string via 'htpasswd -nB username' + # enter your password twice and paste the output here + # myusername:mysafepassword + - "myusername:$2y$05$Tx/.9qaFoZiLi41ZDvO1fOqiSohhuAr8jf9yEbQxZWlqANMKQYnYe" + gzip: + compress: {} + security-headers: + headers: + browserXssFilter: true + contentTypeNosniff: true + forceSTSHeader: true + frameDeny: true + stsIncludeSubdomains: true + stsPreload: true + stsSeconds: 31536000 + customFrameOptionsValue: "SAMEORIGIN" + referrerPolicy: "no-referrer" + customRequestHeaders: + X-Forwarded-Proto: "https" + strict-ratelimit: + rateLimit: + average: 10 + burst: 50 + default-ratelimit: + rateLimit: + average: 100 + burst: 50 + high-ratelimit: + rateLimit: + average: 1000 + burst: 500 + default: + chain: + middlewares: + - "security-headers" + - "gzip" diff --git a/config/traefik/config/dynamic-tls.yml b/config/traefik/config/dynamic-tls.yml new file mode 100644 index 0000000..ca49a0a --- /dev/null +++ b/config/traefik/config/dynamic-tls.yml @@ -0,0 +1,19 @@ +--- +tls: + options: + default: + minVersion: VersionTLS12 + sniStrict: true + cipherSuites: + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 + - TLS_AES_128_GCM_SHA256 + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + curvePreferences: + - CurveP521 + - CurveP384 diff --git a/config/traefik/config/dynamic.yml b/config/traefik/config/dynamic.yml deleted file mode 100644 index 8c807c5..0000000 --- a/config/traefik/config/dynamic.yml +++ /dev/null @@ -1,53 +0,0 @@ -tls: - options: - default: - minVersion: VersionTLS12 - sniStrict: true - cipherSuites: - - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 - - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 - - TLS_AES_128_GCM_SHA256 - - TLS_AES_256_GCM_SHA384 - - TLS_CHACHA20_POLY1305_SHA256 - curvePreferences: - - CurveP521 - - CurveP384 -http: - middlewares: - default: - chain: - middlewares: - - default-security-headers - - gzip - secHeaders: - chain: - middlewares: - - default-security-headers - - gzip - - traefik-auth: - basicauth: - # Encrypt Password via 'echo $(htpasswd -nB username) | sed -e s/\\$/\\$\\$/g' - # Username: username - # Password: EncryptedPassword - users: username:$2y$05$lz9I2C9KoUdHwCOmPsKeBOU7EYMKMyzzCQs3KEmg4JZZL7ahLzjGO - - default-security-headers: - headers: - browserXssFilter: true - contentTypeNosniff: true - forceSTSHeader: true - frameDeny: true - stsIncludeSubdomains: true - stsPreload: true - stsSeconds: 31536000 - customFrameOptionsValue: "SAMEORIGIN" - referrerPolicy: "no-referrer" - customRequestHeaders: - X-Forwarded-Proto: "https" - gzip: - compress: {} diff --git a/config/traefik/docker-compose.override.yml b/config/traefik/docker-compose.override.yml deleted file mode 100644 index c56e726..0000000 --- a/config/traefik/docker-compose.override.yml +++ /dev/null @@ -1,99 +0,0 @@ -version: "3.7" - -services: - traefik: - image: traefik:latest - container_name: traefik - restart: unless-stopped - env_file: env - ports: - # Run traefik on port 80 and 443 - # Feel free to modify depending what port is already occupied - - "80:80" - - "443:443" - # Run traefik dashboard on port 8080 - # Feel free to modify depending what port is already occupied - - "8080:8080" - command: - # By default, the level is set to ERROR. Alternative logging levels are - # DEBUG, PANIC, FATAL, ERROR, WARN, and INFO. - - --log.level=ERROR - - --global.sendAnonymousUsage=false - # Enable Dashboard - - --api.insecure=false - - --api.dashboard=true - - --api.debug=true - # We are using Docker - - --providers.docker=true - - --providers.docker.exposedbydefault=false - # (Optional) Set default hostname if not given explicitly - - --providers.docker.defaultRule=Host(`${APP_URL_DOMAIN}`) - # Listen on port 80 (http) - - --entrypoints.web.address=:80 - # Listen on port 443 (https) - - --entrypoints.websecure.address=:443 - # Listen on port 8080 (traefik Dashboard) - - --entrypoints.traefik-dashboard.address=:8080 - # Watch dynamic configuration file - - --providers.file.directory=/config - - --providers.file.watch=true - # Enable Access Log - - --accesslog.filepath=/var/log/www/access.log - # Automaticly redirect from http to https - - --entrypoints.web.http.redirections.entryPoint.to=websecure - - --entrypoints.web.http.redirections.entryPoint.scheme=https - - ################ START SSL configuration ################ - # ---------> Cloudflare <--------- - # DNS challenge via Cloudflare - - --certificatesresolvers.cloudflare.acme.email=${ACME_EMAIL} - - --certificatesresolvers.cloudflare.acme.storage=/ssl/acme.json - - --certificatesresolvers.cloudflare.acme.dnsChallenge.provider=cloudflare - - --certificatesresolvers.cloudflare.acme.dnsChallenge.delayBeforeCheck=60 - - --certificatesresolvers.cloudflare.acme.dnsChallenge.resolvers=1.1.1.1:53 - # (Optional) Use testing server before receiving the productive ssl certificate - #- --certificatesresolvers.cloudflare.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory - - --entrypoints.websecure.http.tls.domains[0].main=${APP_URL_DOMAIN} - # (Optional) Use only, if you are able to receive a wildcard ssl certificate - # - --entrypoints.websecure.http.tls.domains[0].main=*.${CLOUDFLARE_DOMAIN} - # -------------------------------- - ################ END SSL configuration ################ - volumes: - # So that Traefik can listen to the Docker events - - /var/run/docker.sock:/var/run/docker.sock:ro - # Dynamic configuration files - - ./config/traefik/config:/config - # Enable Access Log - - ./config/traefik/logs/:/var/log/www/ - # LetsEncrypt Configuration Storage - - ./config/traefik/ssl:/ssl - labels: - # Enable Traefik - - traefik.enable=true - # Set Network to use - - traefik.docker.network=invoiceninja - # Load dynamic config - - traefik.http.routers.traefik-dashboard.middlewares=default@file - # Service related labels - - traefik.http.routers.traefik-dashboard.entrypoints=traefik-dashboard - - traefik.http.routers.traefik-dashboard.middlewares=traefik-auth@file - - traefik.http.routers.traefik-dashboard.service=api@internal - - traefik.http.routers.traefik-dashboard.tls=true - - traefik.http.routers.traefik-dashboard.tls.certResolver=cloudflare - - traefik.http.routers.traefik-dashboard.rule=Host(`${APP_URL_DOMAIN}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`)) - networks: - - invoiceninja - - server: - labels: - # Enable Traefik - - "traefik.enable=true" - # Set Network to use - - "traefik.docker.network=invoiceninja" - # Load dynamic config - - "traefik.http.routers.ninja-nginx.middlewares=default@file" - # Service related labels - - "traefik.http.routers.ninja-nginx.entrypoints=websecure" - - "traefik.http.routers.ninja-nginx.tls=true" - - "traefik.http.routers.ninja-nginx.tls.certResolver=cloudflare" - - "traefik.http.routers.ninja-nginx.rule=Host(`${APP_URL_DOMAIN}`)" diff --git a/config/traefik/examples/dns-01-cloudflare/docker-compose.override.yml b/config/traefik/examples/dns-01-cloudflare/docker-compose.override.yml new file mode 100644 index 0000000..c29e9c8 --- /dev/null +++ b/config/traefik/examples/dns-01-cloudflare/docker-compose.override.yml @@ -0,0 +1,98 @@ +version: "3.7" + +services: + traefik: + # The official v2 Traefik docker image + image: traefik:latest + container_name: traefik + restart: always + env_file: env + ports: + - "80:80" + - "443:443" + - "8080:8080" + networks: + - "invoiceninja" + volumes: + # So that Traefik can listen to the Docker events + - "/var/run/docker.sock:/var/run/docker.sock:ro" + # Dynamic configuration files + - "./config/traefik/config:/conf:ro" + # Enable Access Log + - "./config/traefik/logs/:/var/log/www/" + # LetsEncrypt Configuration Storage + - "./config/traefik/ssl-files:/ssl" + command: + # Send usage statistics (or not) + - "--global.sendAnonymousUsage=false" + # By default, the level is set to ERROR. Alternative logging levels are + # DEBUG, PANIC, FATAL, ERROR, WARN, and INFO. + - "--log.level=WARN" + # Enable Access Log + - "--accesslog.filepath=/var/log/www/access.log" + # Enable Dashboard + - "--api.insecure=false" + - "--api.dashboard=true" + - "--api.debug=true" + # We are using Docker + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + # (Optional) Set default hostname if not given explicitly + - "--providers.docker.defaultRule=Host(`${APP_URL_DOMAIN}`)" + # Listen on port 80 (http) + - "--entrypoints.web.address=:80" + # Listen on port 443 (https) + - "--entrypoints.websecure.address=:443" + # Listen on port 8080 (traefik Dashboard) + - "--entrypoints.traefik-dashbaord.address=:8080" + # Watch dynamic configuration file + - "--providers.file.directory=/conf" + - "--providers.file.watch=true" + # Automaticly redirect from http to https + - "--entrypoints.web.http.redirections.entryPoint.to=websecure" + - "--entrypoints.web.http.redirections.entryPoint.scheme=https" + ################ START SSL configuration ################ + # ---------> Cloudflare <--------- + # DNS challenge via Cloudflare + - "--certificatesresolvers.cloudflare.acme.email=${ACME_EMAIL}" + - "--certificatesresolvers.cloudflare.acme.storage=/ssl/acme.json" + - "--certificatesresolvers.cloudflare.acme.dnsChallenge.provider=cloudflare" + - "--certificatesresolvers.cloudflare.acme.dnsChallenge.delayBeforeCheck=60" + - "--certificatesresolvers.cloudflare.acme.dnsChallenge.resolvers=1.1.1.1:53" + # (Optional) Use testing server before receiving the productive ssl certificate + #- --certificatesresolvers.cloudflare.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory + - "--entrypoints.websecure.http.tls.domains[0].main=${APP_URL_DOMAIN}" + # (Optional) Use only, if you are able to receive a wildcard ssl certificate + # - "--entrypoints.websecure.http.tls.domains[0].main=*.${APP_URL_DOMAIN}" + # -------------------------------- + ################ END SSL configuration ################ + labels: + # Enable Traefik + - "traefik.enable=true" + # Set Network to use + - "traefik.docker.network=invoiceninja" + # Set service type + - "traefik.http.routers.traefik-dashbaord.service=api@internal" + # Load dynamic config from conf/*.yml + - "traefik.http.routers.traefik-dashbaord.middlewares=default@file,basic-auth@file" + # Define entrypint to use + - "traefik.http.routers.traefik-dashbaord.entrypoints=traefik-dashbaord" + # Define Hostname and path + - "traefik.http.routers.traefik-dashbaord.rule=Host(`${APP_URL_DOMAIN}`) && PathPrefix(`/api`,`/dashboard`)" + # Enable SSL/TLS + - "traefik.http.routers.traefik-dashbaord.tls=true" + - "traefik.http.routers.traefik-dashbaord.tls.certResolver=cloudflare" + + server: + labels: + # Enable Traefik + - "traefik.enable=true" + # Set Network to use + - "traefik.docker.network=invoiceninja" + # Load dynamic config + - "traefik.http.routers.ninja-nginx.middlewares=default@file" + # Service related labels + - "traefik.http.routers.ninja-nginx.entrypoints=websecure" + - "traefik.http.routers.ninja-nginx.rule=Host(`${APP_URL_DOMAIN}`)" + - "traefik.http.routers.ninja-nginx.tls=true" + - "traefik.http.routers.ninja-nginx.tls.certResolver=cloudflare" diff --git a/config/traefik/examples/http-01/docker-compose.override.yml b/config/traefik/examples/http-01/docker-compose.override.yml new file mode 100644 index 0000000..28388ff --- /dev/null +++ b/config/traefik/examples/http-01/docker-compose.override.yml @@ -0,0 +1,93 @@ +version: "3.7" + +services: + traefik: + # The official v2 Traefik docker image + image: traefik:latest + container_name: traefik + restart: always + env_file: env + ports: + - "80:80" + - "443:443" + - "8080:8080" + networks: + - "invoiceninja" + volumes: + # So that Traefik can listen to the Docker events + - "/var/run/docker.sock:/var/run/docker.sock:ro" + # Dynamic configuration files + - "./config/traefik/config:/conf:ro" + # Enable Access Log + - "./config/traefik/logs/:/var/log/www/" + # LetsEncrypt Configuration Storage + - "./config/traefik/ssl-files:/ssl" + command: + # Send usage statistics (or not) + - "--global.sendAnonymousUsage=false" + # By default, the level is set to ERROR. Alternative logging levels are + # DEBUG, PANIC, FATAL, ERROR, WARN, and INFO. + - "--log.level=WARN" + # Enable Access Log + - "--accesslog.filepath=/var/log/www/access.log" + # Enable Dashboard + - "--api.insecure=false" + - "--api.dashboard=true" + - "--api.debug=true" + # We are using Docker + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + # (Optional) Set default hostname if not given explicitly + - "--providers.docker.defaultRule=Host(`${APP_URL_DOMAIN}`)" + # Listen on port 80 (http) + - "--entrypoints.web.address=:80" + # Listen on port 443 (https) + - "--entrypoints.websecure.address=:443" + # Listen on port 8080 (traefik Dashboard) + - "--entrypoints.traefik-dashbaord.address=:8080" + # Watch dynamic configuration file + - "--providers.file.directory=/conf" + - "--providers.file.watch=true" + # Automaticly redirect from http to https + - "--entrypoints.web.http.redirections.entryPoint.to=websecure" + - "--entrypoints.web.http.redirections.entryPoint.scheme=https" + ################ START SSL configuration ################ + # ---------> LetsEncrypt <--------- + # HTTP-Challenge + - "--certificatesresolvers.http-01.acme.httpchallenge=true" + - "--certificatesresolvers.http-01.acme.httpchallenge.entrypoint=web" + # (Optional) Use testing server before receiving the productive ssl certificate + #- "--certificatesresolvers.http-01.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory" + - "--certificatesresolvers.http-01.acme.email=${ACME_EMAIL}" + - "--certificatesresolvers.http-01.acme.storage=/ssl/acme.json" + ################ END SSL configuration ################ + labels: + # Enable Traefik + - "traefik.enable=true" + # Set Network to use + - "traefik.docker.network=invoiceninja" + # Set service type + - "traefik.http.routers.traefik-dashbaord.service=api@internal" + # Load dynamic config from conf/*.yml + - "traefik.http.routers.traefik-dashbaord.middlewares=default@file,basic-auth@file" + # Define entrypint to use + - "traefik.http.routers.traefik-dashbaord.entrypoints=traefik-dashbaord" + # Define Hostname and path + - "traefik.http.routers.traefik-dashbaord.rule=Host(`${APP_URL_DOMAIN}`) && PathPrefix(`/api`,`/dashboard`)" + # Enable SSL/TLS + - "traefik.http.routers.traefik-dashbaord.tls=true" + - "traefik.http.routers.traefik-dashbaord.tls.certResolver=http-01" + + server: + labels: + # Enable Traefik + - "traefik.enable=true" + # Set Network to use + - "traefik.docker.network=invoiceninja" + # Load dynamic config + - "traefik.http.routers.ninja-nginx.middlewares=default@file" + # Service related labels + - "traefik.http.routers.ninja-nginx.entrypoints=websecure" + - "traefik.http.routers.ninja-nginx.rule=Host(`${APP_URL_DOMAIN}`)" + - "traefik.http.routers.ninja-nginx.tls=true" + - "traefik.http.routers.ninja-nginx.tls.certResolver=http-01" diff --git a/config/traefik/ssl/.gitkeep b/config/traefik/ssl-files/.gitkeep similarity index 100% rename from config/traefik/ssl/.gitkeep rename to config/traefik/ssl-files/.gitkeep diff --git a/env b/env index ebbf2e6..a7e6c63 100644 --- a/env +++ b/env @@ -59,7 +59,8 @@ MYSQL_DATABASE=ninja # APP_CIPHER=AES-256-CBC ## Traefik proxy vars +ACME_EMAIL=ssl-maintainer@domain.tld +# Cloudflare only vars CLOUDFLARE_EMAIL=cloudflare@domain.tld CLOUDFLARE_DNS_API_TOKEN=yourApiTokenHere CLOUDFLARE_DOMAIN=domain.tld -ACME_EMAIL=ssl-maintainer@domain.tld