Improve security of docker registry install
Some checks are pending
CI/CD Pipeline (Fully Isolated DinD) / Run Tests (DinD) (push) Waiting to run
CI/CD Pipeline (Fully Isolated DinD) / Build and Push Docker Images (DinD) (push) Blocked by required conditions
CI/CD Pipeline (Fully Isolated DinD) / Deploy to Production (push) Blocked by required conditions

This commit is contained in:
continuist 2025-08-24 13:07:50 -04:00
parent 9103f53673
commit 95331c2d11

View file

@ -1,6 +1,6 @@
# Docker Registry Install Guide
This guide covers setting up a rootless Docker Registry v2 with host TLS reverse proxy for secure image storage. The registry runs rootless via Podman with loopback-only access, while a host nginx reverse proxy provides TLS termination and authentication.
This guide covers setting up a rootless Docker Registry v2 with host TLS reverse proxy for secure image storage. The registry runs rootless via Podman with loopback-only access, while a host nginx reverse proxy provides TLS termination and mTLS authentication.
## Architecture Overview
@ -14,7 +14,7 @@ This guide covers setting up a rootless Docker Registry v2 with host TLS reverse
│ │ │
│ │ │
└─── HTTPS :443 ────────┼───────────────────────┘
(Unauthenticated pulls)
└─── HTTPS :4443 ───────┘
(mTLS Authentication)
```
@ -26,6 +26,8 @@ This guide covers setting up a rootless Docker Registry v2 with host TLS reverse
- **Loopback Isolation**: Registry only accessible via `127.0.0.1:5000`
- **mTLS Authentication**: Client certificates required for push operations
- **No $HOME I/O**: All Podman state outside user home directory
- **Port 443**: Unauthenticated pulls only (GET requests)
- **Port 4443**: Authenticated pushes only (mTLS required)
## Prerequisites
@ -36,7 +38,7 @@ This guide covers setting up a rootless Docker Registry v2 with host TLS reverse
## Step 1: Install Podman (if not already installed)
### 1.5 Install Podman
### 1.1 Install Podman
```bash
# Install Podman and related tools
@ -54,7 +56,7 @@ sudo usermod --add-subuids 100000-165535 CI_SERVICE_USER
sudo usermod --add-subgids 100000-165535 CI_SERVICE_USER
```
## Step 2: Set Up Rootless Docker Registry v2 with Host TLS Reverse Proxy
## Step 2: Set Up Rootless Docker Registry v2
### 2.1 Create System-wide Podman Configuration
@ -102,6 +104,11 @@ sudo mkdir -p /etc/registry/certs/private /etc/registry/certs/clients
sudo chown root:root /etc/registry/certs/private /etc/registry/certs/clients
sudo chmod 750 /etc/registry/certs/private
sudo chmod 755 /etc/registry/certs/clients
# Create registry data directory
sudo mkdir -p /var/lib/registry/data
sudo chown CI_SERVICE_USER:CI_SERVICE_USER /var/lib/registry/data
sudo chmod 755 /var/lib/registry/data
```
### 2.4 Install Systemd Services
@ -112,27 +119,26 @@ sudo chmod 755 /etc/registry/certs/clients
# Install systemd user service for rootless registry
sudo tee /etc/systemd/user/registry.service > /dev/null << 'EOF'
[Unit]
Description=Rootless Docker Registry v2
After=network.target
Description=Rootless Docker Registry v2 (loopback only)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
Environment=PODMAN_ROOT=/var/tmp/podman-%U/root
Environment=PODMAN_RUNROOT=/run/user/%U/podman-run
Environment=PODMAN_TMPDIR=/var/tmp/podman-%U/tmp
Environment=XDG_DATA_HOME=/var/tmp/podman-%U/xdg-data
Environment=XDG_CONFIG_HOME=/var/tmp/podman-%U/xdg-config
Environment=REGISTRY_HTTP_ADDR=0.0.0.0:5000
Environment=REGISTRY_STORAGE_DELETE_ENABLED=false
ExecStart=/usr/bin/podman run --rm --name registry \
--root=${PODMAN_ROOT} --runroot=${PODMAN_RUNROOT} --tmpdir=${PODMAN_TMPDIR} --events-backend=file \
ExecStart=/usr/bin/podman --root=${PODMAN_ROOT} --runroot=${PODMAN_RUNROOT} --tmpdir=${PODMAN_TMPDIR} --events-backend=file \
run --rm --name registry \
-p 127.0.0.1:5000:5000 \
--cap-drop=ALL --read-only --tmpfs /tmp:size=64m --security-opt=no-new-privileges \
-v /var/lib/registry:/var/lib/registry:Z \
--read-only --tmpfs /tmp:size=64m --cap-drop=ALL --security-opt=no-new-privileges \
-e REGISTRY_HTTP_ADDR=0.0.0.0:5000 \
-e REGISTRY_STORAGE_DELETE_ENABLED=false \
-v /var/lib/registry/data:/var/lib/registry:z \
registry:2
ExecStop=/usr/bin/podman stop --root=${PODMAN_ROOT} --runroot=${PODMAN_RUNROOT} --tmpdir=${PODMAN_TMPDIR} --events-backend=file registry
Restart=always
RestartSec=10
ExecStop=/usr/bin/podman --root=${PODMAN_ROOT} --runroot=${PODMAN_RUNROOT} --tmpdir=${PODMAN_TMPDIR} stop -t 10 registry
Restart=on-failure
[Install]
WantedBy=default.target
@ -145,12 +151,11 @@ EOF
# Install systemd system service for TLS reverse proxy
sudo tee /etc/systemd/system/registry-proxy.service > /dev/null << 'EOF'
[Unit]
Description=Docker Registry TLS Reverse Proxy
After=network.target registry.service
Requires=registry.service
Description=TLS proxy for Docker Registry (443 pulls / 4443 pushes)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=registry-proxy
Group=registry-proxy
AmbientCapabilities=CAP_NET_BIND_SERVICE
@ -159,13 +164,16 @@ NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
LockPersonality=yes
MemoryDenyWriteExecute=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
IPAddressDeny=any
IPAddressAllow=127.0.0.1/8 ::1
ExecStart=/usr/sbin/nginx -c /etc/registry/nginx.conf
ExecReload=/usr/sbin/nginx -c /etc/registry/nginx.conf -s reload
Restart=always
RestartSec=10
ExecStart=/usr/sbin/nginx -g 'daemon off;' -c /etc/registry/nginx.conf
Restart=on-failure
[Install]
WantedBy=multi-user.target
@ -177,99 +185,50 @@ EOF
```bash
# Create nginx configuration for TLS reverse proxy
sudo tee /etc/registry/nginx.conf > /dev/null << 'EOF'
events {
worker_connections 1024;
}
worker_processes auto;
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Rate limiting
limit_req_zone $binary_remote_addr zone=read_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=write_limit:10m rate=5r/s;
# Upstream registry
upstream registry {
server 127.0.0.1:5000;
limit_req_zone $binary_remote_addr zone=reg_read:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=reg_write:10m rate=5r/s;
client_max_body_size 2g;
proxy_http_version 1.1;
proxy_set_header Connection "";
upstream reg { server 127.0.0.1:5000; }
# 443: unauthenticated pulls only
server {
listen 443 ssl http2;
ssl_certificate /etc/registry/certs/registry.crt;
ssl_certificate_key /etc/registry/certs/private/registry.key;
ssl_protocols TLSv1.2 TLSv1.3;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
if ($request_method ~* ^(PUT|PATCH|POST|DELETE)$) { return 403; }
location /v2/ {
limit_req zone=reg_read burst=20 nodelay;
proxy_pass http://reg;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# Port 443: Unauthenticated pulls only
server {
listen 443 ssl http2;
server_name _;
# TLS configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
# Certificate files
ssl_certificate /etc/registry/certs/registry.crt;
ssl_certificate_key /etc/registry/certs/private/registry.key;
# Block write methods (PUT, PATCH, POST, DELETE)
if ($request_method ~ ^(PUT|PATCH|POST|DELETE)$) {
return 403;
}
# Rate limiting for reads
limit_req zone=read_limit burst=20 nodelay;
# Proxy to registry
location / {
proxy_pass http://registry;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Port 4443: Authenticated pushes only (mTLS)
server {
listen 4443 ssl http2;
server_name _;
# TLS configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# mTLS configuration
ssl_client_certificate /etc/registry/certs/clients/ca.crt;
ssl_verify_client on;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
# Certificate files
ssl_certificate /etc/registry/certs/registry.crt;
ssl_certificate_key /etc/registry/certs/private/registry.key;
# Rate limiting for writes
limit_req zone=write_limit burst=10 nodelay;
# Proxy to registry (full API access)
location / {
proxy_pass http://registry;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# 4443: authenticated pushes only (mTLS)
server {
listen 4443 ssl http2;
ssl_certificate /etc/registry/certs/registry.crt;
ssl_certificate_key /etc/registry/certs/private/registry.key;
ssl_protocols TLSv1.2 TLSv1.3;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
ssl_client_certificate /etc/registry/certs/clients/ca.crt;
ssl_verify_client on;
location /v2/ {
limit_req zone=reg_write burst=10;
proxy_pass http://reg;
proxy_set_header Host $host:4443;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
EOF
@ -390,7 +349,9 @@ sudo ufw allow 4443/tcp # Docker Registry via nginx (authenticated pushes with m
# Enable and start services
sudo systemctl --global enable registry.service
sudo systemctl enable registry-proxy.service
sudo -u CI_SERVICE_USER -H sh -lc 'systemctl --user daemon-reload; systemctl --user enable --now registry.service'
# Start as the service user
sudo -u CI_SERVICE_USER sh -lc 'systemctl --user daemon-reload && systemctl --user enable --now registry.service'
sudo systemctl start registry-proxy.service
```
@ -537,6 +498,7 @@ The project uses a two-port configuration:
5. **🔧 Robust Boot**: User manager guaranteed to exist at boot time
6. **🎯 Clear Separation**: Unauthenticated pulls vs authenticated pushes
7. **⚡ Low-Port Binding**: Via minimal capability (`CAP_NET_BIND_SERVICE`)
8. **🛡️ Comprehensive Hardening**: Multiple security layers and restrictions
## 🎉 Congratulations!
@ -549,5 +511,6 @@ You have successfully set up a rootless Docker Registry v2 with host TLS reverse
- ✅ **FHS-compliant directory structure** for better organization and security
- ✅ **No $HOME I/O** policy with all state outside user home directory
- ✅ **Port 443 allows unauthenticated Docker Registry v2 pulls; Port 4443 is for authenticated pushes (mTLS)**
- ✅ **Comprehensive security hardening** with multiple protection layers
Your Docker Registry is now ready for secure image storage with proper authentication and isolation!