Multi-SANs Cert with Let's Encrypt and HSTS

Do you want to request a feature or report a bug ?

Not a feature request. Likely a bug [user error] in my config files.

What did you do?

I'm hosting Traefik in a Debian VM on DigitalOcean. This will be for production use.

I have the following objectives for Traefik:

  • Host Apache blog and Gitlab on different subdomains.
  • Automagically generate and control Let's Encrypt certificates. Either a multi-SANs cert, or multiple single SANs certs. Any recommendation?
  • HSTS everywhere. I would love to totally disable port 80. For now I still have a redirect middleware. I'm fairly certain my HSTS middleware is mis-specified...
  • Ideally A+ scores on https://www.ssllabs.com/ and https://securityheaders.com

Since this is publicly accessible, I'm leaving the API disabled.

I have created an Apache Dockerfile, an empty acme.json file, an .env file, and config files for Traefik. The project directory appears as follows:

$ ls -laF ./*
-rw------- 1 root root   79 Apr  5 18:53 .env
-rw-r--r-- 1 root root 1752 Apr  5 20:58 ./docker-compose.yml

./apache:
total 12
drwxr-xr-x 2 root root 4096 Apr  5 20:57 ./
drwxr-xr-x 7 root root 4096 Apr  5 20:58 ../
-rw-r--r-- 1 root root  157 Apr  5 20:57 Dockerfile

./gitlab:
total 8
drwxr-xr-x 2 root root 4096 Apr  5 20:57 ./
drwxr-xr-x 7 root root 4096 Apr  5 20:58 ../

./letsencrypt:
total 8
drwxr-xr-x 2 root root 4096 Apr  5 20:57 ./
drwxr-xr-x 7 root root 4096 Apr  5 20:58 ../
-rw------- 1 root root    0 Apr  5 20:57 acme.json

./logs:
total 8
drwxr-xr-x 2 root root 4096 Apr  5 20:57 ./
drwxr-xr-x 7 root root 4096 Apr  5 20:58 ../

./traefik:
total 12
drwxr-xr-x 2 root root 4096 Apr  5 20:58 ./
drwxr-xr-x 7 root root 4096 Apr  5 20:58 ../
-rw-r--r-- 1 root root 1794 Apr  5 20:58 traefik.toml

Then I brought up a daemonized container using cd /etc/compose sudo docker-compose up -d --build --force-recreate.

What did you expect to see?

I expected to see a json certificate chain in ./letsencrypt/acme.json, and that same certificate served at https://blog.WEBSITE and https://git.WEBSITE.

Note that, WEBSITE and EMAIL are defined in ./env.

What did you see instead?

Default Traefik certificate. Also, cat ./letsencrypt/acme.json is empty. Also the sites at https://blog.WEBSITE and https://git.WEBSITE are returning 404 error.

Output of traefik version : ( What version of Traefik are you using? )

$ sudo docker run traefik version
Version:      2.2.0
Codename:     chevrotin
Go version:   go1.14.1
Built:        2020-03-25T17:32:57Z
OS/Arch:      linux/amd64

What is your environment & configuration (arguments, toml, provider, platform, ...)?

My ./traefik/traefik.toml is as follows:

[global]
checkNewVersion = true
sendAnonymousUsage = false

[entryPoints]
  [entryPoints.web-secure]
  address = ":443"

[providers]

  [providers.file]
  filename = "/config/traefik.toml"
  watch = true

  [providers.docker]
  endpoint = "unix:///var/run/docker.sock"
  exposedByDefault = false
  network = "public"
  swarmMode = false


[http.routers]
  [http.routers.web-secure]
    rule = "Host(`${WEBSITE}`)"
    middlewares = ["hsts-header"]

    [http.routers.web-secure.tls]
      options = "modern"

[http.middlewares]
  [http.middlewares.redirect-to-https.redirectScheme]
    scheme = "https"
  [http.middlewares.hsts-header.headers]
    [http.middlewares.hsts-header.headers.customResponseHeaders]
      Strict-Transport-Security = "max-age=63072000"


[tls.options]
  [tls.options.modern]
    minVersion = "VersionTLS13"
    cipherSuites = [
      "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
      "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
      "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
      "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
      "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
    ]

[certificatesResolvers]
  [certificatesResolvers.default]
    [certificatesResolvers.default.acme]
    email = "${EMAIL}"
    storage = "/letsencrypt/acme.json"
    onHostRule = true
    entryPoint = "web-secure"
    caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
      [certificatesResolvers.default.acme.tlsChallenge]
      [[certificatesResolvers.default.acme.domains]]
      main = "${WEBSITE}"
      sans = ["blog.${WEBSITE}","git.${WEBSITE}"]

    
[api]
insecure = false
dashboard = false
debug = true

My ./docker-compose.yml file is as follows:

version: "3.4"


networks:
  public:
    driver: bridge


services:
  traefik:
    container_name: traefik
    image: traefik:v2.2
    restart: always
    command:
      - --providers.file.directory=/config
      - --entrypoints.web-secure.address=:443
    labels:
      - "traefik.http.routers.http-catchall.entrypoints=web-secure"
    networks:
      - public
    ports:
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./traefik:/config:ro"
      - "./letsencrypt/acme.json:/letsencrypt/acme.json:ro"
      - "./logs:/logs"
    env_file:
      - "./.env"
    environment:
      - PUID=1000
      - PGID=1000


  blog:
    container_name: blog
    build:
      context: ./apache
    restart: always
    depends_on:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.blog.tls=true"
      - "traefik.http.routers.blog.entrypoints=web-secure"
      - "traefik.http.routers.blog.rule=Host(`blog.${WEBSITE}`)"
    networks:
      - public
    volumes:
      - type: bind
        source: /etc/blog
        target: /usr/local/apache2/htdocs
    environment:
      - "PUID=1000"
      - "PGID=1000"


  gitlab:
    container_name: gitlab
    image: gitlab/gitlab-ce:latest
    restart: always
    depends_on:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.git.tls=true"
      - "traefik.http.routers.git.entrypoints=web-secure"
      - "traefik.http.routers.git.rule=Host(`git.${WEBSITE}`)"
    networks:
      - public
    ports:
      - "22:22"
    volumes:
      - "./gitlab/config:/etc/gitlab"
      - "./gitlab/logs:/var/log/gitlab"
      - "./gitlab/data:/var/opt/gitlab"
    environment:
      PUID: "1000"
      PGID: "1000"

If applicable, please paste the log output in DEBUG level ( --log.level=DEBUG switch)

I definitely know there are issues with my traefik.toml, because I'm seeing this:

$ sudo docker logs -f traefik
time="2020-04-05T21:14:53Z" level=info msg="Configuration loaded from flags."
time="2020-04-05T21:14:53Z" level=error msg="the service is missing on the router" routerName=web-secure@file entryPointName=web-secure
time="2020-04-05T21:14:53Z" level=error msg="the service is missing on the router" entryPointName=web-secure routerName=web-secure@file

Hello,

In the v2, the dynamic configuration and the static configuration must be defined in separated files:


The main goal of Traefik is to manage dynamic architecture:

  • a container appears, Traefik detect it and create the configuration (based on labels)
  • a container disappears, Traefik detect it and remove the configuration

The file provider breaks this dynamic behavior.
The file provider, for the routing (services, routers), has been introduced for only old architecture (VMs, bare metal servers, ...)

Recommend read:


also you cannot use CLI flags and file at the same time to define the static configuration, because they are mutually exclusive.

There are three different, mutually exclusive (e.g. you can use only one at the same time), ways to define static configuration options in Traefik:

  1. In a configuration file
  2. In the command-line arguments
  3. As environment variables

Okay. It looks like I can do all of this inside the docker-compose.yml.

With regards to HSTS, I found this useful link: https://docs.traefik.io/v2.2/middlewares/headers/#using-security-headers, using the Docker examples. For example:

labels:
  - "traefik.http.middlewares.testHeader.headers.framedeny=true"
  - "traefik.http.middlewares.testHeader.headers.sslredirect=true"

Should ^this^ be added to the Traefik service, other services (i.e., Blog and Gitlab), or both?

Thanks!

it's a middleware definition, you need to reference this on the different routers related to your services.