Improve Traefik's HTTPS Encryption with Qualys SSL Labs and testssl.sh

tldr; Encryption (and HTTPS) is a complicated beast, but we have to do our best to make sure that our sites run securely. With the help of tools like Qualys SSL Labs [1] or the open-source testssl.sh [2] I update my production Traefik installations to run with the most secure configurations as possible.

Disclaimer: I am not an encryption expert and will be the first to admit that there is a bit of "cargo culting." I am making the changes necessary to my configurations such that the SSL labs gives an "A" grade or better, and that I see no RED flags from testssl.sh. I provide no advice here but rather share the configuration and outcomes.

Before we begin

Before you start disabling old encryption protocial it is a good idea to know who your users are and what software stacks they use to connect with you. Older stacks such as IE11 on older Windows or Safari on older Macs may break. See all the red? Yeah...

SSL Labs report (after changes)

My current config

Below you see the "B" rating for the SSL configuration for simplecto.com. This is in part because the current configuration allows for older, less secure versions of TLS.

Old rating for simplecto.com on SSL Labs

My current config that earned the "B" rating

This is my current config. There are no options set for encryption settings, cyphers, protocols, etc. I simply take the defaults from Traefik. This may or may not be sufficient for you to pass a security or compliance audit.

I use a single docker-compose.yml file to deploy which keeps complexity to a minimum.

version: '3'

services:

    traefik:
        container_name: traefik
        image: traefik:v2.1
        command:
            - "--providers.docker=true"
            - "--entryPoints.web.address=:80"
            - "--entryPoints.websecure.address=:443"
            - "--certificatesResolvers.le.acme.email=secret_email@domain.com"
            - "--certificatesResolvers.le.acme.storage=acme.json"
            - "--certificatesResolvers.le.acme.tlsChallenge=true"
            - "--certificatesResolvers.le.acme.httpChallenge=true"
            - "--certificatesResolvers.le.acme.httpChallenge.entryPoint=web"
                        
        restart: always
        ports:
            - 80:80
            - 443:443
            - 8080:8080
        networks:
            - web
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock
            - ./acme.json:/acme.json
        labels:

            # Redirect all HTTP to HTTPS permanently
            - traefik.http.routers.http_catchall.rule=HostRegexp(`{any:.+}`)
            - traefik.http.routers.http_catchall.entrypoints=web
            - traefik.http.routers.http_catchall.middlewares=https_redirect
            - traefik.http.middlewares.https_redirect.redirectscheme.scheme=https
            - traefik.http.middlewares.https_redirect.redirectscheme.permanent=true

networks:
    web:
        external: true

What needs to change to get the "A"?

I went searching for answers and quickly found myself on the Containous community forums [3]. It was a post there that held the answers.

I must tell Traefik that I want TLS 1.3 (still experimental) and that the minimum version to support is TLSv1.2. Oh, and remember that pristine single-file config? Yeah, well, now I need to bind-mount some static files into the container. No biggie, but slightly more complex.

The new traefik_conf.yaml

This yaml file tells Traefik to:

  1. Make the minimum supported version of TLS 1.2 and to enable 1.3.
  2. Only accept connections where the hostname is specified (SNI)
  3. Support a specific list of Cyphers (the cargo-cult part for me)
tls:
  options:
    default:
      minVersion: VersionTLS12
      sniStrict : true
      cipherSuites:
        - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305

    mintls13:
      minVersion: VersionTLS13

Now that we have this, we need to get it into the container and tell Traefik to use it.

There are two lines we need to add to our docker-compose.yml file:

  1. "--providers.file.filename=/traefik_conf.yaml" under command:
  2. ./traefik_conf.yaml:/traefik_conf.yaml under volumes:
The two lines you need (in context of the docker-compose.yml)

Now all we need to do is docker-compose up -d and re-test!

Wohoo! – Ok, A-grade achieved.

Test locally with testssl.sh

As an alternative to SSL Labs there is a nice open-source tool called testssl.sh. You can run this locally and see way more information that you might know what to do with. You can test on any docker-enabled machine with:

docker run --rm -ti drwetter/testssl.sh example.com

There is quite a bit of output, but suffice to say you will get more than you are looking for :-)

Conclusion

With a small configuration change to Traefik we were able to move our SSL Labs overall rating from B to A. We had to add support for TLS1.3 and remove support for TLS 1.0 and 1.1.

Reference

  1. https://www.ssllabs.com/ssltest/analyze.html
  2. https://github.com/drwetter/testssl.sh
  3. https://community.containo.us/t/poor-rating-on-ssl-labs/2400/7
  4. https://docs.traefik.io/v2.0/providers/file/#filename

Update February 19, 2020

Reddit user /u/Fredouye left a helpful comment that would push the grading even higher. Indeed, we did improve our grade on key exchange to 100%.

traefik_conf.yaml

tls:
  options:
    default:
      minVersion: VersionTLS12
      curvePreferences:
        - secp521r1
        - secp384r1
      sniStrict: true

traefik's docker-compose.yml

traefik.http.middlewares.securedheaders.headers.forcestsheader: "true"
traefik.http.middlewares.securedheaders.headers.sslRedirect: "true"
traefik.http.middlewares.securedheaders.headers.STSPreload: "true"
traefik.http.middlewares.securedheaders.headers.ContentTypeNosniff: "true"
traefik.http.middlewares.securedheaders.headers.BrowserXssFilter: "true"
traefik.http.middlewares.securedheaders.headers.STSIncludeSubdomains: "true"
traefik.http.middlewares.securedheaders.headers.STSSeconds: "315360000"
Screenshot of the final grading

Thanks!

Show Comments