Securing my site

What's in This Post

Last year I wrote about how I wanted to pass all the tests Internet.nl included on their website.

Having changed domains, had I maintained the level of security I'd achieved back then? Had I failed to note any changes of security practices that would now leave me more vulnerable?

There was also the matter of adding a Content Security Policy to my website. Last year, when I ran through the tests, the lack of CSP was observed and noted as a warning, but it didn't stop me from achieving 100%. This time around I wasn't going to accept any warnings.

Domain

First I had to buy my domain. I've used a couple of different registrars over the years and my favourite was Uniregistry (IANA ID 1659) because of the technical focus of its management UI. They've recently been bought by Godaddy, but that hasn't changed my immediate impression of the service. However for this domain I chose Google (IANA ID 895), mostly out of curiosity. Both Registrars follow my recommendations for ensuring that my domain can't be stolen.

Email

I have plenty of domains for which I receive email, lots of it, nearly all of which I ignore. For this domain, I don't plan on sending or receiving any email at all.

But simply not setting up a mail server or the DNS MX record, does not prevent a bad actor from attempting to send as my domain. I need to tell other email recipients 'I will never send you email'.

A Note on Parked Domains

In the domain name hosting industry, domains which are registered for speculation or as part of a broader portfolio of names to protect a brand, are often unused and referred to as parked.

Luckily for us, the snappily acronymed M3AAWG (Messaging, Malware and Mobile Anti-Abuse Working Group) has provided some advice on how to signal to other mail servers that this domain will not be sending or receiving email. My domain won't be parked, but the recommended DNS records won't impact web hosting.

Here are the relevant records contained within my zone file. You'll find these in a template within my ISC-BIND Ansible role.

1kalfeher.com.		3600	IN	TXT	"v=spf1 -all"
2*.kalfeher.com.		3600	IN	TXT	"v=DKIM1; p="
3*.kalfeher.com.		3600	IN	TXT	"v=spf1 -all"
4_dmarc.kalfeher.com.	3600	IN	TXT	"v=DMARC1; p=reject; rua=mailto:dmarc@nospam.example.net; ruf=mailto:dmarc@nospam.example.net;"
5*._domainkey.kalfeher.com. 3600	IN	TXT	"v=DKIM1; p="

Since I send my DMARC reports to another domain, I needed to ensure that receiving those reports on behalf of kalfeher.com is explicitly allowed. This is done by adding the following record at my recipient domain:

1kalfeher.com._report._dmarc IN TXT "v=DMARC1"

Internet.NL result:

Success. With some information appended. More on that in the results section.

MXToolbox DMARC Lookup:

All tests passed!

Webhosting

I'm using the same server as my previous domain, which passed the security assessment tests without issues last year. I haven't made any significant configuration changes to it, so it will be interesting to see if any tests have gotten more stringent. The server has been regularly patched and its possible that default settings in NGINX have been updated without my interaction. However I don't recall any significant changes related to deprecating ciphers or hashing algos during the year, so I think I'll be ok.

IPv6

My webserver is available via IPv6 and was configured to respond as normal to any incoming connections last year. Unfortunately, I somehow forgot to add the AAAA record for my new blog. I quickly added it after reading the Internet.nl report and face-palming. Of course I needed to wait for the negative cache to expire before re-running the test.

Internet.NL result:

Success. No warnings.

TLS

All tests passed, but I noticed some warnings. And this time, there's no room for warnings! It seems TLS 1.1 is still allowed by my default NGINX configuration. Also the cipher suite order doesn't prioritise secure ciphers. oh dear. It looks like ssl-params.conf within NGINX is a little less rigorous than I'd like.

Note

While fixing the issue, I came across some behaviour that appears buggy. If I explicitly set my TLS 1.3 cipher order, NGINX appears to use the wrong call to openssl to fetch them, resulting in an error on start up. Since there's only 3 TLS 1.3 ciphers and I don't really care about their order, I simply removed that config line and let NGINX use the ciphers in the order openssl provided them.

