# SSL/TLS Certificate Management

*Managing SSL certificates for the homelab infrastructure*

---

## Overview

The homelab uses Nginx Proxy Manager (NPM) as the primary certificate authority, with Let's Encrypt providing free SSL certificates.

---

## Certificate Authorities

### Primary: Let's Encrypt
- **Provider:** Let's Encrypt
- **Validation:** HTTP-01 (automatic via NPM)
- **Renewal:** Automatic at 90 days
- **Domains:** *.vish.local, *.vish.gg

### Secondary: Self-Signed
- **Use:** Internal services (non-public)
- **Tool:** OpenSSL
- **Regeneration:** As needed

---

## Certificate Locations

### Nginx Proxy Manager
```
/opt/docker/npm/data/
├── letsencrypt/
│   └── accounts/
│       └── acme-v02.api.letsencrypt.org/
└── ssl/
    └── <domain>/
        ├── fullchain.pem
        ├── privkey.pem
        └── bundle.crt
```

### Services with Own Certs
- **Authentik:** `/opt/authentik/ssl/`
- **Matrix:** `/etc/matrix-synapse/ssl/`
- **PostgreSQL:** `/etc/ssl/private/`

---

## Adding New Certificates

### Via NPM UI (Recommended)

1. Access NPM: `http://calypso.vish.local:81`
2. Navigate to **SSL Certificates** → **Add SSL Certificate**
3. Enter domain names:
   - `service.vish.local` (internal)
   - `service.vish.gg` (public)
4. Enable **Force SSL**
5. Click **Save**

### Via CLI (Automation)

```bash
# Using certbot directly
certbot certonly --webroot \
  -w /var/www/html \
  -d service.vish.local \
  --agree-tos \
  --email admin@vish.local
```

---

## Certificate Renewal

### Automatic (Default)
- NPM auto-renews 7 days before expiration
- No action required
- Check logs: NPM → Logs

### Manual Renewal

```bash
# Force renewal via NPM
docker exec nginx-proxy-manager npm --root /etc/npm \
  force-renew

# Or via API
curl -X POST http://npm/api/nginx/certificates/<id>/renew
```

### Ansible Playbook
```bash
ansible-playbook ansible/automation/playbooks/certificate_renewal.yml
```

---

## Certificate Status

### Check Expiration

```bash
# Via NPM
# Navigate to SSL Certificates tab

# Via openssl
echo | openssl s_client -connect service.vish.local:443 2>/dev/null | openssl x509 -noout -dates

# Via script
cd /opt/npm/letsencrypt/live/
for cert in */; do
  echo "$cert: $(openssl x509 -enddate -noout -in "$cert/cert.pem" | cut -d= -f2)"
done
```

### Certificate Dashboard

| Domain | Expiry | Status | Renews |
|--------|--------|--------|--------|
| vish.gg | +85 days | ✅ Active | Auto |
| *.vish.local | +85 days | ✅ Active | Auto |

---

## Common Issues

### Rate Limiting

**Problem:** Too many certificate requests

**Solution:**
- Wait 1 hour (Let's Encrypt limit)
- Use staging environment for testing
- Request multiple domains in one cert

### DNS Validation Failure

**Problem:** ACME challenge fails

**Solution:**
- Verify DNS A record points to public IP
- Check firewall allows port 80
- Ensure no CNAME conflicts

### Mixed Content Warnings

**Problem:** HTTP resources on HTTPS page

**Solution:**
- Update service config to use HTTPS URLs
- For internal services, use HTTP (NPM handles SSL)
- Check browser console for details

### Certificate Mismatch

**Problem:** Wrong certificate served

**Solution:**
1. Check NPM proxy host settings
2. Verify certificate is assigned
3. Clear browser cache
4. Check for multiple certificates

---

## Internal Services (Self-Signed)

### Creating Self-Signed Cert

```bash
# Create directory
mkdir -p /opt/service/ssl

# Generate certificate
openssl req -x509 -nodes -days 365 \
  -newkey rsa:2048 \
  -keyout /opt/service/ssl/key.pem \
  -out /opt/service/ssl/cert.pem \
  -addext "subjectAltName=DNS:service.local,DNS:service"

# Set permissions
chmod 600 /opt/service/ssl/key.pem
```

### Adding to Trust Store

```bash
# Linux (Ubuntu/Debian)
sudo cp /opt/service/ssl/cert.pem /usr/local/share/ca-certificates/service.crt
sudo update-ca-certificates

# macOS
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /opt/service/ssl/cert.pem
```

---

## Matrix/Synapse Certificates

### Custom Certificate Setup

```yaml
# docker-compose.yml
services:
  synapse:
    environment:
      - SYNAPSE_TLS_CERT_FILE=/ssl/tls.crt
      - SYNAPSE_TLS_KEY_FILE=/ssl/tls.key
    volumes:
      - ./ssl:/ssl:ro
```

### Federation Certificates

```bash
# Add to TLS certificates
/usr/local/bin/REDACTED_APP_PASSWORD \
  --server-name vish.local \
  --tls-cert /opt/npm/ssl/vish.gg/fullchain.pem \
  --tls-key /opt/npm/ssl/vish.gg/privkey.pem
```

---

## Security Best Practices

### Key Permissions
```bash
# Private keys should be readable only by root
chmod 600 /path/to/privkey.pem
chown root:root /path/to/privkey.pem
```

### Cipher Suites

Configure in NPM under **Settings → SSL → Advanced**:

```
ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA256
```

### HSTS

Enable in NPM:
- **Settings → SSL → Force HSTS**
- Preload recommended

---

## Backup

### Backup Certificates

```bash
# Backup NPM certificates
tar -czf backups/ssl-$(date +%Y%m%d).tar.gz \
  /opt/docker/npm/data/letsencrypt/ \
  /opt/docker/npm/data/ssl/
```

### Restore

```bash
# Restore
tar -xzf backups/ssl-20240101.tar.gz -C /

# Restart NPM
docker-compose -f /opt/docker/npm/docker-compose.yml restart
```

---

## Monitoring

### Expiration Alerts

Configure in Prometheus/Alertmanager:
```yaml
groups:
- name: certificates
  rules:
  - alert: REDACTED_APP_PASSWORD
    expr: (certify_not_after - time()) < (86400 * 30)
    for: 1h
    labels:
      severity: warning
    annotations:
      summary: "Certificate expiring soon"
```

---

## Useful Commands

```bash
# Check all certificates
docker exec nginx-proxy-manager npm --root /etc/npm list

# Force renewal
docker exec nginx-proxy-manager npm --root /etc/npm force-renew

# Manual ACME challenge
docker exec -it nginx-proxy-manager sh
cd /etc/letsencrypt/renewal-hooks/deploy/

# Verify certificate
openssl s_client -connect vish.gg:443 -servername vish.gg
```

---

## Links

- [NPM Documentation](https://nginxproxymanager.com/)
- [Let's Encrypt Docs](https://letsencrypt.org/docs/)
- [SSL Labs Test](https://www.ssllabs.com/ssltest/)
