diff --git a/docs/DEV-HTTPS-SETUP.md b/docs/DEV-HTTPS-SETUP.md new file mode 100644 index 00000000..cb25d69c --- /dev/null +++ b/docs/DEV-HTTPS-SETUP.md @@ -0,0 +1,337 @@ +# HTTPS in Development Environment + +## Overview + +Development environment now uses HTTPS by default to match production behavior and catch SSL-related issues early. + +**Benefits:** +- ✅ Matches production HTTPS behavior +- ✅ Tests SSL/TLS configurations +- ✅ Catches mixed content warnings +- ✅ Tests secure cookie handling +- ✅ Better dev-prod parity + +--- + +## Quick Start + +### 1. Deploy with HTTPS Enabled + +```bash +# Start development environment +skaffold dev --profile=dev + +# Wait for certificate to be issued +kubectl get certificate -n bakery-ia + +# You should see: +# NAME READY SECRET AGE +# bakery-dev-tls-cert True bakery-dev-tls-cert 1m +``` + +### 2. Access Your Application + +```bash +# Access via HTTPS (will show certificate warning in browser) +open https://localhost + +# Or via curl (use -k to skip certificate verification) +curl -k https://localhost/api/health +``` + +--- + +## Trust the Self-Signed Certificate + +To avoid browser certificate warnings, you need to trust the self-signed certificate. + +### Option 1: Accept Browser Warning (Quick & Easy) + +When you visit `https://localhost`: +1. Browser shows "Your connection is not private" or similar +2. Click "Advanced" or "Show details" +3. Click "Proceed to localhost" or "Accept the risk" +4. Certificate warning will appear on first visit only per browser session + +### Option 2: Trust Certificate in System (Recommended) + +#### On macOS: + +```bash +# 1. Export the certificate from Kubernetes +kubectl get secret bakery-dev-tls-cert -n bakery-ia -o jsonpath='{.data.tls\.crt}' | base64 -d > /tmp/bakery-dev-cert.crt + +# 2. Add to Keychain +sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /tmp/bakery-dev-cert.crt + +# 3. Verify +security find-certificate -c localhost -a + +# 4. Cleanup +rm /tmp/bakery-dev-cert.crt +``` + +**Alternative (GUI):** +1. Export certificate: `kubectl get secret bakery-dev-tls-cert -n bakery-ia -o jsonpath='{.data.tls\.crt}' | base64 -d > bakery-dev-cert.crt` +2. Double-click the `.crt` file to open Keychain Access +3. Find "localhost" certificate +4. Double-click → Trust → "Always Trust" +5. Close and enter your password + +#### On Linux: + +```bash +# 1. Export the certificate +kubectl get secret bakery-dev-tls-cert -n bakery-ia -o jsonpath='{.data.tls\.crt}' | base64 -d | sudo tee /usr/local/share/ca-certificates/bakery-dev.crt + +# 2. Update CA certificates +sudo update-ca-certificates + +# 3. For browsers (Chromium/Chrome) +mkdir -p $HOME/.pki/nssdb +certutil -d sql:$HOME/.pki/nssdb -A -t "P,," -n "Bakery Dev" -i /usr/local/share/ca-certificates/bakery-dev.crt +``` + +#### On Windows: + +```powershell +# 1. Export the certificate +kubectl get secret bakery-dev-tls-cert -n bakery-ia -o jsonpath='{.data.tls.crt}' | Out-File -Encoding ASCII bakery-dev-cert.crt + +# 2. Import to Trusted Root +Import-Certificate -FilePath .\bakery-dev-cert.crt -CertStoreLocation Cert:\LocalMachine\Root + +# Or use GUI: +# - Double-click bakery-dev-cert.crt +# - Install Certificate +# - Store Location: Local Machine +# - Place in: Trusted Root Certification Authorities +``` + +--- + +## Testing HTTPS + +### Test with curl + +```bash +# Without certificate verification (quick test) +curl -k https://localhost/api/health + +# With certificate verification (after trusting cert) +curl https://localhost/api/health + +# Check certificate details +curl -vI https://localhost/api/health 2>&1 | grep -A 10 "Server certificate" + +# Test CORS with HTTPS +curl -H "Origin: https://localhost:3000" \ + -H "Access-Control-Request-Method: POST" \ + -X OPTIONS https://localhost/api/health +``` + +### Test with Browser + +1. Open `https://localhost` +2. Check for SSL/TLS padlock in address bar +3. Click padlock → View certificate +4. Verify: + - Issued to: localhost + - Issued by: localhost (self-signed) + - Valid for: 90 days + +### Test Frontend + +```bash +# Update your frontend .env to use HTTPS +echo "VITE_API_URL=https://localhost/api" > frontend/.env.local + +# Frontend should now make HTTPS requests +``` + +--- + +## Certificate Details + +### Certificate Specifications + +- **Type**: Self-signed (for development) +- **Algorithm**: RSA 2048-bit +- **Validity**: 90 days (auto-renews 15 days before expiration) +- **Common Name**: localhost +- **DNS Names**: + - localhost + - bakery-ia.local + - api.bakery-ia.local + - *.bakery-ia.local +- **IP Addresses**: 127.0.0.1, ::1 + +### Certificate Issuer + +- **Issuer**: `selfsigned-issuer` (cert-manager ClusterIssuer) +- **Auto-renewal**: Managed by cert-manager +- **Secret Name**: `bakery-dev-tls-cert` + +--- + +## Troubleshooting + +### Certificate Not Issued + +```bash +# Check certificate status +kubectl describe certificate bakery-dev-tls-cert -n bakery-ia + +# Check cert-manager logs +kubectl logs -n cert-manager deployment/cert-manager + +# Check if cert-manager is installed +kubectl get pods -n cert-manager + +# If cert-manager is not installed: +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml +``` + +### Certificate Warning in Browser + +**Normal for self-signed certificates!** Choose one: +1. Click "Proceed" (quick, temporary) +2. Trust the certificate in your system (permanent) + +### Mixed Content Warnings + +If you see "mixed content" errors: +- Ensure all API calls use HTTPS +- Check for hardcoded HTTP URLs +- Update `VITE_API_URL` to use HTTPS + +### Certificate Expired + +```bash +# Check expiration +kubectl get certificate bakery-dev-tls-cert -n bakery-ia -o jsonpath='{.status.notAfter}' + +# Force renewal +kubectl delete certificate bakery-dev-tls-cert -n bakery-ia +kubectl apply -k infrastructure/kubernetes/overlays/dev + +# cert-manager will automatically recreate it +``` + +### Browser Shows "NET::ERR_CERT_AUTHORITY_INVALID" + +This is expected for self-signed certificates. Options: +1. Click "Advanced" → "Proceed to localhost" +2. Trust the certificate (see instructions above) +3. Use curl with `-k` flag for testing + +--- + +## Disable HTTPS (Not Recommended) + +If you need to temporarily disable HTTPS: + +```bash +# Edit dev-ingress.yaml +vim infrastructure/kubernetes/overlays/dev/dev-ingress.yaml + +# Change: +# nginx.ingress.kubernetes.io/ssl-redirect: "true" → "false" +# nginx.ingress.kubernetes.io/force-ssl-redirect: "true" → "false" + +# Comment out the tls section: +# tls: +# - hosts: +# - localhost +# secretName: bakery-dev-tls-cert + +# Redeploy +skaffold dev --profile=dev +``` + +--- + +## Differences from Production + +| Aspect | Development | Production | +|--------|-------------|------------| +| Certificate Type | Self-signed | Let's Encrypt | +| Validity | 90 days | 90 days | +| Auto-renewal | cert-manager | cert-manager | +| Trust | Manual trust needed | Automatically trusted | +| Domains | localhost | Real domains | +| Browser Warning | Yes (self-signed) | No (CA-signed) | + +--- + +## FAQ + +### Q: Why am I seeing certificate warnings? +**A:** Self-signed certificates aren't trusted by browsers by default. Trust the certificate or click "Proceed." + +### Q: Do I need to trust the certificate? +**A:** No, but it makes development easier. You can click "Proceed" on each browser session. + +### Q: Will this affect my frontend development? +**A:** Slightly. Update `VITE_API_URL` to use `https://`. Otherwise works the same. + +### Q: Can I use HTTP instead? +**A:** Yes, but not recommended. It reduces dev-prod parity and won't catch HTTPS issues. + +### Q: How often do I need to re-trust the certificate? +**A:** Only when the certificate is recreated (every 90 days or when you delete the cluster). + +### Q: Does this work with bakery-ia.local? +**A:** Yes! The certificate is valid for both `localhost` and `bakery-ia.local`. + +--- + +## Additional Security Testing + +With HTTPS enabled, you can now test: + +### 1. Secure Cookies +```javascript +// In your frontend +document.cookie = "session=test; Secure; SameSite=Strict"; +``` + +### 2. Mixed Content Detection +```javascript +// This will show warning in dev (good - catches prod issues!) +fetch('http://api.example.com/data') // ❌ Mixed content +fetch('https://api.example.com/data') // ✅ Secure +``` + +### 3. HSTS (HTTP Strict Transport Security) +```bash +# Check HSTS headers +curl -I https://localhost/api/health | grep -i strict +``` + +### 4. TLS Version Testing +```bash +# Test TLS 1.2 +curl --tlsv1.2 https://localhost/api/health + +# Test TLS 1.3 +curl --tlsv1.3 https://localhost/api/health +``` + +--- + +## Summary + +✅ **Enabled**: HTTPS in development by default +✅ **Certificate**: Self-signed, auto-renewed +✅ **Access**: `https://localhost` +✅ **Trust**: Optional but recommended +✅ **Benefit**: Better dev-prod parity + +**Next Steps:** +1. Deploy: `skaffold dev --profile=dev` +2. Access: `https://localhost` +3. Trust: Follow instructions above (optional) +4. Test: Verify HTTPS works + +For issues, see Troubleshooting section or check cert-manager logs. diff --git a/docs/DEV-PROD-PARITY-CHANGES.md b/docs/DEV-PROD-PARITY-CHANGES.md index a8d90f6f..d852e252 100644 --- a/docs/DEV-PROD-PARITY-CHANGES.md +++ b/docs/DEV-PROD-PARITY-CHANGES.md @@ -79,6 +79,57 @@ nginx.ingress.kubernetes.io/cors-allow-origin: "http://localhost,http://localhos --- +### 4. **Enabled HTTPS with Self-Signed Certificates** + +**Files**: +- `infrastructure/kubernetes/overlays/dev/dev-ingress.yaml` +- `infrastructure/kubernetes/overlays/dev/dev-certificate.yaml` +- `infrastructure/kubernetes/overlays/dev/kustomization.yaml` + +Changed: +```yaml +# Ingress +nginx.ingress.kubernetes.io/ssl-redirect: "false" → "true" +nginx.ingress.kubernetes.io/force-ssl-redirect: "false" → "true" + +# Added TLS configuration +tls: + - hosts: + - localhost + - bakery-ia.local + secretName: bakery-dev-tls-cert + +# Updated CORS to prefer HTTPS +cors-allow-origin: "https://localhost,https://localhost:3000,..." (HTTPS first) +``` + +**Why**: +- Matches production HTTPS-only behavior +- Tests SSL/TLS configurations in development +- Catches mixed content warnings early +- Tests secure cookie handling +- Validates certificate management + +**Benefits**: +- SSL-related issues caught in development +- Tests cert-manager integration +- Secure cookie testing +- Mixed content detection +- Better security testing + +**Certificate Details**: +- Type: Self-signed (via cert-manager) +- Validity: 90 days (auto-renewed) +- Common Name: localhost +- Also valid for: bakery-ia.local, *.bakery-ia.local +- Issuer: selfsigned-issuer + +**Setup Required**: +- Trust certificate in browser/system (optional but recommended) +- See `docs/DEV-HTTPS-SETUP.md` for full instructions + +--- + ## Resource Impact ### Before Option 1 @@ -107,7 +158,7 @@ These settings intentionally remain different from production: | DEBUG | true | false | Need verbose debugging | | LOG_LEVEL | DEBUG | INFO | Need detailed logs | | PROFILING_ENABLED | true | false | Performance analysis | -| SSL/TLS | HTTP | HTTPS | Simpler local dev | +| Certificates | Self-signed | Let's Encrypt | Local CA for dev | | Image Pull Policy | Never | Always | Faster iteration | | Most replicas | 1 | 2-3 | Resource efficiency | | Monitoring | Disabled | Enabled | Save resources | @@ -132,6 +183,13 @@ These settings intentionally remain different from production: - Middleware tested - High limits prevent friction +### ✅ HTTPS/SSL Testing +- Matches production HTTPS-only behavior +- Tests certificate management +- Catches mixed content warnings +- Validates secure cookie handling +- Tests TLS configurations + ### ✅ Resource Efficiency - Only +30% resource usage - Maximum benefit for minimal cost diff --git a/infrastructure/kubernetes/overlays/dev/dev-certificate.yaml b/infrastructure/kubernetes/overlays/dev/dev-certificate.yaml new file mode 100644 index 00000000..b3d9c609 --- /dev/null +++ b/infrastructure/kubernetes/overlays/dev/dev-certificate.yaml @@ -0,0 +1,51 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: bakery-dev-tls-cert + namespace: bakery-ia +spec: + # Self-signed certificate for local development + secretName: bakery-dev-tls-cert + + # Certificate duration + duration: 2160h # 90 days + renewBefore: 360h # 15 days + + # Subject configuration + subject: + organizations: + - Bakery IA Development + + # Common name + commonName: localhost + + # DNS names this certificate is valid for + dnsNames: + - localhost + - bakery-ia.local + - api.bakery-ia.local + - "*.bakery-ia.local" + + # IP addresses (for localhost) + ipAddresses: + - 127.0.0.1 + - ::1 + + # Use self-signed issuer for development + issuerRef: + name: selfsigned-issuer + kind: ClusterIssuer + group: cert-manager.io + + # Private key configuration + privateKey: + algorithm: RSA + encoding: PKCS1 + size: 2048 + + # Usages + usages: + - server auth + - client auth + - digital signature + - key encipherment diff --git a/infrastructure/kubernetes/overlays/dev/dev-ingress.yaml b/infrastructure/kubernetes/overlays/dev/dev-ingress.yaml index 54f328ef..7eacb4a1 100644 --- a/infrastructure/kubernetes/overlays/dev/dev-ingress.yaml +++ b/infrastructure/kubernetes/overlays/dev/dev-ingress.yaml @@ -4,16 +4,21 @@ metadata: name: bakery-ingress namespace: bakery-ia annotations: - nginx.ingress.kubernetes.io/ssl-redirect: "false" - nginx.ingress.kubernetes.io/force-ssl-redirect: "false" + # Dev-Prod Parity: Enable HTTPS by default + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + # Dev-Prod Parity: Use specific origins instead of wildcard to catch CORS issues early - nginx.ingress.kubernetes.io/cors-allow-origin: "http://localhost,http://localhost:3000,http://localhost:3001,http://127.0.0.1,http://127.0.0.1:3000,http://127.0.0.1:3001,http://bakery-ia.local,https://localhost,https://127.0.0.1" + # HTTPS origins first (preferred), with HTTP fallback for development flexibility + nginx.ingress.kubernetes.io/cors-allow-origin: "https://localhost,https://localhost:3000,https://localhost:3001,https://127.0.0.1,https://127.0.0.1:3000,https://127.0.0.1:3001,https://bakery-ia.local,http://localhost,http://localhost:3000,http://localhost:3001,http://127.0.0.1,http://127.0.0.1:3000" nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS, PATCH" nginx.ingress.kubernetes.io/cors-allow-headers: "Content-Type, Authorization, X-Requested-With, Accept, Origin, Cache-Control" nginx.ingress.kubernetes.io/cors-allow-credentials: "true" nginx.ingress.kubernetes.io/enable-cors: "true" + # Prevent nginx from redirecting to add trailing slashes nginx.ingress.kubernetes.io/use-regex: "true" + # Development, SSE and WebSocket annotations nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" nginx.ingress.kubernetes.io/proxy-connect-timeout: "600" @@ -22,10 +27,16 @@ metadata: nginx.ingress.kubernetes.io/proxy-buffering: "off" nginx.ingress.kubernetes.io/proxy-http-version: "1.1" nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "3600" + # WebSocket upgrade support nginx.ingress.kubernetes.io/websocket-services: "gateway-service" spec: ingressClassName: nginx + tls: + - hosts: + - localhost + - bakery-ia.local + secretName: bakery-dev-tls-cert rules: - host: localhost http: diff --git a/infrastructure/kubernetes/overlays/dev/kustomization.yaml b/infrastructure/kubernetes/overlays/dev/kustomization.yaml index 70b46097..15f62096 100644 --- a/infrastructure/kubernetes/overlays/dev/kustomization.yaml +++ b/infrastructure/kubernetes/overlays/dev/kustomization.yaml @@ -12,6 +12,9 @@ resources: # Monitoring disabled for dev to save resources # - ../../base/components/monitoring - dev-ingress.yaml + # Dev-Prod Parity: Enable HTTPS with self-signed certificates + - dev-certificate.yaml + - ../../base/components/cert-manager/cluster-issuer-staging.yaml # Exclude nominatim from dev to save resources # Using scale to 0 for StatefulSet to prevent pod creation