Dale M. Picou, Jr.

Traefik v2 Secure TLS and Header Configuration with Docker Provider

Introduction

Docker and I have had a mixed relationship until discovering Traefik. In the not so distance future, I will fully review how my docker environment is set-up in detail but for this article, I will focus on a single aspect.

I came across this site, some may know it, SSLLabs. I scanned my Docker hosted sites, and to my dismay, they had a ranking of B. This was unacceptable!

Google is now ranking sites based on how secure they are (HTTPS as a ranking signal) and if you want that top search hit, you must use TLS. With free services like Let’s Encrypt, there are no reasons why all sites shouldn’t be running TLS.

Great, so just enable TLS and you are golden, right? Nope. There are other considerations such as TLS version, Ciphers, and Headers. In this article I will review all the different secure recommendations from SSLLabs and how to apply them using Traefik.

Rather jump right to the solution? Check out my docker image for Traefik: djpic/traefik.

TLS version is important!

There are multiple versions of TLS (formally known as SSL). Instead of going into every version, let’s just focus on what is relevant to this article. The current two versions that are considered secure are TLS 1.2 and TLS 1.3. Perfect! Just enable those two….nope. Each version has their own ciphers suites, in which many are considered insecure. Again to save some time, the recommenced ciphers for each version are below.

Recommended ciphers for TLSv1.3:

Recommended ciphers for TLSv1.2:

The visitor’s browser and remote web server negotiate which version and cipher is to be used for a particular connection. Unfortunately, not all legacy browsers support these secure protocols and ciphers. When a visitor uses a legacy browser, the web page / application will not load. Because of this lack of support, some administrators choose to allow the out-dated protocols and ciphers. Congratulations! This site only uses the recommend ciphers and versions therefore if you can view this site, your browser supports these ciphers.

Headers count too!

So we have the Let’s Encrypt certificate, TLS v1.3 enabled, and have the best ciphers chosen. Are we done? Of course not! During my research I found a few recommended response headers to be included. These headers are:

All of these headers are not required for an A+ SSLLabs report. The minimal header for the SSLLabs report is HTTP Strict Transport Security or HSTS. I personally would still look at enabling the other headers whenever possible. For more information on these headers, a great resource is OWASP Secure Headers Project.

DNS CAA Record

Another requirement it seems for a secure report from SSLLabs is a DNS CAA record. This requirement is pretty straight forward. Place a CAA DNS record specifying which certificate authorities are permitted to issue certificates for a domain. However there is a catch, this requires your DNS provider to support CAA entries.

There are three different entry types for the CAA records: issue, issuewild, and iodef.

The issue record is the only one required. This issue record specifies which certificate authorities are authorized to issue a certificate of any type. Check with the issuing certificate authority for the correct value.

The issuewild record is used to specify which certificate authorities are authorized to issue wild card certificates and only wild card certificates. When this record it present, the certificate authority with an issue record is no longer allowed to issue wild card certificates.

And finally, the iodef record specifies a URL where policy violations are reported. Commonly the iodef entry is an email address.

Here is an example of the DNS CAA entry for this demo site:

For a list of DNS providers that support DNS CAA records, SSLMate maintains a list: Who supports CAA Records?

Another great tool from SSLMate is the CAA Record Helper. This tool generates the CAA records for you. Just choose your selected certificate authority along with the options you like to include.

The Problem

Finding examples on how to configure TLS Versions and headers with Nginx or Apache are readily available with a quick google search. However, I was unable to find a single all in one solution for Traefik. This required me to complete many google searches and testing until I came to an elegant solution.

The biggest issue I came across was with the TLS configuration. Traefik has two different methods of configuration: static and dynamic. Static is pretty straight forward and as the name suggests, requires a manually configured static configuration file. Dynamic is where it gets tricky.

Dynamic configuration can be done via docker labels and/or files. The problem came when trying to configure the TLS options. Even though the TLS options are configured via dynamic configuration, there are no associated docker labels.

Traefik v2 Docker Label Configuration

