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:
140
certs/README.md
Normal file
140
certs/README.md
Normal 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
14
certs/openssl.cnf
Normal 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
|
||||
53
certs/regenerate-server-cert.ps1
Normal file
53
certs/regenerate-server-cert.ps1
Normal 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 ""
|
||||
52
certs/regenerate-server-cert.sh
Normal file
52
certs/regenerate-server-cert.sh
Normal 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 ""
|
||||
Reference in New Issue
Block a user