As usual, I use Ansible to push out my configuration. Here's my TLS variables for my webserver's NGINX configuration (shown below):

 1  TLS_v: "TLSv1.3" # Mozilla Modern setting
 2  cipher_order: "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" # Mozilla Modern setting 
 3  TLS_curve_order: "X25519:prime256v1:secp384r1" # Mozilla Modern setting 
 4  resolver_list: "127.0.0.53 [::53]:53" # Ensure that resolved has DNSSEC enabled otherwise use the recursive_DNS list.
 5  recursive_DNS:
 6    - "8.8.8.8" # Google
 7    - "2001:4860:4860::8888" # Google
 8    - "1.1.1.1" # Cloudflare
 9    - "2606:4700:4700::1111" # Cloudflare
10    - "9.9.9.9" # Quad9
11    - "2620:fe::fe" # Quad9
12    - "156.154.70.5" # Neustar
13    - "2610:a1:1018::5" # Neustar

and here's the ssl-params.conf.j2 template I push to the server:

 1ssl_protocols {{ TLS_v }}; 
 2ssl_prefer_server_ciphers on;
 3# ssl_ciphers {{ cipher_order }}; # Left out due to buggy behaviour
 4ssl_ecdh_curve  {{ TLS_curve_order }}; 
 5ssl_session_cache shared:SSL:10m;
 6ssl_session_tickets off; 
 7ssl_stapling on; 
 8ssl_stapling_verify on; 
 9resolver {{ resolver_list }}; # I'll remove this in a later step
10resolver_timeout 5s;
11add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains; preload';
12add_header X-Frame-Options SAMEORIGIN;
13add_header X-Content-Type-Options nosniff;
14ssl_dhparam /etc/nginx/snippets/dhparam.pem;
Compression

My nginx.conf enabled gzip compression, which is no longer recommended for TLS enabled sites. So I added gzip off; to each of my site configuration files. This is a warning for Internet NL's tests.

TLS 1.3

If you're reading this, you've arrived via TLS 1.3. I decided to follow Mozilla's Modern settings, which meant turning off all other TLS/SSL versions. A quick test via ssllabs shows an 'A' grade. At the time of testing the Let's Encrypt OCSP server timed out, which isn't my responsibility so I'll ignore it for now.

SSL Labs result:

A, with an OCSP reachability warning. This is fixed later when I change the default resolver entry.

Internet.NL result:

Success, with no warnings

Content Security Policy

I initially planned to add my content security policy in tags, but doing so was a bit clumsy and some test sites appeared not to recognise the tags at all. So I opted for including the policy in the NGINX headers.

Using the Laboratory Firefox plugin from Mozilla, I was able to create a usable CSP. Here's the variables I use to set my content security policy for my site:

1  content_src:
2    - default-src 'none'; 
3    - connect-src https://links.services.disqus.com/api/domains https://links.services.disqus.com/api/ping https://www.google-analytics.com/g/collect;
4    - font-src https://cdn.jsdelivr.net; 
5    - frame-src https://disqus.com;
6    - img-src 'self' https://cdn.viglink.com https://mirrors.creativecommons.org; 
7    - script-src 'self' 'unsafe-inline' https://c.disquscdn.com/next/embed/alfie_v4.63f1ab6d6b9d5807dc0c94ef3fe0b851.js https://kalfeher-com.disqus.com/embed.js https://www.googletagmanager.com/gtag/js;
8    - style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5/css/;

And here's the nginx site configuration snippet I use:

I use the NGINX function set to concatenate my sources

1    ...
2    set $CSP "";
3    {% for src in content_src %}
4    set $CSP "${CSP}{{src}}";
5    {% endfor %}
6    add_header Content-Security-Policy $CSP;
7    ...
Unsafe Inline

unsafe-inline appears in two of my entries. These relate to Disqus comments, Google Analytics and the Fontawesome icon set that my Minimal Mistakes theme comes with. I'll be looking in to how I can fix this in a future post.

Referrer-Policy

The default configuration of NGINX does not set a strict Referrer-Policy so I had to add the following entry to my site configuration: add_header Referrer-Policy 'strict-origin-when-cross-origin';.

Note

While adding the referrer-policy entry, I initially omitted the single quotes encompassing the strict-origin-when-cross-origin. This appeared to break the IPv6 functionality of the website for some reason.

