Docker - Traefik - Wildcard Subdomain
Intro
I was wondering if you can have Wildcart Certs for certain Subdomain. Idea is to provide a Service with “myservice.auth.your.domain” which automatically requests Authentication, while the same Service “myservice.whitelist.your.domain” is reachable through some Whitelisted IP only.
As Traefik can Chain Middleware, but not implements some logic (If Whitelist -> ok, else do Basic Auth …), i have to build another solution.
let’s have a look
Prepare Folders
cd /your/traffic/rootfolder
mkdir -p config/dynamic
.env File
we need two variables, so let’s put them in the .env File
cat << 'EOF' > .env
DOMAIN="your.domain"
INFOMANIAK_ACCESS_TOKEN=2Bxxxxxxxxxxxxxxxxxxxx
EOF
DNS Provider
i used infomaniak as DNS Provider. you have to pick one from the list (https://doc.traefik.io/traefik/https/acme/#providers) and adapt accordingly. if you go with infomaniak and can’t find the page to create a new api token …
traefik.conf
we extend the existing traefik.conf with few lines for auth.your.domain and list.your.domain
cat << 'EOF' > docker-compose.yml
version: "3"
services:
  traefik:
    container_name: traefik
    hostname: traefik
    image: traefik:latest
    restart: always
    security_opt:
      - no-new-privileges:true
    ports:
      - 80:80/tcp
      - 443:443/tcp
    environment:
      - "TZ=Europe/Zurich"
      # DNS Provider
      - "INFOMANIAK_ACCESS_TOKEN=${INFOMANIAK_ACCESS_TOKEN}"
    volumes:
      - ./config/dynamic:/dynamic
      - ./config/traefik.yml:/traefik.yml
      - ./plugins-local/:/plugins-local/
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik-acme:/acme
    networks:
      - traefik
    labels:
      # Traefik
      traefik.enable: true
      traefik.http.routers.traefik.entrypoints: websecure
      traefik.http.routers.traefik.middlewares: simpleAuth@file
      traefik.http.routers.traefik.rule: Host(`traefik.${DOMAIN}`)
      traefik.http.routers.traefik.service: api@internal
      traefik.http.routers.traefik.tls.certresolver: infomaniakdns
      traefik.http.routers.traefik.tls: true
      # your.domain
      traefik.http.routers.traefik.tls.domains[0].main: "${DOMAIN}"
      traefik.http.routers.traefik.tls.domains[0].sans: "*.${DOMAIN}"
      # auth.your.domain  
      traefik.http.routers.traefik.tls.domains[1].main: "auth.${DOMAIN}"
      traefik.http.routers.traefik.tls.domains[1].sans: "*.auth.${DOMAIN}"
      # list.your.domain
      traefik.http.routers.traefik.tls.domains[2].main: "list.${DOMAIN}"
      traefik.http.routers.traefik.tls.domains[2].sans: "*.list.${DOMAIN}"
networks:
  traefik:
    name: traefik
volumes:
  traefik-acme:
    name: traefik-acme
EOF
Traefik.conf
cat << 'EOF' > config/traefik.yml
# Traefik static config
log:
  # PANIC, DEBUG, FATAL, ERROR, WARN, and INFO.
  level: DEBUG
api:
  dashboard: true
global:
  checkNewVersion: false
  sendAnonymousUsage: false
entrypoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
          permanent: true
  websecure:
    address: ":443"
certificatesResolvers:
  infomaniakdns:
    acme:
      email: docker@stoege.net
      storage: "/acme/acme.json"
      dnsChallenge:
        provider: infomaniak
        delayBeforeCheck: 60
providers:
  docker:
    exposedByDefault: false
    network: traefik
    watch: true
  file:
    directory: "./dynamic"
    watch: true
EOF
Prepare Middlware
cat << 'EOF' > config/dynamic/whitelists.yml
---
http:
  middlewares:
    blog-ipwhitelist:
      ipwhitelist:
        sourcerange:
          - 127.0.0.1
          - your.allowed.ip.address
EOF
cat << 'EOF' > config/dynamic/simpleauth.yml 
---
# user=myuser; echo "          - \"$(htpasswd -nB $user)\"" >> simpleauth.yml
http:
  middlewares:
    simpleAuth:
      basicAuth:
        users:
          - "myuser:$2y$05$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
EOF
Service
let’s fireup a Nginx Server, which will be reached under the following URL:
- https://nginx.your.domain -> public available
 - https://nginx.auth.your.domain -> same Service, with Basic Auth
 - https://nginx.list.your.domain -> same Service, with Whitelist
 
version: '3'
services:
  nginx_top:
    container_name: nginx_container_name
    hostname: nginx_hostname
    image: nginx
    networks:
      - traefik
    volumes:
    - ./templates:/etc/nginx/templates
    environment:
    - NGINX_HOST=nginx.${DOMAIN}
    - NGINX_PORT=80A
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.nginx.rule=Host(`nginx.${DOMAIN}`)"
      - "traefik.http.routers.nginx.tls=true"
      - "traefik.http.routers.nginx-auth.rule=Host(`nginx.auth.${DOMAIN}`)"
      - "traefik.http.routers.nginx-auth.tls=true"
      - "traefik.http.routers.nginx-auth.middlewares=simpleAuth@file"
      - "traefik.http.routers.nginx-list.rule=Host(`nginx.list.${DOMAIN}`)"
      - "traefik.http.routers.nginx-list.tls=true"
      - "traefik.http.routers.nginx-list.middlewares=blog-ipwhitelist@file"
networks:
  traefik:
    external: true
Test Config
Test the URL’s without any authentication and from an IP Address not whitelisted
$ curl -s -I https://blog.norma.li |grep HTTP
HTTP/2 200 
$ curl -s -I https://blog.auth.norma.li |grep HTTP
HTTP/2 401 
$ curl -s -I https://blog.list.norma.li |grep HTTP 
HTTP/2 403 
Test Basic Auth
$ curl -s -I -u "myuser:myfamouspassword" https://blog.auth.norma.li
HTTP/2 200 
accept-ranges: bytes
content-type: text/html; charset=utf-8
date: Sun, 23 Oct 2022 08:05:38 GMT
last-modified: Sun, 23 Oct 2022 07:54:39 GMT
content-length: 55832
Restart Docker Container
and what happens, when we bring a container down and up again while looking at the HTTP Response Code ?
$ while true; do timeout 1 curl -s -I https://blog.list.norma.li |grep -E "HTTP|Term" |ts; sleep 1; done 
Oct 23 09:54:23 HTTP/2 200 
Oct 23 09:54:25 HTTP/2 200 
Oct 23 09:54:26 HTTP/2 200 
Oct 23 09:54:27 HTTP/2 404 
Oct 23 09:54:28 HTTP/2 404 
Oct 23 09:54:29 HTTP/2 404 
Terminated 
Terminated 
Terminated 
Terminated 
Oct 23 09:54:39 HTTP/2 200 
Oct 23 09:54:40 HTTP/2 200 
Oct 23 09:54:41 HTTP/2 200 
here we are … :)
Any Comments ?
sha256: 8b9c51c93e5809946904cb52accbfe492ba35c5e53fef9328b7bac839a4f278d