From fd6240772e8d17b1aff5a245f4a3137cd04f7995 Mon Sep 17 00:00:00 2001 From: Proudlock <270015376@geaerospace.com> Date: Mon, 14 Sep 2026 14:47:37 -0400 Subject: [PATCH] Add server certificate management documentation and regeneration scripts Documentation: - certs/README.md: Complete guide for managing nginx TLS certificates - Server cert generation (self-signed, CA-signed, Let's Encrypt) - Certificate expiry monitoring and renewal procedures - Agent certificate pinning configuration - Air-gapped deployment notes Tooling: - certs/regenerate-server-cert.sh: Bash script for cert regeneration - certs/regenerate-server-cert.ps1: PowerShell script for Windows - Both scripts backup old certs and display new thumbprint Config: - Updated .gitignore to allow cert management files (docs/scripts/config) - Private keys and actual certificates still excluded (security) Context: - User asked where to manage server-side certificate - Current cert: self-signed, CN=localhost, valid 1 year - Agents validate server cert via pinning or Windows trusted roots - Critical for mTLS client cert authentication (ADR-006) Co-Authored-By: Claude Sonnet 4.5 --- .gitignore | 4 + certs/README.md | 140 +++++++++++++++++++++++++++++++ certs/openssl.cnf | 14 ++++ certs/regenerate-server-cert.ps1 | 53 ++++++++++++ certs/regenerate-server-cert.sh | 52 ++++++++++++ 5 files changed, 263 insertions(+) create mode 100644 certs/README.md create mode 100644 certs/openssl.cnf create mode 100644 certs/regenerate-server-cert.ps1 create mode 100644 certs/regenerate-server-cert.sh diff --git a/.gitignore b/.gitignore index 08d35fe3..25202407 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,10 @@ id_rsa.* # Local/test TLS material for the dev HTTPS listener. Self-signed, per-host, # never shipped. The container mounts these at runtime; a site provides its own. certs/ +!certs/README.md +!certs/regenerate-server-cert.sh +!certs/regenerate-server-cert.ps1 +!certs/openssl.cnf secrets.json secrets.yaml secrets.yml diff --git a/certs/README.md b/certs/README.md new file mode 100644 index 00000000..79d1051a --- /dev/null +++ b/certs/README.md @@ -0,0 +1,140 @@ +# ShopDB Server Certificate Management + +This directory contains the TLS certificates for ShopDB's nginx server. + +## Current Certificate + +- **File**: `server.crt` (certificate) and `server.key` (private key) +- **Common Name**: localhost +- **Subject Alternative Names**: localhost, 192.168.100.1 +- **Validity**: 1 year from generation date +- **Type**: Self-signed (for development and air-gapped sites) + +## Certificate Location + +The `certs/` directory is mounted into the nginx container at `/etc/nginx/certs/`: + +```yaml +# docker-compose.yml +nginx: + volumes: + - ./certs:/etc/nginx/certs:ro +``` + +Referenced in `nginx.conf`: +```nginx +ssl_certificate /etc/nginx/certs/server.crt; +ssl_certificate_key /etc/nginx/certs/server.key; +``` + +## Generating a New Server Certificate + +### Option 1: Self-Signed (Development/Air-Gapped) + +Generate a new self-signed certificate valid for 1 year: + +```bash +cd certs + +# Generate new private key and certificate +openssl req -x509 -newkey rsa:2048 -nodes \ + -keyout server.key \ + -out server.crt \ + -days 365 \ + -config openssl.cnf + +# Restart nginx to pick up new certificate +docker-compose restart nginx +``` + +### Option 2: Custom Certificate (Production) + +If you have a certificate from a trusted CA or internal PKI: + +1. Copy your certificate to `certs/server.crt` +2. Copy your private key to `certs/server.key` +3. Update `certs/openssl.cnf` with the correct CN and SANs +4. Restart nginx: `docker-compose restart nginx` + +### Option 3: Let's Encrypt (Public-Facing Sites) + +For internet-accessible ShopDB instances, use Let's Encrypt: + +```bash +# Install certbot +apt-get install certbot python3-certbot-nginx + +# Generate certificate (interactive) +certbot --nginx -d your-domain.com + +# Copy certificates to certs directory +cp /etc/letsencrypt/live/your-domain.com/fullchain.pem certs/server.crt +cp /etc/letsencrypt/live/your-domain.com/privkey.pem certs/server.key + +# Restart nginx +docker-compose restart nginx +``` + +## Certificate Expiry + +Check current certificate expiry: + +```bash +openssl x509 -in certs/server.crt -noout -dates +``` + +ShopDB agents validate the server certificate using: +- **Pinning** (if `ServerCertThumbprint` configured in agent appsettings.json) +- **Windows trusted roots** (if no pinning configured) + +## Agent Certificate Pinning + +To enable server certificate pinning on agents: + +1. Get the server certificate thumbprint: + ```powershell + # Windows + certutil -hashfile certs\server.crt SHA256 + + # Or from certificate details + openssl x509 -in certs/server.crt -fingerprint -sha256 -noout + ``` + +2. Update agent `appsettings.json`: + ```json + { + "ServerCertThumbprint": "AA:BB:CC:DD:EE:FF:..." + } + ``` + +3. Restart the agent service + +**NOTE**: When you renew the server certificate, you must update `ServerCertThumbprint` on all agents before the old certificate expires, or agents will reject the new certificate. + +## Air-Gapped Site Deployment + +For air-gapped sites: +1. Generate certificate on work PC (with correct hostname/IP) +2. Include `certs/` directory in bundle (already in git repo) +3. Deploy bundle to site +4. Certificate deploys automatically with `docker-compose up` + +## Certificate Files + +- `server.crt` - Server certificate (nginx HTTPS) +- `server.key` - Server private key +- `openssl.cnf` - OpenSSL configuration for certificate generation +- `test-client.crt`, `test-client.key`, `test-client.p12` - Test client certificates (for development only) + +## Security Notes + +- **Private Key Protection**: `server.key` should be readable only by nginx (done via Docker volume mount) +- **Self-Signed Trust**: Self-signed certificates are expected in air-gapped environments +- **Agent Pinning**: Use `ServerCertThumbprint` to prevent MITM attacks with self-signed certs +- **Expiry Monitoring**: Set calendar reminders to renew certificates before expiry + +## Related Documentation + +- Agent certificate renewal: `plugins/endpoints/api/certificate_renewal.py` +- Client certificate authentication: `shopdb/core/auth/client_cert.py` +- Nginx configuration: `nginx.conf` diff --git a/certs/openssl.cnf b/certs/openssl.cnf new file mode 100644 index 00000000..e4345d70 --- /dev/null +++ b/certs/openssl.cnf @@ -0,0 +1,14 @@ +[req] +distinguished_name = req_distinguished_name +x509_extensions = v3_req +prompt = no + +[req_distinguished_name] +CN = localhost + +[v3_req] +subjectAltName = @alt_names + +[alt_names] +DNS.1 = localhost +IP.1 = 192.168.100.1 diff --git a/certs/regenerate-server-cert.ps1 b/certs/regenerate-server-cert.ps1 new file mode 100644 index 00000000..ca7d3a0f --- /dev/null +++ b/certs/regenerate-server-cert.ps1 @@ -0,0 +1,53 @@ +# Regenerate self-signed server certificate for ShopDB nginx +# Run from repository root: .\certs\regenerate-server-cert.ps1 + +$ErrorActionPreference = "Stop" + +$CertDir = "certs" +$DaysValid = 365 + +Write-Host "=== ShopDB Server Certificate Generator ===" -ForegroundColor Cyan +Write-Host "" + +# Check if running from repo root +if (-not (Test-Path "docker-compose.yml")) { + Write-Host "ERROR: Must run from repository root" -ForegroundColor Red + Write-Host "Usage: .\certs\regenerate-server-cert.ps1" + exit 1 +} + +# Backup old certificate +if (Test-Path "$CertDir\server.crt") { + Write-Host "Backing up existing certificate..." + Copy-Item "$CertDir\server.crt" "$CertDir\server.crt.backup" -Force + Copy-Item "$CertDir\server.key" "$CertDir\server.key.backup" -Force + + $oldExpiry = openssl x509 -in "$CertDir\server.crt" -noout -enddate 2>$null + Write-Host " Old certificate: $oldExpiry" +} + +# Generate new certificate +Write-Host "" +Write-Host "Generating new self-signed certificate (valid $DaysValid days)..." -ForegroundColor Yellow + +openssl req -x509 -newkey rsa:2048 -nodes ` + -keyout "$CertDir\server.key" ` + -out "$CertDir\server.crt" ` + -days $DaysValid ` + -config "$CertDir\openssl.cnf" 2>$null + +Write-Host "" +Write-Host "Certificate generated successfully!" -ForegroundColor Green +Write-Host "" + +# Show certificate details +Write-Host "=== Certificate Details ===" -ForegroundColor Cyan +openssl x509 -in "$CertDir\server.crt" -noout -subject -dates -fingerprint -sha256 + +Write-Host "" +Write-Host "=== Next Steps ===" -ForegroundColor Yellow +Write-Host "1. Restart nginx: docker-compose restart nginx" +Write-Host "2. If agents use ServerCertThumbprint pinning:" +Write-Host " - Update appsettings.json with new thumbprint (shown above)" +Write-Host " - Restart all agents BEFORE old certificate expires" +Write-Host "" diff --git a/certs/regenerate-server-cert.sh b/certs/regenerate-server-cert.sh new file mode 100644 index 00000000..6529d10f --- /dev/null +++ b/certs/regenerate-server-cert.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# Regenerate self-signed server certificate for ShopDB nginx +# Run this script from the repository root: bash certs/regenerate-server-cert.sh + +set -e + +CERT_DIR="certs" +DAYS_VALID=365 + +echo "=== ShopDB Server Certificate Generator ===" +echo "" + +# Check if running from repo root +if [ ! -f "docker-compose.yml" ]; then + echo "ERROR: Must run from repository root" + echo "Usage: bash certs/regenerate-server-cert.sh" + exit 1 +fi + +# Backup old certificate +if [ -f "$CERT_DIR/server.crt" ]; then + echo "Backing up existing certificate..." + cp "$CERT_DIR/server.crt" "$CERT_DIR/server.crt.backup" + cp "$CERT_DIR/server.key" "$CERT_DIR/server.key.backup" + OLD_EXPIRY=$(openssl x509 -in "$CERT_DIR/server.crt" -noout -enddate) + echo " Old certificate: $OLD_EXPIRY" +fi + +# Generate new certificate +echo "" +echo "Generating new self-signed certificate (valid $DAYS_VALID days)..." +openssl req -x509 -newkey rsa:2048 -nodes \ + -keyout "$CERT_DIR/server.key" \ + -out "$CERT_DIR/server.crt" \ + -days $DAYS_VALID \ + -config "$CERT_DIR/openssl.cnf" 2>/dev/null + +echo "" +echo "Certificate generated successfully!" +echo "" + +# Show certificate details +echo "=== Certificate Details ===" +openssl x509 -in "$CERT_DIR/server.crt" -noout -subject -dates -fingerprint -sha256 + +echo "" +echo "=== Next Steps ===" +echo "1. Restart nginx: docker-compose restart nginx" +echo "2. If agents use ServerCertThumbprint pinning:" +echo " - Update appsettings.json with new thumbprint (shown above)" +echo " - Restart all agents BEFORE old certificate expires" +echo ""