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