Even though the docker label configuration does not include the TLS options as of Traefik v2.4, labels can be used to configure the secure headers. This is done with defining a middleware that configures those options.

Here is a single middleware defined via labels that contains all the required secure response headers:

- "traefik.http.middlewares.secure-headers.headers.sslredirect=true"
- "traefik.http.middlewares.secure-headers.headers.framedeny=true"
- "traefik.http.middlewares.secure-headers.headers.stsincludesubdomains=true"
- "traefik.http.middlewares.secure-headers.headers.stspreload=true"
- "traefik.http.middlewares.secure-headers.headers.stsseconds=63072000"
- "traefik.http.middlewares.secure-headers.headers.contenttypenosniff=true"
- "traefik.http.middlewares.secure-headers.headers.accesscontrolallowmethods=GET,POST"
- "traefik.http.middlewares.secure-headers.headers.accesscontrolalloworiginlist=foobar.com"
- "traefik.http.middlewares.secure-headers.headers.accesscontrolmaxage=100"
- "traefik.http.middlewares.secure-headers.headers.addvaryheader=true"
- "traefik.http.middlewares.secure-headers.headers.contentsecuritypolicy=script-src 'self'"
- "traefik.http.middlewares.secure-headers.headers.referrerpolicy=origin-when-cross-origin"

There is no need to manually create this middleware for every container. The middleware can be defined once, for example in a Traefik docker-compose file. Every container can then call that middleware.

Traefik v2 Dynamic Configuration

Here is where I myself learned a lot more about Traefik v2 configuration. As stated before, the options to limit TLS versions and ciphers is not available in docker labels. But it is configurable by the dynamic file provider. And yes, Traefik v2 can have multiple configuration providers.

First step is to create a dynamic configuration file. Below are the TLS options in the dynamic configuration file I use. By default the configuration allows a minimum version of TLS v1.2 with all the recommended secure ciphers. With the default setting, all HTTPS routers will use at least TLS v1.2 without any other configuration. Also included is an option that allows only TLS v1.3. This option must be manually configured. There is an example below on how to do this with a docker label.

tls:
  options:
    default:
      minVersion: VersionTLS12
      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
    tlsv13only:
      minVersion: VersionTLS13
- "traefik.http.routers.router0.tls.options=tlsv13only"

There is another bonus to the dynamic configuration file however. Any other configuration that is configurable with docker labels can be defined in the dynamic configuration file. Such as the secure headers middleware. For example:

http:
  middlewares:
    secure-headers:
      headers:
        sslRedirect: true
        frameDeny: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 63072000
        contentTypeNosniff: true
        accessControlAllowMethods:
          - GET
          - POST
        accessControlAllowOriginList:
          - https://foobar.net
          - https://www.foobar.net
        accessControlMaxAge: 100
        addVaryheader: true
        contentSecurityPolicy: script-src 'self'
        referrerPolicy: origin-when-cross-origin

To use these dynamic configuration file defined middleware and options, there is a slight catch. When assigning, the name of the options needs to be postfixed with @file. For example:

- "traefik.http.routers.site01.middlewares=secure-headers@file"

Based on these findings, managing and using Traefik v2 can be simplified.

The Easy Button: Dale's Traefik v2 Docker Image

Don’t want to deal with configuring all these options and just want a version of Traefik v2 that runs with these options pre-defined? Meet my Traefik docker image. This image has everything preconfigured. The dynamic configuration file is already included with all the options to secure the routers along with a few extras.

To use this image, pull djpic/traefik. The options this image includes are as follows:

Conclusion

Experimenting and researching these items was a major success. The best part is all my applications and sites are receiving an A+ on SSLLabs. I also do hope this has helped some out there when trying to find the answer on how to secure a site with Traefik.

Disclaimer: The contents of this article are of my opinion and based on my experience. Before applying any of the concepts or suggestions in this article, complete your own independent research and testing. By reading this article, the reader agrees that Dale M. Picou, Jr. shall not be held liable for any negative impact when applying these tutorials, concepts, and/or suggestions.