Securing a Private Docker Registry

So when I researched this a few weeks back most of the guidance I found suggested using Basic Auth. Now nothing wrong with this method as such, it works after all. However if you’re running a registry for more than one user you obviously don’t want to have just one username/password to access it. This then means having a way to add new users easily to it + “bot” users for your servers and so on.

However there is actually a much better way, using x.509 certificates (the same method used to verify and authenticate access to the docker daemon) with your own self-signed CA. Once a user has their client key and certificate setup the authentication is transparent to them. It also provides stronger security than a simple username/password combo.

The Docker docs go through the basic process for configuring this: https://docs.docker.com/articles/certificates/. However they don’t go through how to create your own CA. There are a few important things to keep in mind first: The security of the entire thing rests on keeping your root CA private key secret. Failure to do so means a miscreant with your private key could easily sign any number of new client certificates gaining full access to your registry. You will also need to ensure you have a process for signing new users certificate requests (CSRs), as you certainly don’t want to be generating the private key for them and emailing it over.

You may already have a a PKI implementation within your organisation that you can reuse here (such as used for providing access to build systems, internal wikis and so fourth), if not you have a few choices: You can either generate the Root CA using OpenSSL (or another encryption toolkit) and manage it all your self or use a PKI system, such as Cloudflare’s CFSSL lib which allows to specify all the config and CSRs in json files.

If you wanted to use OpenSSL here is a quick and dirty guide on how you would go about this:

Setup:
Create a new empty directory to create the root CA in then create the basic dir structure and required files:

mkdir certs crl newcerts private
chmod 700 private
touch index.txt
echo 1000 > serial

Openssl Configuration:
Copy your systems default openssl.cnf file to the cwd and name it something like: “openssl-ca.cnf”. Then edit it to setup the CA and client sections (un-commenting and editing lines where appropriate):

[ usr_cert ]
basicConstraints=CA:FALSE
nsCertType = client
nsComment = "Docker Registry CA"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
[ v3_ca ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true
keyUsage = cRLSign, keyCertSign

And change the default directory to the current one:

[ CA_default ]
dir = . # Where everything is kept

The usr_cert section is used when creating the client certificates, the v3_ca is used for the CA creation, it simply configs the type of cert we want it what we want it it be usable for.

Generating the Private Key:

openssl genrsa -aes256 -out private/docker_ca.key 4096

Self-signing the certificate to be usable as a CA:

openssl req -new -x509 -days 3650 -key private/docker_ca.key -sha256 -extensions v3_ca -out certs/docker_ca.crt -config openssl-ca.cnf

Now you can go about signing CSRs for the client certificates:

Sign a signing request using your CA key:

openssl ca -keyfile private/docker_ca.key -cert certs/docker_ca.crt -extensions usr_cert -notext -md sha256 -in certs/sebdangerfield.csr -out certs/sebdangerfield.crt -config openssl-ca.cnf -days 366

Troubleshooting:

If when you try and authenticate with your Docker Registry Docker thinks the private key and certificate don’t match make sure your client certificate is the first one in your client.cert file (i.e. the certs making up the chain come after).

Leave a Reply

Your email address will not be published. Required fields are marked *