{"id":171,"date":"2020-01-04T14:48:00","date_gmt":"2020-01-04T19:48:00","guid":{"rendered":"http:\/\/127.0.0.1:8080\/?p=171"},"modified":"2024-01-13T14:11:02","modified_gmt":"2024-01-13T19:11:02","slug":"cloudflare-mtls","status":"publish","type":"post","link":"http:\/\/10.42.0.68:8080\/blog\/cloudflare-mtls","title":{"rendered":"How to force CloudFlare WAF: mTLS"},"content":{"rendered":"\n
We’ll see how it’s possible to do mutual TLS (mTLS) with Nginx and force your users to pass through CloudFlare WAF and reject all direct connections.<\/p>\n\n\n\n\n\n\n\n
mTLS or mutual TLS is a double check that the web server and browser do at the same time.<\/p>\n\n\n\n
Your browser checks the server certificate when accessing your web server. It’s the normal<\/em> way for you.<\/p>\n\n\n\n Your browser or service server (like API) sends a client certificate to your web server in the same that the web server sends his certificate.<\/p>\n\n\n\n The server verifies and if it’s ok, no problem otherwise it rejects with an HTTP 496 SSL Certificate Required (expansion of the 400 Bad Request response code, used when a client certificate is required but not provided).<\/p>\n\n\n\n If a person finds the IP address of your server, it’s possible to bypass CloudFlare WAF so we need to harden.<\/p>\n\n\n\n You have 2 ways to restrict your web server from having exclusive traffic through CloudFlare.<\/p>\n\n\n\n For a dedicated web server, you can use IP filtering with nftables\/iptables but in addition, you need to keep your configuration up to date with CloudFlare ranges (IPv4\/IPv6).<\/p>\n\n\n\n When you’re mutualizing your web server like having some virtual hosts with CloudFlare WAF and others without CloudFlare WAF, your only way is mTLS, and in addition, it’s more easy and secure because forged packets with spoofed IP origin still fail due to mTLS.<\/p>\n\n\n\n You need to do very little configuration:<\/p>\n\n\n\n Never change ssl_verify_client to another possibility otherwise you’ll accept a connection without certificate and\/or with an invalid certificate.<\/p>\n\n\n\n On your domain dashboard, in SSL\/TLS, you need to activate Authenticated Origin Pulls<\/strong><\/p>\n\n\n\n All done, you’re using mTLS for your virtual host with strict mode so any connection without a valid certificate is rejected.<\/p>\n\n\n\n In your virtual host config, inside your location, you can add:<\/p>\n\n\n\n This replaces the return code of 496 to 403 or if you want to be quicker, you can prefer 444 Connection Closed Without Response.<\/p>\n\n\n\n 444 is a non-standard status code used to instruct Nginx to close the connection without sending a response to the client, most commonly used to deny malicious or malformed requests.<\/p>\n\n\n\n This status code is not seen by the client, it only appears in Nginx log files.<\/p>\n","protected":false},"excerpt":{"rendered":" We’ll see how it’s possible to do mutual TLS (mTLS) with Nginx and force your users to pass through CloudFlare WAF and reject all direct connections.<\/p>\n","protected":false},"author":1,"featured_media":98,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"saved_in_kubio":false,"footnotes":""},"categories":[9],"tags":[23,24,19],"_links":{"self":[{"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/posts\/171"}],"collection":[{"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/comments?post=171"}],"version-history":[{"count":1,"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/posts\/171\/revisions"}],"predecessor-version":[{"id":172,"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/posts\/171\/revisions\/172"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/media\/98"}],"wp:attachment":[{"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/media?parent=171"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/categories?post=171"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/10.42.0.68:8080\/wp-json\/wp\/v2\/tags?post=171"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}mTLS way<\/h3>\n\n\n\n
Why do you need to use it with CloudFlare?<\/h2>\n\n\n\n
mTLS vs IP filtering<\/h3>\n\n\n\n
Enabling mTLS<\/h3>\n\n\n\n
Webserver<\/h4>\n\n\n\n
\n
cd \/etc\/nginx\nwget -N -O cloudflare-mtls.pem https:\/\/support.cloudflare.com\/hc\/en-us\/article_attachments\/360044928032\/origin-pull-ca.pem<\/code><\/pre>\n\n\n\n
\n
ssl_client_certificate \/etc\/nginx\/cloudflare-mtls.pem;\nssl_verify_client on;<\/code><\/pre>\n\n\n\n
\n
systemctl restart nginx<\/code><\/pre>\n\n\n\n
On CloudFlare<\/h4>\n\n\n\n
Conclusion and bonus<\/h2>\n\n\n\n
Bonus: change the return code when no client certificate<\/h3>\n\n\n\n
if ($ssl_client_verify != SUCCESS) { return 403; }<\/code><\/p>\n\n\n\n