diff --git a/CI_CD_PIPELINE_SETUP_GUIDE.md b/CI_CD_PIPELINE_SETUP_GUIDE.md index 811744a..b1aa76d 100644 --- a/CI_CD_PIPELINE_SETUP_GUIDE.md +++ b/CI_CD_PIPELINE_SETUP_GUIDE.md @@ -684,6 +684,7 @@ sudo sed -i "s/YOUR_CI_CD_IP/YOUR_ACTUAL_IP_ADDRESS/g" /opt/APP_NAME/registry/Ca # Create environment file for registry authentication # First, create a secure password hash +# Save this password somewhere safe REGISTRY_PASSWORD="your-secure-registry-password" REGISTRY_PASSWORD_HASH=$(htpasswd -nbB registry-user "$REGISTRY_PASSWORD" | cut -d: -f2) @@ -731,7 +732,7 @@ sudo -u CI_SERVICE_USER openssl genrsa -out ca.key 4096 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" + -subj "/C=US/ST=State/L=City/O=Organization/OU=IT/CN=YOUR_DESIRED_CA" # Generate server private key sudo -u CI_SERVICE_USER openssl genrsa -out registry.key 4096 @@ -857,9 +858,6 @@ sudo su - CI_SERVICE_USER # Navigate to the application directory cd /opt/APP_NAME/registry -# Update Caddyfile to use FHS-compliant certificate paths -sudo sed -i "s|/opt/registry/certs|/etc/registry/certs|g" /opt/APP_NAME/registry/Caddyfile - # Start the Docker Registry and Caddy services using the registry compose file docker compose -f docker-compose.registry.yml up -d @@ -876,6 +874,12 @@ exit # Install systemd service from repository sudo cp /opt/APP_NAME/registry/docker-registry.service /etc/systemd/system/docker-registry.service +# Replace APP_NAME placeholder with actual application name +sudo sed -i "s/APP_NAME/YOUR_ACTUAL_APP_NAME/g" /etc/systemd/system/docker-registry.service + +# Replace CI_SERVICE_USER placeholder with actual CI service user name +sudo sed -i "s/CI_SERVICE_USER/YOUR_ACTUAL_CI_SERVICE_USER/g" /etc/systemd/system/docker-registry.service + # Enable and start Docker Registry service sudo systemctl daemon-reload sudo systemctl enable docker-registry.service diff --git a/registry/Caddyfile b/registry/Caddyfile index 0eadc6c..e133e78 100644 --- a/registry/Caddyfile +++ b/registry/Caddyfile @@ -1,139 +1,25 @@ -(registry_auth) { - basicauth { - {env.REGISTRY_USERNAME} {env.REGISTRY_PASSWORD_HASH} - } +# Auth-required pushes on 4443 +:4443 { + tls /etc/certs/registry.crt /etc/certs/registry.key + log + + # require auth on writes + @writes method PUT POST PATCH DELETE + basic_auth @writes { + registry-user DOCKER_REGISTRY_PASSWORD + } + + # also require auth on the /v2/ ping so Docker sends creds + @v2ping { + path /v2/ + method GET + } + basic_auth @v2ping { + registry-user DOCKER_REGISTRY_PASSWORD + } + + reverse_proxy /v2/* registry:5000 } -# Option A: Self-signed certificates (IP address) -YOUR_ACTUAL_IP_ADDRESS { - # Use our generated TLS certificate - tls /etc/caddy/certs/registry.crt /etc/caddy/certs/registry.key - # 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 -} - -# 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 -# } +# TODO: Add Option B: Let's Encrypt certificates (Domain name) diff --git a/registry/config.yml b/registry/config.yml index f68872c..09d7a38 100644 --- a/registry/config.yml +++ b/registry/config.yml @@ -9,11 +9,4 @@ storage: http: addr: :5000 headers: - X-Content-Type-Options: [nosniff] -middleware: - repository: - - name: AwsEc2PublicBlock - storage: - - name: Redirect - options: - baseurl: https://YOUR_ACTUAL_IP_ADDRESS \ No newline at end of file + X-Content-Type-Options: [nosniff] \ No newline at end of file diff --git a/registry/docker-compose.registry.yml b/registry/docker-compose.registry.yml index b3c32ae..d4b89e8 100644 --- a/registry/docker-compose.registry.yml +++ b/registry/docker-compose.registry.yml @@ -1,40 +1,29 @@ -version: '3' - services: registry: image: registry:2 container_name: registry - networks: - - sharenet-ci + restart: unless-stopped + environment: + REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry + # Optional but recommended if you want to be able to delete images: + REGISTRY_STORAGE_DELETE_ENABLED: "true" + # Listen only inside the compose network + REGISTRY_HTTP_ADDR: 0.0.0.0:5000 volumes: - - /var/lib/registry:/var/lib/registry - - ./config.yml:/etc/docker/registry/config.yml - ports: - - "127.0.0.1:5000:5000" # Localhost only + - ./registry:/var/lib/registry + expose: + - "5000" # internal only, not published caddy: - image: caddy:latest + image: caddy:2 container_name: caddy + restart: unless-stopped depends_on: - registry - networks: - - sharenet-ci ports: - - "80:80" - - "443:443" + - "443:443" # HTTPS only + - "4443:4443" + # deliberately no "80:80" – no HTTP volumes: - - ./Caddyfile:/etc/caddy/Caddyfile - - /etc/registry/certs:/etc/caddy/certs - - /var/log/registry:/var/log/caddy - - caddy_data:/data - - caddy_config:/config - env_file: - - .env - -volumes: - caddy_data: - caddy_config: - -networks: - sharenet-ci: - driver: bridge \ No newline at end of file + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - ./certs:/etc/certs:ro diff --git a/registry/openssl.conf b/registry/openssl.conf index 81bd817..04827e7 100644 --- a/registry/openssl.conf +++ b/registry/openssl.conf @@ -1,23 +1,16 @@ -[req] -distinguished_name = req_distinguished_name -req_extensions = v3_req -prompt = no +[ req ] +default_bits = 2048 +prompt = no +default_md = sha256 +req_extensions = req_ext +distinguished_name = dn -[req_distinguished_name] -C = US -ST = State -L = City -O = Organization -OU = IT -CN = YOUR_ACTUAL_IP_ADDRESS +[ dn ] +O=YOUR_REGISTRY_NAME +CN=YOUR_CI_CD_IP -[v3_req] -keyUsage = keyEncipherment, dataEncipherment -extendedKeyUsage = serverAuth +[ req_ext ] 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 \ No newline at end of file +[ alt_names ] +IP.1 = YOUR_CI_CD_IP \ No newline at end of file