130 lines
5.4 KiB
HTML
130 lines
5.4 KiB
HTML
<h1 class="title">Nginx + SSL Client Certificate Verification: Manage access to a site</h1>
|
|
<p class="paragraph">Access control is a fundamental part of security. Most entities rely on
|
|
the combination of username and password, sometimes with additional multi-factor authentication
|
|
to improve security. Some entities also use the SSL client certificate verification to manage access
|
|
to specific resources. One of the use cases where SSL client certificate verification fits perfectly is
|
|
managing access to internet-facing development or staging servers. In this post, I'll share how
|
|
to set up the certificates and configure nginx to verify users based on their certificates.</p>
|
|
<h1>Preparing the certificates</h1>
|
|
<p class="paragraph">There are two certificates we are going to create. The first one is the root
|
|
certificate. It will be placed in the Nginx server. The second one is the client certificate. It will
|
|
be installed in the client machine/browsers.</p>
|
|
<h2>Root CA</h2>
|
|
<p class="paragraph">For generating a root CA, execute these two steps:</p>
|
|
<h3>Generate RSA Key</h3>
|
|
<code lang="shell">openssl genrsa -aes256 -out ca.key 4096</code>
|
|
<h3>Create Root CA crt file.</h3>
|
|
<code lang="shell">openssl req -new -x509 -days 3650 -key ca.key -out ca.crt</code>
|
|
<h2>Setup CA configuration</h2>
|
|
<p class="paragraph">This is an optional step, but if you want to be able to revoke access you
|
|
previously granted, you need to do this step.</p>
|
|
<p class="paragraph">Create a file named ca.cnf in the same directory as the ca.key and ca.crt.</p>
|
|
<code lang="ini" class="linenumber">[ ca ]
|
|
default_ca = gca
|
|
|
|
[ crl_ext ]
|
|
authorityKeyIdentifier=keyid:always
|
|
|
|
[ gca ]
|
|
dir = ./
|
|
new_certs_dir = $dir
|
|
unique_subject = no
|
|
certificate = $dir/ca.crt
|
|
database = $dir/certindex
|
|
; mark(1[9:11]) dimgrey
|
|
private_key = $dir/ca.key
|
|
serial = $dir/certserial
|
|
default_days = 365
|
|
default_md = sha256
|
|
policy = gca_policy
|
|
x509_extensions = gca_extensions
|
|
crlnumber = $dir/crlnumber
|
|
default_crl_days = 365
|
|
|
|
[ gca_policy ]
|
|
commonName = supplied
|
|
stateOrProvinceName = supplied
|
|
countryName = optional
|
|
emailAddress = optional
|
|
organizationName = supplied
|
|
organizationUnitName = optional
|
|
|
|
[ gca_extensions ]
|
|
basicConstraints = CA:false
|
|
subjectKeyIdentifier = hash
|
|
authorityKeyIdentifier = keyid:always
|
|
keyUsage = digitalSignature,keyEncipherment
|
|
extendedKeyUsage = serverAuth
|
|
crlDistributionPoints = URI:http://example.com/root.crl
|
|
subjectAltName = @alt_names
|
|
|
|
[ alt_names ]
|
|
DNS.1 = example.com
|
|
DNS.2 = *.example.com</code>
|
|
<p class="paragraph">Initialize an empty file for the CA database.</p>
|
|
<code lang="shell">touch certindex</code>
|
|
<p class="paragraph">Initialize value for certserial and crlnumber</p>
|
|
<code lang="shell">echo 01 > certserial
|
|
echo 01 > crlnumber</code>
|
|
<h2>User Certificates</h2>
|
|
<h3>Generate the user RSA key.</h3>
|
|
<code lang="shell">openssl genrsa -aes256 -out client01/user.key 4096</code>
|
|
<h3>Create Certificate-Signing Request (CSR)</h3>
|
|
<code lang="shell">openssl req -new -key client01/user.key -out client01/user.csr</code>
|
|
<h3>Sign the CSR.</h3>
|
|
<p class="paragraph">If you did the setup CA configuration step, sign the CSR file by running this command.</p>
|
|
<code lang="shell">openssl ca -config ca.cnf -in client01/user.csr -out client01/user.crt</code>
|
|
<p class="paragraph">If you skipped the setup CA configuration step, sign the CSR file by running this command.</p>
|
|
<code lang="shell">openssl x509 -req -days 365 -in client01/user.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client01/user.crt</code>
|
|
<h3>Convert the crt file to pfx/p12 file.</h3>
|
|
<p class="paragraph">Most of the time, browsers/client machines only accept a certificate in the pfx format. Run this
|
|
command to convert the crt file to the pfx/p12 format.</p>
|
|
<code lang="shell">openssl pkcs12 -export -out client01/user.pfx -inkey client01/user.key -in client01/user.crt -certfile ca.crt</code>
|
|
<p class="paragraph">You'll be prompted to enter an export password. You must input the exact password when adding
|
|
the certificate to a browser.</p>
|
|
<br/>
|
|
<h1>Setting up nginx with client certificates verification</h1>
|
|
<p class="paragraph">Add these lines to a server block in your nginx configuration</p>
|
|
<code lang="shell" class="linenumber">ssl_client_certificate /path/to/client/verfication/ca.crt;
|
|
ssl_verify_client optional;
|
|
ssl_verify_depth 2;</code>
|
|
<p class="paragraph">You can do location-based access control. Location-based here refers to a location block in your
|
|
nginx configuration, for example:</p>
|
|
<code lang="shell"> location /private {
|
|
# mark(1[13:41]) dimgrey
|
|
if ($ssl_client_verify != SUCCESS) {
|
|
return 403;
|
|
}
|
|
|
|
....
|
|
|
|
}
|
|
</code>
|
|
<p class="paragraph">Here is a complete example of a server block in the nginx configuration</p>
|
|
<code lang="shell">server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
|
|
server_name www.example.com;
|
|
ssl_certificate /path/to/your/https/certificate.pem;
|
|
ssl_certificate_key /path/to/your/https/private-key.pem;
|
|
include snippets/ssl-params.conf;
|
|
|
|
# mark(1:3) dimgrey
|
|
ssl_client_certificate /path/to/client/verification/ca.crt;
|
|
ssl_verify_client optional;
|
|
ssl_verify_depth 2;
|
|
|
|
root /usr/share/nginx/html;
|
|
index index.html index.htm;
|
|
|
|
location / {
|
|
# mark(1[13:41]) dimgrey
|
|
if ($ssl_client_verify != SUCCESS) {
|
|
return 403;
|
|
}
|
|
|
|
}
|
|
}</code>
|
|
<br/>
|
|
<h1>Adding the User Certificates to the client machine/browsers</h1> |