2016-09-29 19:21:06 +00:00
|
|
|
---
|
|
|
|
description: Restricting access to your registry using a nginx proxy
|
2016-11-10 19:54:25 +00:00
|
|
|
keywords: registry, on-prem, images, tags, repository, distribution, nginx, proxy, authentication, TLS, recipe, advanced
|
2016-11-04 22:38:40 +00:00
|
|
|
title: Authenticate proxy with nginx
|
2016-09-29 19:21:06 +00:00
|
|
|
---
|
2016-09-28 21:46:28 +00:00
|
|
|
|
|
|
|
## Use-case
|
|
|
|
|
2016-11-04 22:38:40 +00:00
|
|
|
People already relying on a nginx proxy to authenticate their users to other
|
|
|
|
services might want to leverage it and have Registry communications tunneled
|
|
|
|
through the same pipeline.
|
2016-09-28 21:46:28 +00:00
|
|
|
|
2016-11-04 22:38:40 +00:00
|
|
|
Usually, that includes enterprise setups using LDAP/AD on the backend and a SSO
|
|
|
|
mechanism fronting their internal http portal.
|
2016-09-28 21:46:28 +00:00
|
|
|
|
|
|
|
### Alternatives
|
|
|
|
|
2016-11-04 22:38:40 +00:00
|
|
|
If you just want authentication for your registry, and are happy maintaining
|
|
|
|
users access separately, you should really consider sticking with the native
|
2023-10-12 07:32:37 +00:00
|
|
|
[basic auth registry feature](/about/deploying#native-basic-auth).
|
2016-09-28 21:46:28 +00:00
|
|
|
|
|
|
|
### Solution
|
|
|
|
|
2016-11-04 22:38:40 +00:00
|
|
|
With the method presented here, you implement basic authentication for docker
|
|
|
|
engines in a reverse proxy that sits in front of your registry.
|
2016-09-28 21:46:28 +00:00
|
|
|
|
2016-11-04 22:38:40 +00:00
|
|
|
While we use a simple htpasswd file as an example, any other nginx
|
|
|
|
authentication backend should be fairly easy to implement once you are done with
|
|
|
|
the example.
|
2016-09-28 21:46:28 +00:00
|
|
|
|
2016-11-04 22:38:40 +00:00
|
|
|
We also implement push restriction (to a limited user group) for the sake of the
|
|
|
|
example. Again, you should modify this to fit your mileage.
|
2016-09-28 21:46:28 +00:00
|
|
|
|
|
|
|
### Gotchas
|
|
|
|
|
2016-11-04 22:38:40 +00:00
|
|
|
While this model gives you the ability to use whatever authentication backend
|
|
|
|
you want through the secondary authentication mechanism implemented inside your
|
|
|
|
proxy, it also requires that you move TLS termination from the Registry to the
|
|
|
|
proxy itself.
|
2016-09-28 21:46:28 +00:00
|
|
|
|
2022-01-19 09:31:42 +00:00
|
|
|
> **Note**: It is not recommended to bind your registry to `localhost:5000` without
|
|
|
|
> authentication. This creates a potential loophole in your registry security.
|
|
|
|
> As a result, anyone who can log on to the server where your registry is running
|
2020-01-10 12:39:40 +00:00
|
|
|
> can push images without authentication.
|
2018-06-20 13:28:10 +00:00
|
|
|
|
2018-01-26 01:37:23 +00:00
|
|
|
Furthermore, introducing an extra http layer in your communication pipeline
|
|
|
|
makes it more complex to deploy, maintain, and debug. Make sure the extra
|
|
|
|
complexity is required.
|
2016-09-28 21:46:28 +00:00
|
|
|
|
2016-11-04 22:38:40 +00:00
|
|
|
For instance, Amazon's Elastic Load Balancer (ELB) in HTTPS mode already sets
|
|
|
|
the following client header:
|
2016-09-28 21:46:28 +00:00
|
|
|
|
2023-10-12 07:32:37 +00:00
|
|
|
```none
|
2016-09-28 21:46:28 +00:00
|
|
|
X-Real-IP
|
|
|
|
X-Forwarded-For
|
|
|
|
X-Forwarded-Proto
|
|
|
|
```
|
|
|
|
|
2017-05-23 17:21:56 +00:00
|
|
|
So if you have an Nginx instance sitting behind it, remove these lines from the
|
2016-11-04 22:38:40 +00:00
|
|
|
example config below:
|
2016-09-28 21:46:28 +00:00
|
|
|
|
2017-05-23 17:21:56 +00:00
|
|
|
```none
|
|
|
|
proxy_set_header Host $http_host; # required for docker client's sake
|
|
|
|
proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP
|
|
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
2016-09-28 21:46:28 +00:00
|
|
|
```
|
|
|
|
|
2018-01-26 01:37:23 +00:00
|
|
|
Otherwise Nginx resets the ELB's values, and the requests are not routed
|
2016-11-04 22:38:40 +00:00
|
|
|
properly. For more information, see
|
2022-01-19 09:31:42 +00:00
|
|
|
[#970](https://github.com/distribution/distribution/issues/970).
|
2016-09-28 21:46:28 +00:00
|
|
|
|
|
|
|
## Setting things up
|
|
|
|
|
2023-10-12 07:32:37 +00:00
|
|
|
Review the [requirements](../#requirements), then follow these steps.
|
2016-09-28 21:46:28 +00:00
|
|
|
|
2023-10-12 07:32:37 +00:00
|
|
|
1. Create the required directories
|
|
|
|
|
|
|
|
```console
|
|
|
|
$ mkdir -p auth data
|
|
|
|
```
|
|
|
|
|
|
|
|
2. Create the main nginx configuration. Paste this code block into a new file called `auth/nginx.conf`:
|
|
|
|
|
|
|
|
```conf
|
|
|
|
events {
|
|
|
|
worker_connections 1024;
|
|
|
|
}
|
|
|
|
|
|
|
|
http {
|
|
|
|
|
|
|
|
upstream docker-registry {
|
|
|
|
server registry:5000;
|
|
|
|
}
|
|
|
|
|
|
|
|
## Set a variable to help us decide if we need to add the
|
|
|
|
## 'Docker-Distribution-Api-Version' header.
|
|
|
|
## The registry always sets this header.
|
|
|
|
## In the case of nginx performing auth, the header is unset
|
|
|
|
## since nginx is auth-ing before proxying.
|
|
|
|
map $upstream_http_docker_distribution_api_version $docker_distribution_api_version {
|
|
|
|
'' 'registry/2.0';
|
|
|
|
}
|
|
|
|
|
|
|
|
server {
|
|
|
|
listen 443 ssl;
|
|
|
|
server_name myregistrydomain.com;
|
|
|
|
|
|
|
|
# SSL
|
|
|
|
ssl_certificate /etc/nginx/conf.d/domain.crt;
|
|
|
|
ssl_certificate_key /etc/nginx/conf.d/domain.key;
|
|
|
|
|
|
|
|
# Recommendations from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
|
|
|
|
ssl_protocols TLSv1.1 TLSv1.2;
|
|
|
|
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
|
|
|
|
ssl_prefer_server_ciphers on;
|
|
|
|
ssl_session_cache shared:SSL:10m;
|
|
|
|
|
|
|
|
# disable any limits to avoid HTTP 413 for large image uploads
|
|
|
|
client_max_body_size 0;
|
|
|
|
|
|
|
|
# required to avoid HTTP 411: see Issue #1486 (https://github.com/moby/moby/issues/1486)
|
|
|
|
chunked_transfer_encoding on;
|
|
|
|
|
|
|
|
location /v2/ {
|
|
|
|
# Do not allow connections from docker 1.5 and earlier
|
|
|
|
# docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
|
|
|
|
if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
|
|
|
|
return 404;
|
|
|
|
}
|
|
|
|
|
|
|
|
# To add basic authentication to v2 use auth_basic setting.
|
|
|
|
auth_basic "Registry realm";
|
|
|
|
auth_basic_user_file /etc/nginx/conf.d/nginx.htpasswd;
|
|
|
|
|
|
|
|
## If $docker_distribution_api_version is empty, the header is not added.
|
|
|
|
## See the map directive above where this variable is defined.
|
|
|
|
add_header 'Docker-Distribution-Api-Version' $docker_distribution_api_version always;
|
|
|
|
|
|
|
|
proxy_pass http://docker-registry;
|
|
|
|
proxy_set_header Host $http_host; # required for docker client's sake
|
|
|
|
proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP
|
|
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
proxy_read_timeout 900;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
3. Create a password file `auth/nginx.htpasswd` for "testuser" and "testpassword".
|
|
|
|
|
|
|
|
```console
|
2023-12-10 15:28:19 +00:00
|
|
|
$ docker run --rm --entrypoint htpasswd httpd -Bbn testuser testpassword > auth/nginx.htpasswd
|
2023-10-12 07:32:37 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
> **Note**: If you do not want to use `bcrypt`, you can omit the `-B` parameter.
|
|
|
|
|
|
|
|
4. Copy your certificate files to the `auth/` directory.
|
|
|
|
|
|
|
|
```console
|
|
|
|
$ cp domain.crt auth
|
|
|
|
$ cp domain.key auth
|
|
|
|
```
|
|
|
|
|
|
|
|
5. Create the compose file. Paste the following YAML into a new file called `docker-compose.yml`.
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
version: "3"
|
|
|
|
|
|
|
|
services:
|
|
|
|
nginx:
|
|
|
|
# Note : Only nginx:alpine supports bcrypt.
|
|
|
|
# If you don't need to use bcrypt, you can use a different tag.
|
|
|
|
# Ref. https://github.com/nginxinc/docker-nginx/issues/29
|
|
|
|
image: "nginx:alpine"
|
|
|
|
ports:
|
|
|
|
- 5043:443
|
|
|
|
depends_on:
|
|
|
|
- registry
|
|
|
|
volumes:
|
|
|
|
- ./auth:/etc/nginx/conf.d
|
|
|
|
- ./auth/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
|
|
|
|
|
|
registry:
|
|
|
|
image: registry:2
|
|
|
|
volumes:
|
|
|
|
- ./data:/var/lib/registry
|
|
|
|
```
|
2016-09-28 21:46:28 +00:00
|
|
|
|
|
|
|
## Starting and stopping
|
|
|
|
|
|
|
|
Now, start your stack:
|
|
|
|
|
2023-10-12 07:32:37 +00:00
|
|
|
```consonle
|
|
|
|
$ docker-compose up -d
|
|
|
|
```
|
2016-09-28 21:46:28 +00:00
|
|
|
|
2016-11-04 22:38:40 +00:00
|
|
|
Login with a "push" authorized user (using `testuser` and `testpassword`), then
|
|
|
|
tag and push your first image:
|
2016-09-28 21:46:28 +00:00
|
|
|
|
2023-10-12 07:32:37 +00:00
|
|
|
```console
|
|
|
|
$ docker login -u=testuser -p=testpassword -e=root@example.ch myregistrydomain.com:5043
|
|
|
|
$ docker tag ubuntu myregistrydomain.com:5043/test
|
|
|
|
$ docker push myregistrydomain.com:5043/test
|
|
|
|
$ docker pull myregistrydomain.com:5043/test
|
|
|
|
```
|