loading...

Going from an “A” to an “A+” on ssllabs.com

kritner profile image Russ Hammett Originally published at Medium on ・1 min read

In a previous post, I went into some (hopefully enough) detail on getting nginx working on docker with Letsencrypt. To wrap up the post, I had ran my website through ssllabs.com, and received an “A” score. “A”s are pretty great, but I was hoping it wouldn’t be too difficult to push that “A” up to an “A+”.

Luckily, it was pretty easy!

As a baseline here’s what I’m currently working with from SSL Labs:

ssllabs.com score

An “A”, not terrible. This is the first time I’m really going through this report, not really sure what should keep an eye on, but let’s take a look.

Green and yellow — this seems promising! “more info” under DNS CAA points me over to https://blog.qualys.com/ssllabs/2017/03/13/caa-mandated-by-cabrowser-forum — tldr seems to be add a record through your DNS that whitelists specific CAs, in my case let’s encrypt.

I use DNSimple as my DNS, and it was pretty straightforward to add the record (and I’m doing this as I type, so hopefully I don’t screw it up!).

DNSimple add CAA record

Okay, so the CAA record is taken care of, let’s see what else is going on in the SSL Labs report…

TLS and SSL are different methods of accomplishing HTTPS, TLS being the successor to SSL. From the looks of this, it’s a good thing my site supports TLS 1.1 and 1.2, a bad thing it supports TLS 1.0, and a good thing all flavors of SSL are not supported. TLS 1.0 being a bad thing to support makes sense — since it was end of lifed on 2018–06–30.

A large remainder of the warnings in the SSL Labs report seems to be centered around the fact that TLS 1.0 is supported:

So let’s see about getting TLS 1.0 disabled through my nginx config.

Nginx Updates

I recalled an “ssl.conf” file from the mapped volume in the previous post — though I had not made any changes to it up to this point. Since I will likely need to make changes to this file (it makes sense that SSL related settings would be here, no?), I will be adding this file to a new docker volume; so I can check in changes to the file.

A snippet of my original docker-compose file:

volumes:
 # used to have a copy of config on the DOCKER HOST, can mod from there
 - ${DOCKER_KRITNER_NGINX}:/config 
 # the actual configuration file to use for the reverse proxy (located in current repo directory)
 - ./nginxConfig/nginx.conf:/config/nginx/site-confs/default

The new addition to the volumes portion of the file:

# overrides for ssl conf from base image
 - ./nginxConfig/ssl.conf:/config/nginx/ssl.conf

This will allow my ssl.conf file to be mapped within the docker container, overwriting the original image’s ssl.conf; no API keys, passwords, etc in this file, so might as well keep its changes in source.

The entire docker-compose file now looks like:

version: '3.6'
services:

 nginx:
 image: linuxserver/letsencrypt
 ports:
 - "80:80"
 - "443:443"
 volumes:
 # used to have a copy of config on the DOCKER HOST, can mod from there
 - ${DOCKER_KRITNER_NGINX}:/config 
 # the actual configuration file to use for the reverse proxy (localted in current repo directory)
 - ./nginxConfig/nginx.conf:/config/nginx/site-confs/default
 # overrides for ssl conf from base image
 - ./nginxConfig/ssl.conf:/config/nginx/ssl.conf
 depends_on:
 - kritnerwebsite
 networks:
 - frontend
 container_name: nginx
 environment:
 - PUID=1001 # get on dockerhost through command "id <user>""
 - PGID=1001
 - EMAIL=kritner@gmail.com
 - URL=kritner.com
 - SUBDOMAINS=www
 - TZ=America/NewYork
 - VALIDATION=dns # using dns validation
 - DNSPLUGIN=dnsimple # via dnsimple, note there is additional configuration require separate from this file
 # - STAGING=true # this should be uncommented when testing for initial success, to avoid some rate limiting

kritnerwebsite:
 image: ${DOCKER_REGISTRY}/kritnerwebsite
 networks:
 - frontend
 expose:
 - "5000"
 restart: always
 container_name: kritnerwebsite

networks:
 frontend:

Next, onto the ssl.conf itself. The original ssl.conf is:

## Version 2018/05/31 - Changelog: https://github.com/linuxserver/docker-letsencrypt/commits/master/root/defaults/ssl.conf

