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
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:
parent
9103f53673
commit
95331c2d11
1 changed files with 78 additions and 115 deletions
|
@ -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;
|
||||
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; }
|
||||
|
||||
# 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;
|
||||
# 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!
|
||||
|
|
Loading…
Add table
Reference in a new issue