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 <noreply@anthropic.com>
This commit is contained in:
Proudlock
2026-09-14 14:47:37 -04:00
parent bb301c6d84
commit fd6240772e
5 changed files with 263 additions and 0 deletions

140
certs/README.md Normal file
View File

@@ -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`

14
certs/openssl.cnf Normal file
View File

@@ -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

View File

@@ -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 ""

View File

@@ -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 ""