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 theX-Frame-Options
HTTP header toDENY
, 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 untilstsSeconds
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 sayshttp://
. WithstsIncludeSubdomains
, this extends to subdomains, i.e., if HSTS is enabled forexample.com
including its subdomains, access towww.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!