Control Read/Write Access to Your Private Docker Registry
February 07, 2015
Last year, I wrote a post on how to run a private Docker registry backed by SoftLayer Object Storage. Soon after, my team and I started using such a registry at work behind an nginx proxy requiring basic authentication. This setup, documented in numerous places on the web, sufficed for the last six months: it let our team to push and pull images while denying anonymous access.
Recently, we needed to grant some users pull-only access to our registry while retaining full push-pull access ourselves. I could not locate a recipe for such a setup on the web, and struggled a bit to find the appropriate incantation in nginx. In the end, I landed on a solution using subrequests and conditional status codes to grant some users read-write access, others read-only access, and anonymous users no access at all. The trick is to determine which HTTP methods to allow based on a portion of the authenticated username.
I've posted a complete nginx.conf implementing this scheme. You can grab it and go, or keep reading for an explanation of the configuration and an outline of how to use it.
The following snippets show the sections of the
nginx.conf file used to implement the authorization mechanism.
The snippet above enables basic auth over all
docker-registry upstream endpoints by default. It authenticates users based on credentials in the
/etc/users/registry_users file created with the htpasswd utility. It then delegates to the
/_auth location for further checks.
/_auth location tests the value of the
$remote_user (i.e., the authenticated username) and the
- If the username starts with
admin-, the subrequest handler responds with a status code of 200. This response allows any client request to proceed to the registry via the original handler (
location /). A match on this condition effectively grants full read-write access to the registry.
- If the username does not start with the
adminprefix, but the HTTP request method is
HEAD, the subrequest handler responds with 200 status. This response allows the client request to proceed to the upstream registry. A match on this condition effectively grants read-only access to the registry.
- In any other case, the subrequest handler responds with a
403 Forbidden. The original request handler aborts further processing and returns the 403 response to the client. A match on this condition prohibits the client request from reaching the registry. In effect, this condition prevents all anonymous user access and prevents anything but
HEADaccess (i.e., read-only access) by non-admin users.
The Docker client requires the ability to
POST to the
/v1/users endpoint during login. The above location definition protects
/v1/users with basic authentication, but allows any authenticated user to
POST here to complete the Docker login process.
To use this config, follow these general steps:
- Grab the complete config template.
- Modify the
server_namevalue at least.
- Pull the registry and nginx images from Docker Hub.
htpasswdto create a users file.
- Run a registry container and link the configured nginx container to it.
A simple deployment might look like something like the following. Of course, you'll want to configure more robust storage for the registry if you start to use it in earnest.
$ docker pull registry:0.9.1 $ docker pull nginx:2.7 $ htpasswd -c registry_users janedoe New password: ****** Re-type new password: ****** Adding password for user janedoe $ htpasswd registry_users admin-parente New password: ****** Re-type new password: ****** Adding password for user admin-parente $ docker run -itd \ --user www-data \ --name registry \ --volume /tmp \ -e SETTINGS_FLAVOR=local \ -e GUNICORN_WORKERS=4 \ registry:0.9.1 $ docker run -itd \ -p 443:443 \ --name proxy \ --link registry:registry \ -v /host/path/to/nginx.conf:/etc/nginx.conf \ -v /host/path/to/registry_users:/etc/nginx/registry_users \ -v /host/path/to/ssl_cert:/etc/nginx/server.crt \ -v /host/path/to/ssl_key:/etc/nginx/server.key \ nginx:1.7