V2 Config for Self-Signed Certificates with Redirect

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

I suspect I have an error in my traefik.yml that causes TLS parameters specified therein to be ignored.

What did you do?

I'm trying to take security seriously as I refactor my Traefik config from v1 to v2. I want to serve numerous apps on a private network and need to encrypt communications where possible. To mock up a sound Traefik config, I'm testing 2 whoami apps, one with TLS and one without. I've followed the steps outlined at Protect the Docker daemon socket to issue self-signed CA, server, and client certificates and keys.

My development directory (named traefik) containing all config files and certs has the following structure and permissions:

$ ls -la ./*
-rw-r--r-- 1 user user 1425 Dec 22 23:19 ./docker-compose.yml
-rw-r--r-- 1 user user 1392 Dec 22 23:27 ./traefik.yml

./certs:
total 36
drwxr-xr-x 2 root root 4096 Dec 22 14:39 .
drwxr-xr-x 3 user user 4096 Dec 22 23:34 ..
-r-------- 1 root root 3326 Dec 22 14:36 ca-key.pem
-r--r--r-- 1 root root 2061 Dec 21 21:41 ca.pem
-rw-r--r-- 1 root root   41 Dec 21 21:57 ca.srl
-r--r--r-- 1 root root 1850 Dec 21 21:57 cert.pem
-r-------- 1 root root 3243 Dec 22 14:38 key.pem
-r--r--r-- 1 root root 1915 Dec 21 21:52 server-cert.pem
-r-------- 1 root root 3247 Dec 22 14:39 server-key.pem

I'm trying hard to put as much configuration into the treafik.yml file as possible, rather than jamming everything into docker-compose.yml (my old strategy). Most relevant errors are probably in my traefik.yml, further below. My docker-compose.yml config is as follows:

version: "3.4"


networks:
  public:
    driver: bridge


services:
  traefik:
    container_name: traefik
    image: traefik:v2.1
    restart: always
    command:
      - "--log.level=DEBUG"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web-secure.address=:443"
    labels:
      - "traefik.enable=true"
    ports:
      - "80:80"
      - "443:443"
    networks:
      - public
    volumes:
      - "./traefik.yml:/etc/traefik/traefik.yml"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./certs:/certs:ro"
    environment:
      - "PUID=1000"
      - "PGID=1000"


  whoami_1:
    container_name: whoami_1
    image: containous/whoami
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami_1.rule=Host(`whoami_1.localhost`)"
      - "traefik.http.routers.whoami_1.entrypoints=web"
    networks:
      - public
    environment:
      - "PUID=1000"
      - "PGID=1000"


  whoami_2:
    container_name: whoami_2
    image: containous/whoami
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami_2.rule=Host(`whoami_2.localhost`)"
      - "traefik.http.routers.whoami_2.entrypoints=web-secure"
    networks:
      - public
    environment:
      - "PUID=1000"
      - "PGID=1000"

What did you expect to see?

I expect, when visiting http://whoami_2.localhost, to be redirected to https://whoami_2.localhost, and see the certificate from ./certs/cert.pem on the localhost (which should be at /certs/cert.pem in the Traefik container).

What did you see instead?

For both webpages, http://whoami_1.localhost and https://whoami_2.localhost, I see:

404 page not found

At https://whoami_2.localhost, I see the Traefik Default Certificate.

If I comment out the traefik.yml config from docker-compose.yml like so:

    volumes:
      #- "./traefik.yml:/etc/traefik/traefik.yml"

then http://whoami_1.localhost works as expected through the web entrypoint (not serving any certificate).

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

$ sudo docker run traefik version
Unable to find image 'traefik:latest' locally
latest: Pulling from library/traefik
Digest: sha256:a87b61f3254d03c4fcc0b994e2cb7af89abba8178f1fbec3bce3f4bdc080f8a6
Status: Downloaded newer image for traefik:latest
Version:      2.1.1
Codename:     cantal
Go version:   go1.13.5
Built:        2019-12-12T19:01:37Z
OS/Arch:      linux/amd64

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

My traefik.yml is as follows:

global:
  checkNewVersion: true
  sendAnonymousUsage: false

log:
  level: DEBUG

entryPoints:
  web:
    address: ":80"
  web-secure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: public
    swarmMode: false
    tls:
      insecureSkipVerify: false
      caOptional: false
      ca: "/certs/ca.pem"
      cert: "/certs/server-cert.pem" 
      key: "/certs/server-key.pem"

http:
  routers:

    # dynamic redirection: 
    router0:
      rule: "Host(`localhost`)"
      entryPoints: 
        - web
      middlewares:
        - redirect

    # secure router:
    router1:
      rule: "Host(`localhost`)"
      entryPoints:
        - web-secure
      tls:
        options: myTLSOptions

  middlewares:
    redirect:
      redirectScheme:
        scheme: https

tls:
  certificates:
    - certFile: "/certs/cert.pem"
      keyFile: "/certs/key.pem"
  options:
    myTLSOptions:
      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

serversTransport:
  insecureSkipVerify: false
  rootCAs:
    - "/certs/ca.pem"

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

sudo docker logs -f traefik
time="2019-12-23T08:09:33Z" level=info msg="Configuration loaded from flags."
time="2019-12-23T08:09:33Z" level=info msg="Traefik version 2.1.1 built on 2019-12-12T19:01:37Z"
time="2019-12-23T08:09:33Z" level=debug msg="Static configuration loaded {\"global\":{\"checkNewVersion\":true},\"serversTransport\":{\"maxIdleConnsPerHost\":200},\"entryPoints\":{\"web\":{\"address\":\":80\",\"transport\":{\"lifeCycle\":{\"graceTimeOut\":10000000000},\"respondingTimeouts\":{\"idleTimeout\":180000000000}},\"forwardedHeaders\":{}},\"web-secure\":{\"address\":\":443\",\"transport\":{\"lifeCycle\":{\"graceTimeOut\":10000000000},\"respondingTimeouts\":{\"idleTimeout\":180000000000}},\"forwardedHeaders\":{}}},\"providers\":{\"providersThrottleDuration\":2000000000,\"docker\":{\"watch\":true,\"endpoint\":\"unix:///var/run/docker.sock\",\"defaultRule\":\"Host(`{{ normalize .Name }}`)\",\"swarmModeRefreshSeconds\":15000000000}},\"log\":{\"level\":\"DEBUG\",\"format\":\"common\"}}"
time="2019-12-23T08:09:33Z" level=info msg="\nStats collection is disabled.\nHelp us improve Traefik by turning this feature on :)\nMore details on: https://docs.traefik.io/v2.0/contributing/data-collection/\n"
time="2019-12-23T08:09:33Z" level=info msg="Starting provider aggregator.ProviderAggregator {}"
time="2019-12-23T08:09:33Z" level=debug msg="Start TCP Server" entryPointName=web
time="2019-12-23T08:09:33Z" level=info msg="Starting provider *traefik.Provider {}"
time="2019-12-23T08:09:33Z" level=info msg="Starting provider *docker.Provider {\"watch\":true,\"endpoint\":\"unix:///var/run/docker.sock\",\"defaultRule\":\"Host(`{{ normalize .Name }}`)\",\"swarmModeRefreshSeconds\":15000000000}"
time="2019-12-23T08:09:33Z" level=debug msg="Start TCP Server" entryPointName=web-secure
time="2019-12-23T08:09:33Z" level=debug msg="Configuration received from provider internal: {\"http\":{},\"tcp\":{},\"tls\":{}}" providerName=internal
time="2019-12-23T08:09:33Z" level=debug msg="No default certificate, generating one"
time="2019-12-23T08:09:33Z" level=debug msg="Provider connection established with docker 19.03.5 (API 1.40)" providerName=docker
time="2019-12-23T08:09:33Z" level=debug msg="Configuration received from provider docker: {\"http\":{\"routers\":{\"traefik-traefik\":{\"service\":\"traefik-traefik\",\"rule\":\"Host(`traefik-traefik`)\"},\"whoami_1\":{\"entryPoints\":[\"web\"],\"service\":\"whoami-1-traefik\",\"rule\":\"Host(`whoami_1.localhost`)\"},\"whoami_2\":{\"entryPoints\":[\"web-secure\"],\"service\":\"whoami-2-traefik\",\"rule\":\"Host(`whoami_2.localhost`)\"}},\"services\":{\"traefik-traefik\":{\"loadBalancer\":{\"servers\":[{\"url\":\"http://172.28.0.3:80\"}],\"passHostHeader\":true}},\"whoami-1-traefik\":{\"loadBalancer\":{\"servers\":[{\"url\":\"http://172.28.0.4:80\"}],\"passHostHeader\":true}},\"whoami-2-traefik\":{\"loadBalancer\":{\"servers\":[{\"url\":\"http://172.28.0.2:80\"}],\"passHostHeader\":true}}}},\"tcp\":{}}" providerName=docker
time="2019-12-23T08:09:33Z" level=debug msg="No entryPoint defined for this router, using the default one(s) instead: [web web-secure]" routerName=traefik-traefik@docker
time="2019-12-23T08:09:33Z" level=debug msg="Creating middleware" middlewareType=Pipelining entryPointName=web routerName=whoami_1@docker serviceName=whoami-1-traefik middlewareName=pipelining
time="2019-12-23T08:09:33Z" level=debug msg="Creating load-balancer" entryPointName=web routerName=whoami_1@docker serviceName=whoami-1-traefik
time="2019-12-23T08:09:33Z" level=debug msg="Creating server 0 http://172.28.0.4:80" serviceName=whoami-1-traefik serverName=0 entryPointName=web routerName=whoami_1@docker
time="2019-12-23T08:09:33Z" level=debug msg="Added outgoing tracing middleware whoami-1-traefik" routerName=whoami_1@docker middlewareName=tracing middlewareType=TracingForwarder entryPointName=web
time="2019-12-23T08:09:33Z" level=debug msg="Creating middleware" serviceName=traefik-traefik entryPointName=web middlewareName=pipelining middlewareType=Pipelining routerName=traefik-traefik@docker
time="2019-12-23T08:09:33Z" level=debug msg="Creating load-balancer" serviceName=traefik-traefik entryPointName=web routerName=traefik-traefik@docker
time="2019-12-23T08:09:33Z" level=debug msg="Creating server 0 http://172.28.0.3:80" routerName=traefik-traefik@docker serviceName=traefik-traefik serverName=0 entryPointName=web
time="2019-12-23T08:09:33Z" level=debug msg="Added outgoing tracing middleware traefik-traefik" routerName=traefik-traefik@docker middlewareName=tracing middlewareType=TracingForwarder entryPointName=web
time="2019-12-23T08:09:33Z" level=debug msg="Creating middleware" entryPointName=web middlewareName=traefik-internal-recovery middlewareType=Recovery
time="2019-12-23T08:09:33Z" level=debug msg="Creating middleware" entryPointName=web-secure routerName=whoami_2@docker serviceName=whoami-2-traefik middlewareName=pipelining middlewareType=Pipelining
time="2019-12-23T08:09:33Z" level=debug msg="Creating load-balancer" routerName=whoami_2@docker serviceName=whoami-2-traefik entryPointName=web-secure
time="2019-12-23T08:09:33Z" level=debug msg="Creating server 0 http://172.28.0.2:80" entryPointName=web-secure routerName=whoami_2@docker serviceName=whoami-2-traefik serverName=0
time="2019-12-23T08:09:33Z" level=debug msg="Added outgoing tracing middleware whoami-2-traefik" middlewareName=tracing middlewareType=TracingForwarder entryPointName=web-secure routerName=whoami_2@docker
time="2019-12-23T08:09:33Z" level=debug msg="Creating middleware" entryPointName=web-secure middlewareName=traefik-internal-recovery middlewareType=Recovery
time="2019-12-23T08:09:33Z" level=debug msg="No default certificate, generating one"
time="2019-12-23T08:11:23Z" level=debug msg="Serving default certificate for request: \"whoami_2.localhost\""
time="2019-12-23T08:11:23Z" level=debug msg="http: TLS handshake error from 172.28.0.1:45326: remote error: tls: bad certificate"
time="2019-12-23T08:11:25Z" level=debug msg="Serving default certificate for request: \"whoami_2.localhost\""
time="2019-12-23T08:13:31Z" level=debug msg="vulcand/oxy/roundrobin/rr: begin ServeHttp on request" Request="{\"Method\":\"GET\",\"URL\":{\"Scheme\":\"\",\"Opaque\":\"\",\"User\":null,\"Host\":\"\",\"Path\":\"/\",\"RawPath\":\"\",\"ForceQuery\":false,\"RawQuery\":\"\",\"Fragment\":\"\"},\"Proto\":\"HTTP/1.1\",\"ProtoMajor\":1,\"ProtoMinor\":1,\"Header\":{\"Accept\":[\"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"],\"Accept-Encoding\":[\"gzip, deflate\"],\"Accept-Language\":[\"en-US,en;q=0.5\"],\"Cache-Control\":[\"max-age=0\"],\"Connection\":[\"keep-alive\"],\"Dnt\":[\"1\"],\"Upgrade-Insecure-Requests\":[\"1\"],\"User-Agent\":[\"Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0\"],\"X-Forwarded-Host\":[\"whoami_1.localhost\"],\"X-Forwarded-Port\":[\"80\"],\"X-Forwarded-Proto\":[\"http\"],\"X-Forwarded-Server\":[\"5895aa373195\"],\"X-Real-Ip\":[\"172.28.0.1\"]},\"ContentLength\":0,\"TransferEncoding\":null,\"Host\":\"whoami_1.localhost\",\"Form\":null,\"PostForm\":null,\"MultipartForm\":null,\"Trailer\":null,\"RemoteAddr\":\"172.28.0.1:52470\",\"RequestURI\":\"/\",\"TLS\":null}"
time="2019-12-23T08:13:31Z" level=debug msg="vulcand/oxy/roundrobin/rr: Forwarding this request to URL" Request="{\"Method\":\"GET\",\"URL\":{\"Scheme\":\"\",\"Opaque\":\"\",\"User\":null,\"Host\":\"\",\"Path\":\"/\",\"RawPath\":\"\",\"ForceQuery\":false,\"RawQuery\":\"\",\"Fragment\":\"\"},\"Proto\":\"HTTP/1.1\",\"ProtoMajor\":1,\"ProtoMinor\":1,\"Header\":{\"Accept\":[\"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"],\"Accept-Encoding\":[\"gzip, deflate\"],\"Accept-Language\":[\"en-US,en;q=0.5\"],\"Cache-Control\":[\"max-age=0\"],\"Connection\":[\"keep-alive\"],\"Dnt\":[\"1\"],\"Upgrade-Insecure-Requests\":[\"1\"],\"User-Agent\":[\"Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0\"],\"X-Forwarded-Host\":[\"whoami_1.localhost\"],\"X-Forwarded-Port\":[\"80\"],\"X-Forwarded-Proto\":[\"http\"],\"X-Forwarded-Server\":[\"5895aa373195\"],\"X-Real-Ip\":[\"172.28.0.1\"]},\"ContentLength\":0,\"TransferEncoding\":null,\"Host\":\"whoami_1.localhost\",\"Form\":null,\"PostForm\":null,\"MultipartForm\":null,\"Trailer\":null,\"RemoteAddr\":\"172.28.0.1:52470\",\"RequestURI\":\"/\",\"TLS\":null}" ForwardURL="http://172.28.0.4:80"
time="2019-12-23T08:13:31Z" level=debug msg="vulcand/oxy/roundrobin/rr: completed ServeHttp on request" Request="{\"Method\":\"GET\",\"URL\":{\"Scheme\":\"\",\"Opaque\":\"\",\"User\":null,\"Host\":\"\",\"Path\":\"/\",\"RawPath\":\"\",\"ForceQuery\":false,\"RawQuery\":\"\",\"Fragment\":\"\"},\"Proto\":\"HTTP/1.1\",\"ProtoMajor\":1,\"ProtoMinor\":1,\"Header\":{\"Accept\":[\"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"],\"Accept-Encoding\":[\"gzip, deflate\"],\"Accept-Language\":[\"en-US,en;q=0.5\"],\"Cache-Control\":[\"max-age=0\"],\"Connection\":[\"keep-alive\"],\"Dnt\":[\"1\"],\"Upgrade-Insecure-Requests\":[\"1\"],\"User-Agent\":[\"Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0\"],\"X-Forwarded-Host\":[\"whoami_1.localhost\"],\"X-Forwarded-Port\":[\"80\"],\"X-Forwarded-Proto\":[\"http\"],\"X-Forwarded-Server\":[\"5895aa373195\"],\"X-Real-Ip\":[\"172.28.0.1\"]},\"ContentLength\":0,\"TransferEncoding\":null,\"Host\":\"whoami_1.localhost\",\"Form\":null,\"PostForm\":null,\"MultipartForm\":null,\"Trailer\":null,\"RemoteAddr\":\"172.28.0.1:52470\",\"RequestURI\":\"/\",\"TLS\":null}"

Hello,

you have to activate the TLS on the router for whoami_2.

  whoami_2:
    container_name: whoami_2
    image: containous/whoami
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami_2.rule=Host(`whoami_2.localhost`)"
      - "traefik.http.routers.whoami_2.entrypoints=web-secure"
      - "traefik.http.routers.whoami_2.tls=true"

Thanks for the quick reply.

This works for me, but only if I comment out my traefik.yml config file. When doing so, the TLS site at whoami_2.localhost works (no longer 404 page not found), but it is still being served with the Default Traefik Certificate.

Is there some problem with my traefik.yml that is preventing my self-signed certificates in ./certs from being used?

Well, you're defining certs in the docker section AND a seperate tls section of your traefik.yml. Surely it only needs one entry? (I'm no expert though).