Use own self-signed cert chain for Option A
Some checks failed
Some checks failed
This commit is contained in:
parent
502643b5b0
commit
491deea461
4 changed files with 226 additions and 38 deletions
|
@ -710,6 +710,9 @@ sudo cp /opt/APP_NAME/registry/config.yml /opt/registry/config.yml
|
||||||
# Update the baseurl with your actual IP address
|
# Update the baseurl with your actual IP address
|
||||||
sudo sed -i "s/YOUR_CI_CD_IP/YOUR_ACTUAL_IP_ADDRESS/g" /opt/registry/config.yml
|
sudo sed -i "s/YOUR_CI_CD_IP/YOUR_ACTUAL_IP_ADDRESS/g" /opt/registry/config.yml
|
||||||
|
|
||||||
|
# Note: For Option B (domain-based setup), you'll need to update this again later
|
||||||
|
# with: sudo sed -i "s/YOUR_ACTUAL_IP_ADDRESS/YOUR_DOMAIN_NAME/g" /opt/registry/config.yml
|
||||||
|
|
||||||
# Set proper permissions
|
# Set proper permissions
|
||||||
sudo chown CI_SERVICE_USER:CI_SERVICE_USER /opt/registry/config.yml
|
sudo chown CI_SERVICE_USER:CI_SERVICE_USER /opt/registry/config.yml
|
||||||
```
|
```
|
||||||
|
@ -725,53 +728,49 @@ sudo chown CI_SERVICE_USER:CI_SERVICE_USER /opt/registry/config.yml
|
||||||
**Perform all of these steps if you do NOT have a domain name:**
|
**Perform all of these steps if you do NOT have a domain name:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Generate self-signed certificate
|
# 1. Generate self-signed certificate with proper CA chain
|
||||||
sudo mkdir -p /opt/registry/certs
|
sudo mkdir -p /opt/registry/certs
|
||||||
sudo chown CI_SERVICE_USER:CI_SERVICE_USER /opt/registry/certs
|
sudo chown CI_SERVICE_USER:CI_SERVICE_USER /opt/registry/certs
|
||||||
cd /opt/registry/certs
|
cd /opt/registry/certs
|
||||||
|
|
||||||
|
# Generate CA private key
|
||||||
|
sudo -u CI_SERVICE_USER openssl genrsa -out ca.key 4096
|
||||||
|
|
||||||
|
# Generate CA certificate
|
||||||
|
sudo -u CI_SERVICE_USER openssl req -new -x509 -key ca.key \
|
||||||
|
-out ca.crt \
|
||||||
|
-days 365 \
|
||||||
|
-subj "/C=US/ST=State/L=City/O=Organization/OU=IT/CN=Registry-CA"
|
||||||
|
|
||||||
|
# Generate server private key
|
||||||
sudo -u CI_SERVICE_USER openssl genrsa -out registry.key 4096
|
sudo -u CI_SERVICE_USER openssl genrsa -out registry.key 4096
|
||||||
|
|
||||||
|
# Generate server certificate signing request
|
||||||
sudo -u CI_SERVICE_USER openssl req -new -key registry.key \
|
sudo -u CI_SERVICE_USER openssl req -new -key registry.key \
|
||||||
-out registry.csr \
|
-out registry.csr \
|
||||||
-subj "/C=US/ST=State/L=City/O=Organization/OU=IT/CN=YOUR_ACTUAL_IP_ADDRESS"
|
-subj "/C=US/ST=State/L=City/O=Organization/OU=IT/CN=YOUR_ACTUAL_IP_ADDRESS"
|
||||||
sudo -u CI_SERVICE_USER tee registry.conf > /dev/null << EOF
|
|
||||||
[req]
|
|
||||||
distinguished_name = req_distinguished_name
|
|
||||||
req_extensions = v3_req
|
|
||||||
prompt = no
|
|
||||||
|
|
||||||
[req_distinguished_name]
|
# Copy and customize the OpenSSL configuration file
|
||||||
C = US
|
sudo cp /opt/APP_NAME/registry/openssl.conf /opt/registry/certs/
|
||||||
ST = State
|
sudo sed -i "s/YOUR_ACTUAL_IP_ADDRESS/YOUR_ACTUAL_IP_ADDRESS/g" /opt/registry/certs/openssl.conf
|
||||||
L = City
|
|
||||||
O = Organization
|
|
||||||
OU = IT
|
|
||||||
CN = YOUR_ACTUAL_IP_ADDRESS
|
|
||||||
|
|
||||||
[v3_req]
|
# Sign server certificate with CA
|
||||||
keyUsage = keyEncipherment, dataEncipherment
|
|
||||||
extendedKeyUsage = serverAuth
|
|
||||||
subjectAltName = @alt_names
|
|
||||||
|
|
||||||
[alt_names]
|
|
||||||
DNS.1 = YOUR_ACTUAL_IP_ADDRESS
|
|
||||||
DNS.2 = localhost
|
|
||||||
IP.1 = YOUR_ACTUAL_IP_ADDRESS
|
|
||||||
IP.2 = 127.0.0.1
|
|
||||||
EOF
|
|
||||||
sudo -u CI_SERVICE_USER openssl x509 -req -in registry.csr \
|
sudo -u CI_SERVICE_USER openssl x509 -req -in registry.csr \
|
||||||
-signkey registry.key \
|
-CA ca.crt -CAkey ca.key -CAcreateserial \
|
||||||
-out registry.crt \
|
-out registry.crt \
|
||||||
-days 365 \
|
-days 365 \
|
||||||
-extensions v3_req \
|
-extensions v3_req \
|
||||||
-extfile registry.conf
|
-extfile /opt/registry/certs/openssl.conf
|
||||||
sudo chmod 600 registry.key
|
|
||||||
sudo chmod 644 registry.crt
|
# Set proper permissions
|
||||||
|
sudo chmod 600 ca.key registry.key
|
||||||
|
sudo chmod 644 ca.crt registry.crt
|
||||||
sudo -u CI_SERVICE_USER openssl x509 -in registry.crt -text -noout
|
sudo -u CI_SERVICE_USER openssl x509 -in registry.crt -text -noout
|
||||||
|
|
||||||
# 2. Install certificate into Docker trust store
|
# 2. Install CA certificate into Docker trust store
|
||||||
sudo mkdir -p /etc/docker/certs.d/registry
|
sudo mkdir -p /etc/docker/certs.d/registry
|
||||||
sudo cp /opt/registry/certs/registry.crt /etc/docker/certs.d/registry/ca.crt
|
sudo cp /opt/registry/certs/ca.crt /etc/docker/certs.d/registry/ca.crt
|
||||||
sudo cp /opt/registry/certs/registry.crt /usr/local/share/ca-certificates/registry-ca.crt
|
sudo cp /opt/registry/certs/ca.crt /usr/local/share/ca-certificates/registry-ca.crt
|
||||||
sudo update-ca-certificates
|
sudo update-ca-certificates
|
||||||
sudo systemctl restart docker
|
sudo systemctl restart docker
|
||||||
```
|
```
|
||||||
|
@ -921,6 +920,38 @@ sudo journalctl -u docker-registry.service -f
|
||||||
|
|
||||||
#### 5.9 Test Registry Setup
|
#### 5.9 Test Registry Setup
|
||||||
|
|
||||||
|
**For Option A (Self-signed certificates):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Switch to CI_SERVICE_USER for testing (CI_SERVICE_USER runs CI pipeline and Docker operations)
|
||||||
|
sudo su - CI_SERVICE_USER
|
||||||
|
|
||||||
|
# Test Docker login and push using IP address with self-signed certificate
|
||||||
|
echo "your-secure-registry-password" | docker login YOUR_ACTUAL_IP_ADDRESS -u registry-user --password-stdin
|
||||||
|
|
||||||
|
# Create and push test image
|
||||||
|
echo "FROM alpine:latest" > /tmp/test.Dockerfile
|
||||||
|
docker build -f /tmp/test.Dockerfile -t YOUR_ACTUAL_IP_ADDRESS/APP_NAME/test:latest /tmp
|
||||||
|
docker push YOUR_ACTUAL_IP_ADDRESS/APP_NAME/test:latest
|
||||||
|
|
||||||
|
# Test public pull (no authentication)
|
||||||
|
docker logout YOUR_ACTUAL_IP_ADDRESS
|
||||||
|
docker pull YOUR_ACTUAL_IP_ADDRESS/APP_NAME/test:latest
|
||||||
|
|
||||||
|
# Test that unauthorized push is blocked
|
||||||
|
echo "FROM alpine:latest" > /tmp/unauthorized.Dockerfile
|
||||||
|
docker build -f /tmp/unauthorized.Dockerfile -t YOUR_ACTUAL_IP_ADDRESS/APP_NAME/unauthorized:latest /tmp
|
||||||
|
docker push YOUR_ACTUAL_IP_ADDRESS/APP_NAME/unauthorized:latest
|
||||||
|
# Expected: This should fail with authentication error
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
docker rmi YOUR_ACTUAL_IP_ADDRESS/APP_NAME/test:latest
|
||||||
|
docker rmi YOUR_ACTUAL_IP_ADDRESS/APP_NAME/unauthorized:latest
|
||||||
|
exit
|
||||||
|
```
|
||||||
|
|
||||||
|
**For Option B (Let's Encrypt certificates):**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Switch to CI_SERVICE_USER for testing (CI_SERVICE_USER runs CI pipeline and Docker operations)
|
# Switch to CI_SERVICE_USER for testing (CI_SERVICE_USER runs CI pipeline and Docker operations)
|
||||||
sudo su - CI_SERVICE_USER
|
sudo su - CI_SERVICE_USER
|
||||||
|
@ -949,13 +980,74 @@ docker rmi YOUR_DOMAIN_NAME/APP_NAME/unauthorized:latest
|
||||||
exit
|
exit
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Important**: For Option B, you'll also need to update the registry config file to use your domain:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update registry config for domain-based setup
|
||||||
|
sudo sed -i "s/YOUR_ACTUAL_IP_ADDRESS/YOUR_DOMAIN_NAME/g" /opt/registry/config.yml
|
||||||
|
```
|
||||||
|
|
||||||
**Expected behavior**:
|
**Expected behavior**:
|
||||||
- ✅ Push requires authentication with `registry-user` credentials
|
- ✅ Push requires authentication with `registry-user` credentials
|
||||||
- ✅ Pull works without authentication (public read access)
|
- ✅ Pull works without authentication (public read access)
|
||||||
- ✅ Unauthorized push is blocked
|
- ✅ Unauthorized push is blocked
|
||||||
- ✅ Registry accessible at `https://YOUR_DOMAIN_NAME` with valid Let's Encrypt certificate
|
- ✅ Registry accessible at `https://YOUR_ACTUAL_IP_ADDRESS` with self-signed certificate (Option A)
|
||||||
- ✅ No insecure registry configuration needed
|
- ✅ Registry accessible at `https://YOUR_DOMAIN_NAME` with valid Let's Encrypt certificate (Option B)
|
||||||
- ✅ Certificate automatically renews every 60 days
|
- ✅ Certificate automatically renews every 60 days (Option B only)
|
||||||
|
- ✅ Proper certificate chain management for both options
|
||||||
|
|
||||||
|
**Troubleshooting TLS Errors (Option A only):**
|
||||||
|
|
||||||
|
If you get a TLS error like `remote error: tls: internal error` when using self-signed certificates, you need to configure Docker to trust the certificate. Since Caddy automatically generates its own certificates, we need to configure Docker to accept the insecure registry:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verify the certificate was installed correctly
|
||||||
|
ls -la /etc/docker/certs.d/registry/
|
||||||
|
|
||||||
|
# Configure Docker to accept the insecure registry (Caddy-generated certificates)
|
||||||
|
sudo tee /etc/docker/daemon.json << EOF
|
||||||
|
{
|
||||||
|
"insecure-registries": ["YOUR_ACTUAL_IP_ADDRESS:443"]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
sudo systemctl restart docker
|
||||||
|
|
||||||
|
# Test the certificate
|
||||||
|
openssl s_client -connect YOUR_ACTUAL_IP_ADDRESS:443 -servername YOUR_ACTUAL_IP_ADDRESS < /dev/null
|
||||||
|
|
||||||
|
# Test Docker login again
|
||||||
|
echo "your-secure-registry-password" | docker login YOUR_ACTUAL_IP_ADDRESS -u registry-user --password-stdin
|
||||||
|
```
|
||||||
|
|
||||||
|
**Configure Caddy to Use Our Certificates**
|
||||||
|
|
||||||
|
Since we're creating our own certificate chain, we need to configure Caddy to use our certificates instead of generating its own:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update Caddyfile to use our certificates
|
||||||
|
sudo tee /opt/registry/Caddyfile << EOF
|
||||||
|
YOUR_ACTUAL_IP_ADDRESS {
|
||||||
|
tls /opt/registry/certs/registry.crt /opt/registry/certs/registry.key
|
||||||
|
|
||||||
|
reverse_proxy registry:5000 {
|
||||||
|
header_up Host {host}
|
||||||
|
header_up X-Real-IP {remote}
|
||||||
|
header_up X-Forwarded-For {remote}
|
||||||
|
header_up X-Forwarded-Proto {scheme}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Restart the registry services
|
||||||
|
cd /opt/registry
|
||||||
|
docker compose down
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# Test Docker login
|
||||||
|
echo "your-secure-registry-password" | docker login YOUR_ACTUAL_IP_ADDRESS -u registry-user --password-stdin
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: This approach uses our own certificate chain, which Docker already trusts, eliminating the need to extract or trust Caddy's certificates.
|
||||||
|
|
||||||
### Step 6: Install Forgejo Actions Runner
|
### Step 6: Install Forgejo Actions Runner
|
||||||
|
|
||||||
|
|
|
@ -4,14 +4,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
YOUR_DOMAIN_NAME {
|
# Option A: Self-signed certificates (IP address)
|
||||||
# Use generated TLS certificate
|
YOUR_ACTUAL_IP_ADDRESS {
|
||||||
tls /etc/caddy/certs/registry.crt /etc/caddy/certs/registry.key
|
# Use our generated TLS certificate
|
||||||
|
tls /opt/registry/certs/registry.crt /opt/registry/certs/registry.key
|
||||||
|
|
||||||
# Security headers
|
# Security headers
|
||||||
header {
|
header {
|
||||||
X-Content-Type-Options nosniff
|
X-Content-Type-Options nosniff
|
||||||
X-Frame-Options DENY
|
X-Frame-Options DENY
|
||||||
|
X-XSS-Protection "1; mode=block"
|
||||||
|
Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
Content-Security-Policy "default-src 'self'; frame-ancestors 'none'"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Handle registry operations based on URL patterns
|
# Handle registry operations based on URL patterns
|
||||||
|
@ -37,6 +41,7 @@ YOUR_DOMAIN_NAME {
|
||||||
header_up X-Forwarded-For {remote_host}
|
header_up X-Forwarded-For {remote_host}
|
||||||
header_up X-Forwarded-Proto {scheme}
|
header_up X-Forwarded-Proto {scheme}
|
||||||
header_up X-Forwarded-Host {host}
|
header_up X-Forwarded-Host {host}
|
||||||
|
header_up Host {host}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +51,7 @@ YOUR_DOMAIN_NAME {
|
||||||
header_up X-Forwarded-For {remote_host}
|
header_up X-Forwarded-For {remote_host}
|
||||||
header_up X-Forwarded-Proto {scheme}
|
header_up X-Forwarded-Proto {scheme}
|
||||||
header_up X-Forwarded-Host {host}
|
header_up X-Forwarded-Host {host}
|
||||||
|
header_up Host {host}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,3 +70,70 @@ YOUR_DOMAIN_NAME {
|
||||||
# Compression
|
# Compression
|
||||||
encode zstd gzip
|
encode zstd gzip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Option B: Let's Encrypt certificates (Domain name)
|
||||||
|
# Uncomment and customize for domain-based setup
|
||||||
|
# YOUR_DOMAIN_NAME {
|
||||||
|
# # Let's Encrypt handles TLS automatically
|
||||||
|
#
|
||||||
|
# # Security headers
|
||||||
|
# header {
|
||||||
|
# X-Content-Type-Options nosniff
|
||||||
|
# X-Frame-Options DENY
|
||||||
|
# X-XSS-Protection "1; mode=block"
|
||||||
|
# Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
# Content-Security-Policy "default-src 'self'; frame-ancestors 'none'"
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# # Handle registry operations based on URL patterns
|
||||||
|
# @push_operations {
|
||||||
|
# path /v2/*/blobs/uploads/*
|
||||||
|
# path /v2/*/manifests/*
|
||||||
|
# method PUT POST PATCH DELETE
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# @pull_operations {
|
||||||
|
# path /v2/*/blobs/*
|
||||||
|
# path /v2/*/manifests/*
|
||||||
|
# path /v2/_catalog
|
||||||
|
# path /v2/*/tags/list
|
||||||
|
# method GET HEAD OPTIONS
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# # Require authentication for push operations
|
||||||
|
# handle @push_operations {
|
||||||
|
# import registry_auth
|
||||||
|
# reverse_proxy registry:5000 {
|
||||||
|
# header_up Authorization {http.request.header.Authorization}
|
||||||
|
# header_up X-Forwarded-For {remote_host}
|
||||||
|
# header_up X-Forwarded-Proto {scheme}
|
||||||
|
# header_up X-Forwarded-Host {host}
|
||||||
|
# header_up Host {host}
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# # Allow unauthenticated pull operations
|
||||||
|
# handle @pull_operations {
|
||||||
|
# reverse_proxy registry:5000 {
|
||||||
|
# header_up X-Forwarded-For {remote_host}
|
||||||
|
# header_up X-Forwarded-Proto {scheme}
|
||||||
|
# header_up X-Forwarded-Host {host}
|
||||||
|
# header_up Host {host}
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# # Block all other requests
|
||||||
|
# handle {
|
||||||
|
# respond "Registry operation not allowed" 405
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# # Logging
|
||||||
|
# log {
|
||||||
|
# output file /var/log/caddy/registry.log
|
||||||
|
# format json
|
||||||
|
# level INFO
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# # Compression
|
||||||
|
# encode zstd gzip
|
||||||
|
# }
|
||||||
|
|
|
@ -16,4 +16,4 @@ middleware:
|
||||||
storage:
|
storage:
|
||||||
- name: Redirect
|
- name: Redirect
|
||||||
options:
|
options:
|
||||||
baseurl: https://YOUR_DOMAIN_NAME
|
baseurl: https://YOUR_ACTUAL_IP_ADDRESS
|
23
registry/openssl.conf
Normal file
23
registry/openssl.conf
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[req]
|
||||||
|
distinguished_name = req_distinguished_name
|
||||||
|
req_extensions = v3_req
|
||||||
|
prompt = no
|
||||||
|
|
||||||
|
[req_distinguished_name]
|
||||||
|
C = US
|
||||||
|
ST = State
|
||||||
|
L = City
|
||||||
|
O = Organization
|
||||||
|
OU = IT
|
||||||
|
CN = YOUR_ACTUAL_IP_ADDRESS
|
||||||
|
|
||||||
|
[v3_req]
|
||||||
|
keyUsage = keyEncipherment, dataEncipherment
|
||||||
|
extendedKeyUsage = serverAuth
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = YOUR_ACTUAL_IP_ADDRESS
|
||||||
|
DNS.2 = localhost
|
||||||
|
IP.1 = YOUR_ACTUAL_IP_ADDRESS
|
||||||
|
IP.2 = 127.0.0.1
|
Loading…
Add table
Reference in a new issue