OCSP Stapling

The snippets/ssl-params.conf NGINX configuration file which is included in my site configuration, included OCSP stapling, so no changes were required.

Many OCSP configuration entry examples, including the default config on my host, include resolver entries which explicitly set DNS resolvers. The reasoning behind this is a mix of performance (avoid blocking local resolver) and control (set your own DNS servers rather than rely on system defaults).

But NGINX resolver configuration is quite primitive and can't be secured other than by relying on an upstream DNS server to secure it for you. systemd.resolved is present on my server, it can be secured and it will dynamically update to changing network conditions (eg changes to the tenant or VPC within which my web server resides). Therefore I excluded the resolver entry from my NGINX configuration. I'm now able to resolve and validate all names and can use DoH or DoT servers without altering my NGINX configuration.

DNS hosting

I've migrated my DNS to a new server in order to test out some of the fancy new BIND features such as dnssec-policy. Of course, until there is support from Verisign or Google for RFC 8078 my dream of a hands off DNSSec solution is not within reach. Well done to .cz and .ch who do support it.

I'm using the free Hurricane Electric secondary hosting service. Like many DNS hosting organisations, they anycast the IP addresses of their name servers. That means the same IP address is actually present on several hosts around the world. This may explain my inability to replicate the one error I saw when using DNSViz to check my DNS configuration. The report tells me they can't reach the IPv4 address of the name server in my SOA record over TCP.

After a bit of testing I noticed that some of my responses from ns1.he.net took as long as 10,000msec to reply! Although no queries were ever dropped, I wonder if DNSViz simply times out (waiting around that long for a response probably isn't sustainable). I suppose you get what you pay for.

DNSSec

I used ECDSA P-256 for my key algorithm since this is a MUST for all resolvers to support. I'm planning on doing an algo roll to an Ed curve in the near future, to see what happens.

Internet.NL result:

Success, with no warnings

I tried testing my DNS with the MXToolbox tools, but I think that some tests are a little buggy. I repeatedly received a 'x' for DNS Record Published when testing my nsec3param record. 🤷

Unfortunately for MXToolbox, that record was alive and well and reachable from multiple points around the world. I'd enabled NSEC3 signing several days ago, so this wont be a negative cache issue. The website doesnt publish their methodology for the tests, so I've only used these tests for quick validation, when I'm fairly confident of the result (it's much faster than other test suites in this article).

A quick check to see if nsec3param appears in my domain:

1~ dig nsec3param kalfeher.com +short
21 0 10 2F58CC5C993FD084

TLSA Entry

This is mostly pointless because the only DANE record being used with any regularity is the TLSA entry for mail servers. But since I use my domain to show how TLSA records can be created, I added some for port 443.

Results

If you've followed along, you'll know that all tests passed, but here's a quick summary:

Result

I'm in the website Hall of Fame. Here's the report. There are no warnings or alerts of missed security settings!

Result

The Qualys SSL Labs test site gives me an A

Result

I also made it into the email Hall of Fame. Here's the report.

The report contains several informational entries which suggest that although the anti malware records are recommended, they are not tested. There was also advice suggesting I add those records if I hadn't already (I had). So I can't take much from this report. I'll look around to see if there are alternative test sites.
Update!

Update 29 March 2021: Just after I posted this blog, Internet NL introduced better checking for domains which explicitly don't send email. This time around the site tested for and observed the Null MX records. The presence of those records is acknowledged in an informational item where Mail server tests results would be.

I used the mxtoolbox suite of tools for some point testing. There's no permalink to the results, so there's no report I can share. I just used them to quickly validate changes I'd made.
Result

I passed all the DNSSec tests and the zone is well formed. But Verisign can't talk to one of my provider's hosts over TCP. It's annoying and I'm sure it will be resolved by HE.net eventually. Since it doesn't represent a security issue, I consider this test passed. You can find the report here.

Result

I also tested the published primary server (ns1.he.net) for EDNS0 compliance via ISC's testing tool. The result is similar to the DNSViz report. That server simply doesn't respond fast enough (10,000 msecs is an eternity in DNS years) or perhaps at all. My direct testing wasn't thorough enough to be sure.