From 95331c2d118d3297a5895c2b166e6d02833fe104 Mon Sep 17 00:00:00 2001 From: continuist Date: Sun, 24 Aug 2025 13:07:50 -0400 Subject: [PATCH] Improve security of docker registry install --- Docker_Registry_Install_Guide.md | 193 +++++++++++++------------------ 1 file changed, 78 insertions(+), 115 deletions(-) diff --git a/Docker_Registry_Install_Guide.md b/Docker_Registry_Install_Guide.md index 95f761c..1076361 100644 --- a/Docker_Registry_Install_Guide.md +++ b/Docker_Registry_Install_Guide.md @@ -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!