Docker - Traefik - Wildcard Subdomain

Page content

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: [email protected]
      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:

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