Træfik and SSLLabs: Reach for A+

Web server security is an ongoing issue these days. Fortunately thanks to Let's Encrypt getting good-quality X.509 certificates for HTTPS is no longer a problem, but we still want to keep our servers up-to-date with the latest developments. For HTTPS-based web sites, the free “SSL Server Test” offered by Qualys' SSL Labs is a very helpful resource that lets you assess and optimise how your HTTPS server serves its resources. The test scrutinises your X.509 certificates, evaluates your server's support of TLS versions and possible vulnerabilities, checks through the available key exchange mechanisms and reports on the available cipher strength. All of this is compiled into a report-card type grade; obviously what you want to see there is “A+”.

My servers used to be pretty good but it was time for a re-assessment, and I was slightly miffed to see that the SSL Labs grade for most of my sites dropped to “B”, mostly on account of the fact that the server supports the TLS versions 1.0 and 1.1, both of which are considered obsolete and have been deprecated. These days you're supposed to confine yourself to TLS versions 1.2 and up; there are older browsers around that don't offer TLS version 1.2, but that is too bad. The other problem was that the server offered some cipher suites which are now considered weak.

The Pingucloud infrastructure funnels all of its web traffic through a single ingress server, Træfik, which handles TLS for all sites, including managing X.509 certificates (thanks to Let's Encrypt). The big advantage is that it is all pretty much automatic; Træfik will transparently obtain certificates for new sites and keep the existing ones up-to-date with no human intervention. It also figures out the desired HTTP configuration from services in Portainer by way of Docker Swarm service labels. All in all, a pretty sweet setup.

What we need to do in order to go for the vaunted “A+” designation is to add some TLS options to the Træfik configuration. This is part of Træfik's “dynamic” configuration, which so far we have fed from Docker Swarm labels, so the first thing to do is to allow for part of the dynamic configuration to come from a file:

[providers]
  
  [providers.docker]
  
  [providers.file]
    directory = "/dynamic"
    watch = true

(The watch = true is supposed to tell Træfik to watch for changes to files below /dynamic, and to reread these files if necessary.) This goes with a new /v/0/traefik2/dynamic directory, and a

volumes:
  
  - /v/0/traefik2/dynamic:/dynamic
  

declaration in docker-stack.yml, which maps the host directory into the Træfik container.

This lets us put some TLS directives into dynamic/tls.yml:

tls:
  options:
    default:
      minVersion: VersionTLS12
      cipherSuites:
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
        - TLS_AES_128_GCM_SHA256
        - TLS_AES_256_GCM_SHA384
        - TLS_CHACHA20_POLY1305_SHA256
      curvePreferences:
        - CurveP521
        - CurveP384
      sniStrict: true

Most of these are pretty self-explanatory; the sniStrict directive refuses connection requests that don't specify a “web site” name during connection setup. Since these TLS options are part of the default set of configuration settings, Træfik applies them to all HTTPS connections without further configuration.

We can also use Træfik's HTTP middleware to improve the security of our HTTPS handling even further. Here's a bunch of directives that we can add to the tls.yml file:

http:
  middlewares:
    secHeaders:
      headers:
        browserXssFilter: true
        contentTypeNosniff: true
        frameDeny: true
        sslRedirect: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 15768000 

Here's what some of these directives do:

  • The sslRedirect option will make sure that non-HTTPS requests are redirected to HTTPS.
  • frameDeny sets the X-Frame-Options HTTP header to DENY, which prevents our pages from being embedded in a frame on other sites (at least as long as people use browsers that play along).
  • browserXssFilter tries to mitigate “cross-site scripting” attacks in some older browsers. Today it should no longer be necessary, but it can't really hurt, either.
  • 'contentTypeNosniff` keeps browsers from trying to guess the data type of resources by looking at their content if the server doesn't specify a data type (shouldn't happen) or the server's data type looks iffy. More importantly, it enables “cross-origin read blocking” (CORB) for HTML, JSON, and XML resources. In a nutshell, this tries to prevent such resources from being used in an improper context in order to exploit security vulnerabilities in browsers.
  • The headers that are relevant to our SSL Labs score are the ones that deal with “HTTP Strict Transport Security” (HSTS). HSTS uses the Strict-Transport-Security HTTP header to tell browsers that they are only supposed to use HTTPS to access our sites, from the point in time of the request until stsSeconds seconds later (15768000 seconds are half a year, you do the math). This helps protect against “downgrade” attacks, where an attacker transparently converts an HTTPS connection to an HTTP connection – users can see that the site is not secure but can't tell whether this is by design or because of an attack. With HSTS, the site avers that it should use HTTPS, and from then on browsers only use HTTPS to access it even if the URL says http://. With stsIncludeSubdomains, this extends to subdomains, i.e., if HSTS is enabled for example.com including its subdomains, access to www.foo.example.com would be similarly protected.

This “middleware” is not enabled automatically, but a label like

labels:
  
  - "traefik.http.routers.static.middlewares=secHeaders@file"
  

will do the trick. Here, static is the router being configured, and secHeaders@file refers to the middleware definition called secHeaders in the file provider (which will dig it out of our tls.yml file). Note that this must be done for all sites that are supposed to take advantage of the protection.

The icing on the cake is a CAA DNS record for the domain in question. This asserts that certificates for the domain (and its subdomains) can only be issued by the named CA:

anselms.net  IN  CAA  128 issue "letsencrypt.org"

prevents other CAs from issuing certificates for names within anselms.net. (Note that this is an administrative limitation, not a technical one; CAs that play by the rules are supposed to check for a CAA record before issuing a certificate, but a “rogue” CA could still do it. Hopefully if that sort of thing got out, that CA's root certificate would be removed from everyone's trust stores.)

All of these together should push a site's rating to “A+” in the SSL Labs test (for now, anyway). Yay!