# session settings
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

# Diffie-Hellman parameter for DHE cipher suites
ssl_dhparam /config/nginx/dhparams.pem;

# ssl certs
ssl_certificate /config/keys/letsencrypt/fullchain.pem;
ssl_certificate_key /config/keys/letsencrypt/privkey.pem;

# protocols
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';

# HSTS, remove # from the line below to enable HSTS
#add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;

# Optional additional headers
#add_header Content-Security-Policy "upgrade-insecure-requests";
#add_header X-Frame-Options "SAMEORIGIN" always;
#add_header X-XSS-Protection "1; mode=block" always;
#add_header X-Content-Type-Options "nosniff" always;
#add_header X-UA-Compatible "IE=Edge" always;
#add_header Cache-Control "no-transform" always;
#add_header Referrer-Policy "same-origin" always;

This all seems pretty standard and simple to figure out. Under “protocols” you can see there is a TLSv1 listed, which we’ll be removing. There is also a commented out line regarding HSTS, and I can’t really think of a reason to keep that disabled. Those two changes make the entire file look like:

## Version 2018/05/31 - Changelog: https://github.com/linuxserver/docker-letsencrypt/commits/master/root/defaults/ssl.conf

# session settings
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

# Diffie-Hellman parameter for DHE cipher suites
ssl_dhparam /config/nginx/dhparams.pem;

# ssl certs
ssl_certificate /config/keys/letsencrypt/fullchain.pem;
ssl_certificate_key /config/keys/letsencrypt/privkey.pem;

# protocols
ssl_protocols TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';

# HSTS, remove # from the line below to enable HSTS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;

# Optional additional headers
#add_header Content-Security-Policy "upgrade-insecure-requests";
#add_header X-Frame-Options "SAMEORIGIN" always;
#add_header X-XSS-Protection "1; mode=block" always;
#add_header X-Content-Type-Options "nosniff" always;
#add_header X-UA-Compatible "IE=Edge" always;
#add_header Cache-Control "no-transform" always;
#add_header Referrer-Policy "same-origin" always;

I think that’s pretty much everything — we added a CAA record, disabled TLSv1, and made a few modifications to support the TLS change in our docker-compose file.

Let’s see what SSL Labs thinks now:

aww yeah+

Woohoo! A+! Here’s the GitHub PR related to changes.

Related:

Discussion

pic
Editor guide
Collapse
dotnetcoreblog profile image
Jamie

I love seeing posts like this.

It really is that easy to make websites and web applications secure. The only remaining excuse is ignorance, and that's not really a valid one. Especially when all of the information is available online or provided by tools like this.

Collapse
kritner profile image
Russ Hammett Author

Thanks Jamie! :)
I don't yet know a whole lot about this stuff so figured I'd ask - the header information that's applied through your owasp middleware wouldn't really apply in cases when using a reverse proxy, would it? I haven't double checked all the headers your middleware applies, but it seems at a minimum there's some overlap between what nginx can throw in and what your middleware accomplishes. I dunno if it couldn't hurt to just slap yours in as well, but I"m curious as to your input!

Collapse
dotnetcoreblog profile image
Jamie

Great question.

In the case of ASP NET Core (which I'm assuming you're using, since you asked about the middleware), your request pipeline is wired up so that all responses are sent to Kestrel, Kestrel then sends those requests to whichever reverse proxy fed it the request. Kestrel communicates with nginx, IIS, Apache, etc. in a similar way to how ASP NET communicates with IIS (for example).

As such, you can totally replace the header values at the nginx level. I can't speak for exactly how nginx handles things (because I haven't looked too deeply into it), but my gut feeling is that the headers would be replaced as-is by nginx.

Let's say that your MVC pipeline added the Cross Site Scripting Protection header with the following value:

X-XSS-Protection "1"

but nginx had the following config:

X-XSS-Protection "1; mode=block" always;

then the value of the header generated in ASP NET Core MVC land would be replaced by the one generated by nginx.

Collapse
benjaminblack profile image
Benjamin Black

I recommend Mozilla Observatory as a supplement to SSLLabs.

observatory.mozilla.org/

In particular, they maintain a web server TLS configuration generator.

mozilla.github.io/server-side-tls/...

Collapse
kritner profile image
Russ Hammett Author

Cool, I'll check it out!