A lot of my public facing websites are for my private use only. I don't always have a system available that can dial a VPN back to my web servers so, instead, I use certificate authentication as the first line of defense.

...first line of defense.

Most web services come with their own authentication check. I have a hard time trusting them. Yes, I should learn more about PHP and audit the code myself but until I find the time to do so, this works...

You can put some brute force protection in front of your web server with fail2ban or denyhosts but this still exposes your websites publically. If someone knows you are hosting X webapp, they might know how to get around your authentication. Simply performing a client certificate verification is what I've settled on. No noise in logs from brute force and unnecessary emails about unsuccessful attempts. This can also be done with HTTP Basic Authentication but who wants to type their username and password each time they access their site?

Here's the design:

If I'm the sole user of a web application:
cert1

  1. The proxy server (nginx) is set to require a certificate with ssl_verify_client on;.
  2. The proxy server checks the contents of $ssl_client_s_dn. If it doesn't match my CN, it's set to return 403;:
    if ($ssl_client_s_dn !~ "CN=<my CN>") {
        return 403;
    }
    

OR

If the site has multiple users:
cert2-2

  1. The proxy server (nginx) checks for a valid certificate:
    ssl_verify_client   optional;
    ssl_verify_depth    2; # Certs signed by a SUBCA
    expires             0;
    add_header          Cache-Control private;
    
  2. If the cert is not valid, the proxy server returns a 403:
    if ($ssl_client_verify != SUCCESS) {
    return 403;
    }
    
  3. The proxy server sets the X-SSL-Cert header which is used by the down-stream web application:
    proxy_set_header	X-SSL-Cert		$ssl_client_escaped_cert;
    

At this point, the proxy server handles the initial authorization check. To use the data passed by the proxy server in the X-SSL-Cert header, here's a simple PHP file that displays all data available to the web application:

<?php
echo "<pre>";
var_dump($_SERVER);
$cert=(openssl_x509_parse( urldecode($_SERVER['HTTP_X_SSL_CERT'])));
echo "</br>*********CERT DATA*********</br></br>";
var_dump($cert);
echo "</pre>";
?>