I'm looking to replace my SWAG (Nginx-based) reverse proxy with Traefik, however I'm having some issues with chaining basicauth and security-header middlewares together, it appears I can only get one of the middlewares to function at a time, not both.
I can get an A+ rating at securityheaders.io when they're running correctly, however the current Content Security Policy security headers break Portainer formatting, and I'm not exactly sure where I need to make additional changes... would it be in contentSecurityPolicy or accessControlAllowOriginList ?
Also appreciate a check of my current configurations, as I want to have access to the Traefik dashboard externally, however I think I'm also exposing the raw API, which I'd prefer not to do - I understand I need to make a change to the "insecure" setting, however then the dashboard doesn't appear to work.
Have added my current working configs for reference - appreciate any assistance.
Docker Compose:
networks:
mediastack:
name: mediastack
driver: bridge
ipam:
driver: default
config:
- subnet: ${DOCKER_SUBNET:?err}
gateway: ${DOCKER_GATEWAY:?err}
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: always
security_opt:
- no-new-privileges=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ${FOLDER_FOR_DATA:?err}/portainer:/data
# ports:
# - "${WEBUI_PORT_PORTAINER:?err}:9443"
networks:
- mediastack
labels:
- traefik.enable=true
- traefik.docker.network=mediastack
# ROUTERS
- traefik.http.routers.portainer.rule=Host(`portainer.${CLOUDFLARE_DNS_ZONE:?err}`)
- traefik.http.routers.portainer.entrypoints=secureweb
- traefik.http.routers.portainer.tls=true
- traefik.http.routers.portainer.tls.certresolver=letsencrypt
- traefik.http.routers.portainer.tls.options=default
- traefik.http.routers.portainer.middlewares=portainer-auth@docker # No CSP Headers - They Break Portainer Formatting
# - traefik.http.routers.portainer.middlewares=security-headers@file,portainer-auth@docker
# SERVICES
- traefik.http.services.portainer.loadbalancer.server.scheme=http
- traefik.http.services.portainer.loadbalancer.server.port=9000
# MIDDLEWARES
- traefik.http.middlewares.portainer-auth.basicauth.users=${BASIC_WEB_AUTH:?err}
traefik:
image: traefik:latest
container_name: traefik
restart: unless-stopped
networks:
- mediastack
environment:
- TZ=${TIMEZONE:?err}
- CF_DNS_API_TOKEN=${CLOUDFLARE_DNS_API_TOKEN:?err}
ports:
- 80:80
- 443:443
- 8080:8080
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ${FOLDER_FOR_DATA:?err}/traefik:/etc/traefik:ro
- ${FOLDER_FOR_DATA:?err}/traefik/letsencrypt:/letsencrypt:rw
labels:
- traefik.enable=true
- traefik.docker.network=mediastack
# ROUTERS
- traefik.http.routers.traefik.rule=Host(`traefik.${CLOUDFLARE_DNS_ZONE:?err}`)
- traefik.http.routers.traefik.entrypoints=secureweb
- traefik.http.routers.traefik.tls=true
- traefik.http.routers.traefik.tls.certresolver=letsencrypt
- traefik.http.routers.traefik.tls.options=default
- traefik.http.routers.traefik.middlewares=security-headers@file,traefik-auth@docker
# SERVICES
- traefik.http.services.traefik.loadbalancer.server.scheme=http
- traefik.http.services.traefik.loadbalancer.server.port=8080
# MIDDLEWARES
- traefik.http.middlewares.traefik-auth.basicauth.users=${BASIC_WEB_AUTH:?err}
Docker ENV
DOCKER_SUBNET=172.28.10.0/24
DOCKER_GATEWAY=172.28.10.1
FOLDER_FOR_DATA=/docker/appdata
TIMEZONE=Europe/Zurich
WEBUI_PORT_PORTAINER=9000
BASIC_WEB_AUTH=portal:REDACTED
CLOUDFLARE_EMAIL=REDACTED
CLOUDFLARE_DNS_ZONE=ADD-DOMAIN-NAME-HERE
CLOUDFLARE_DNS_API_TOKEN=REDACTED
Static Config
############################################
############################################
#
# Traefik.YAML - Static Configuration File
#
############################################
############################################
global:
checkNewVersion: true
sendAnonymousUsage: true
log:
level: INFO
# level: DEBUG
accessLog:
filePath: /letsencrypt/access.log
format: json
api:
dashboard: true
insecure: true
entryPoints:
web:
address: :80
http:
redirections:
entryPoint:
to: secureweb
scheme: https
permanent: true
secureweb:
address: :443
http:
tls:
options: default
certResolver: letsencrypt
domains:
- main: ADD-DOMAIN-NAME-HERE
sans:
- "*.ADD-DOMAIN-NAME-HERE"
providers:
docker:
exposedByDefault: false
file:
directory: /etc/traefik
watch: true
certificatesResolvers:
letsencrypt:
acme:
storage: /letsencrypt/acme.json
keyType: EC384
caServer: https://acme-v02.api.letsencrypt.org/directory
dnsChallenge:
provider: cloudflare
resolvers:
- 1.1.1.1:53
- 1.0.0.1:53
propagation:
delayBeforeChecks: 2s
Dynamic Config
############################################
############################################
#
# Dynamic.YAML - Dynamic Configuration File
#
############################################
############################################
tls:
stores:
default:
defaultGeneratedCert:
resolver: letsencrypt
domain:
main: ADD-DOMAIN-NAME-HERE
sans:
- "*.ADD-DOMAIN-NAME-HERE"
options:
default:
minVersion: VersionTLS12
cipherSuites:
- TLS_AES_256_GCM_SHA384
- TLS_CHACHA20_POLY1305_SHA256
- TLS_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_SHA256
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
curvePreferences:
- CurveP521
- CurveP384
sniStrict: true
http:
middlewares:
security-headers:
headers:
accessControlAllowCredentials: true
accessControlAllowHeaders: "*"
accessControlAllowMethods:
- GET
- OPTIONS
- PUT
accessControlAllowOriginList:
- https://ADD-DOMAIN-NAME-HERE
- https://*.ADD-DOMAIN-NAME-HERE
accessControlMaxAge: 100
addVaryHeader: true
browserXssFilter: true
stsSeconds: 63072000
stsIncludeSubdomains: true
stsPreload: true
forceSTSHeader: true
customFrameOptionsValue: SAMEORIGIN
contentTypeNosniff: true
contentSecurityPolicy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'
referrerPolicy: strict-origin-when-cross-origin
permissionsPolicy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), bluetooth=()