Protect Docker Registry by Nginx
Original documentation is placed in this page https://docs.docker.com/registry/recipes/nginx/, but in this page I will investigate some another way.
- 1. Install docker compose.
- 2. Create htpasswd file.
- Attempt 1. With self-signed certificate.
- 3.1. Create self-signed certificate.
- 3.2. Create composer definition.
- 3.3. Create proxy config.
- 3.4. Start compose as daemon with disconnected mode and with full log.
- 3.5. Check endpoint with docker and CURL
- Attempt 2. Temporary scheme without https and authentication.
- 4.1. Create self-signed certificate.
1. Install docker compose.
# sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose # sudo chmod +x /usr/local/bin/docker-compose # sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose # docker-compose --version
2. Create htpasswd file.
# sudo docker run --rm --entrypoint htpasswd registry:2.7.0 -Bbn registry XXXXXXXXXXX > ./auth/nginx.htpasswd
3.1. Create self-signed certificate.
# mkdir -p auth data # sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout auth/nginx-selfsigned.key -out auth/nginx-selfsigned.crt
3.2. Create composer definition with related Registry image, copy self-signed certificate to Nginx, and map port 5043->443 and 5000->80.
This is my first attempt. Line 11 and 12 is copy my self-signed certificate inside Nginx image, line 10 is Nginx config, and line 15 is my own implementation of docker Registry.
# cat registry-compose.yml
1: nginx:
2: image: "nginx:alpine"
3: ports:
4: - 5000:80
5: - 5043:443
6: links:
7: - registry:registry
8: volumes:
9: - ./auth:/etc/nginx/conf.d
10: - ./auth/nginx.conf:/etc/nginx/nginx.conf:ro
11: - ./auth/domain.crt:/etc/ssl/certs/nginx-selfsigned.crt:ro
12: - ./auth/domain.key:/etc/ssl/private/nginx-selfsigned.key:ro
13:
14: registry:
15: image: 5-shit-money:latest
16: volumes:
17: - ./var/lib/registry:/var/lib/registry
3.3. Create proxy config.
In this case I created config follow documentation. Nginx will be listen port 443, select domain name and redirect stream to port 5000 (to Registry container). And of course handle file nginx.htpasswd inserting in YML file.
# cat auth/nginx.conf
1: events {
2: worker_connections 1024;
3: }
4:
5: http {
6:
7: upstream docker-registry {
8: server registry:5000;
9: }
10:
11: ## Set a variable to help us decide if we need to add the
12: ## 'Docker-Distribution-Api-Version' header.
13: ## The registry always sets this header.
14: ## In the case of nginx performing auth, the header is unset
15: ## since nginx is auth-ing before proxying.
16: map $upstream_http_docker_distribution_api_version $docker_distribution_api_version {
17: '' 'registry/2.0';
18: }
19:
20: server {
21: listen 443 ssl;
22: #listen 80;
23: server_name registry.XXXXXXXXXXX.io;
24:
25: # SSL
26: ssl_certificate /etc/nginx/conf.d/domain.crt;
27: ssl_certificate_key /etc/nginx/conf.d/domain.key;
28:
29: # Recommendations from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
30: ssl_protocols TLSv1.1 TLSv1.2;
31: ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
32: ssl_prefer_server_ciphers on;
33: ssl_session_cache shared:SSL:10m;
34:
35: # disable any limits to avoid HTTP 413 for large image uploads
36: client_max_body_size 0;
37:
38: # required to avoid HTTP 411: see Issue #1486 (https://github.com/moby/moby/issues/1486)
39: chunked_transfer_encoding on;
40:
41: location /v2/ {
42: # Do not allow connections from docker 1.5 and earlier
43: # docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
44: if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
45: return 404;
46: }
47:
48: # To add basic authentication to v2 use auth_basic setting.
49: auth_basic "Registry realm";
50: auth_basic_user_file /etc/nginx/conf.d/nginx.htpasswd;
51:
52: ## If $docker_distribution_api_version is empty, the header is not added.
53: ## See the map directive above where this variable is defined.
54: add_header 'Docker-Distribution-Api-Version' $docker_distribution_api_version always;
55:
56: proxy_pass http://docker-registry;
57: proxy_set_header Host $http_host; # required for docker client's sake
58: proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP
59: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
60: proxy_set_header X-Forwarded-Proto $scheme;
61: proxy_read_timeout 900;
62: }
63: }
64: }
3.4. Start compose as daemon with disconnected mode and with full log.
Composer can started in two mode, with full log.
# sudo docker-compose -f registry-compose.yml logs --follow
And in disconnected mode as daemon.
# sudo docker-compose -f registry-compose.yml up -d
3.5. Check endpoint with docker and CURL.
# sudo docker login -u=registry https://registry.XXXXXXXXXXX.io:5043
And second way check external endpoint with authentication by curl.
# sudo curl -v --user registry:YYYYYYYYYYYYY https://localhost:5043/v2/_catalog
Unfortunately this way don't follow me to success, "x509: certificate signed by unknown authority".
But this is full workflow to set up authentication proxy to protect docker registry.
4.1. Create self-signed certificate.
This is temporary workable scheme for development process without AU and before certificate will be ready.
# cat registry-compose.yml
1: nginx:
2: # Note : Only nginx:alpine supports bcrypt.
3: # If you don't need to use bcrypt, you can use a different tag.
4: # Ref. https://github.com/nginxinc/docker-nginx/issues/29
5: image: "nginx:alpine"
6: ports:
7: - 6000:80
8: links:
9: - registry:registry
10: volumes:
11: - ./auth:/etc/nginx/conf.d
12: - ./auth/nginx.conf:/etc/nginx/nginx.conf:ro
13: - ./auth/domain.crt:/etc/ssl/certs/nginx-selfsigned.crt:ro
14: - ./auth/domain.key:/etc/ssl/private/nginx-selfsigned.key:ro
15:
16: registry:
17: image: 5-shit-money:latest
18: #volumes:
19: #- ./var/lib/registry:/var/lib/registry
# cat auth/nginx.conf
1: events {
2: worker_connections 1024;
3: }
4:
5: http {
6:
7: upstream docker-registry {
8: server registry:5000;
9: }
10:
11: ## Set a variable to help us decide if we need to add the
12: ## 'Docker-Distribution-Api-Version' header.
13: ## The registry always sets this header.
14: ## In the case of nginx performing auth, the header is unset
15: ## since nginx is auth-ing before proxying.
16: map $upstream_http_docker_distribution_api_version $docker_distribution_api_version {
17: '' 'registry/2.0';
18: }
19:
20: server {
21: #listen 443 ssl;
22: listen 80;
23: server_name registry.XXXXXXXXXX.io;
24:
25: # SSL
26: #ssl_certificate /etc/nginx/conf.d/domain.crt;
27: #ssl_certificate_key /etc/nginx/conf.d/domain.key;
28:
29: # Recommendations from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
30: #ssl_protocols TLSv1.1 TLSv1.2;
31: #ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
32: #ssl_prefer_server_ciphers on;
33: #ssl_session_cache shared:SSL:10m;
34:
35: # disable any limits to avoid HTTP 413 for large image uploads
36: client_max_body_size 0;
37:
38: # required to avoid HTTP 411: see Issue #1486 (https://github.com/moby/moby/issues/1486)
39: chunked_transfer_encoding on;
40:
41: location /v2/ {
42:
43: # To add basic authentication to v2 use auth_basic setting.
44: # auth_basic "Registry realm";
45: # auth_basic_user_file /etc/nginx/conf.d/nginx.htpasswd;
46:
47: ## If $docker_distribution_api_version is empty, the header is not added.
48: ## See the map directive above where this variable is defined.
49: add_header 'Docker-Distribution-Api-Version' $docker_distribution_api_version always;
50:
51: proxy_pass http://docker-registry;
52: proxy_set_header Host $http_host; # required for docker client's sake
53: proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP
54: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
55: proxy_set_header X-Forwarded-Proto $scheme;
56: proxy_read_timeout 900;
57: }
58: }
59: }
|