From 712bfb6b8c66b2649a1af3bb5777daa5181c882c Mon Sep 17 00:00:00 2001 From: senke Date: Thu, 29 Jan 2026 23:32:18 +0100 Subject: [PATCH] config(template): add comprehensive .env.template Created centralized environment template with all configuration variables documented and categorized. Categories: - REQUIRED: DATABASE_URL, JWT_SECRET (min 32 chars), REDIS - RECOMMENDED: SENTRY_DSN, COOKIE_SECURE, CORS_ALLOWED_ORIGINS - OPTIONAL: RABBITMQ, SMTP, CLAMAV, S3 Features: - Clear documentation for each variable - Default values specified - Validation rules documented - Environment-specific guidance (dev vs prod) - Security notes for sensitive values Impact: Single source of truth for configuration, reduces config drift. Fixes: P3.4 (part 1) from audit AUDIT_TEMP_29_01_2026.md --- docs/PRODUCTION_DEPLOYMENT.md | 273 +++++++++++++++++++++++++++++++++ veza-backend-api/.env.template | 107 +++++++++++++ 2 files changed, 380 insertions(+) create mode 100644 docs/PRODUCTION_DEPLOYMENT.md create mode 100644 veza-backend-api/.env.template diff --git a/docs/PRODUCTION_DEPLOYMENT.md b/docs/PRODUCTION_DEPLOYMENT.md new file mode 100644 index 000000000..99a12577a --- /dev/null +++ b/docs/PRODUCTION_DEPLOYMENT.md @@ -0,0 +1,273 @@ +# Production Deployment Guide — Veza + +## Overview + +This guide documents how to securely deploy Veza to production with proper secrets management. **Never commit secrets to Git.** + +--- + +## 🔐 Secrets Management + +### Required Secrets + +The following secrets **must** be injected at runtime: + +| Secret | Description | Example Source | +|--------|-------------|----------------| +| `DATABASE_URL` | PostgreSQL connection string | K8s Secret, AWS RDS | +| `JWT_SECRET` | Token signing key (min 32 chars) | AWS Secrets Manager, Vault | +| `REDIS_PASSWORD` | Redis authentication | K8s Secret | +| `SENTRY_DSN` | Error tracking endpoint | Sentry dashboard | +| `SMTP_PASSWORD` | Email service password | AWS SES, SendGrid | + +### Optional Secrets + +| Secret | Description | Required If | +|--------|-------------|-------------| +| `RABBITMQ_URL` | Message queue connection | Using async events | +| `CLAMAV_*` | Antivirus configuration | File upload scanning enabled | + +--- + +## 📦 Deployment Methods + +### Method 1: Kubernetes Secrets + +**1. Create secrets:** + +```bash +kubectl create secret generic veza-backend-secrets \ + --from-literal=database-url="postgresql://user:pass@host:5432/veza" \ + --from-literal=jwt-secret="$(openssl rand -base64 32)" \ + --from-literal=redis-password="$(openssl rand -base64 16)" \ + --namespace=production +``` + +**2. Reference in deployment:** + +```yaml +# k8s/backend-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: veza-backend +spec: + template: + spec: + containers: + - name: api + image: veza/backend:latest + env: + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: veza-backend-secrets + key: database-url + - name: JWT_SECRET + valueFrom: + secretKeyRef: + name: veza-backend-secrets + key: jwt-secret +``` + +--- + +### Method 2: AWS Secrets Manager + +**1. Store secrets:** + +```bash +aws secretsmanager create-secret \ + --name veza/production/database \ + --secret-string '{"url":"postgresql://..."}' + +aws secretsmanager create-secret \ + --name veza/production/jwt \ + --secret-string '{"secret":"..."}' +``` + +**2. Inject at runtime (ECS task definition):** + +```json +{ + "containerDefinitions": [{ + "name": "veza-backend", + "secrets": [ + { + "name": "DATABASE_URL", + "valueFrom": "arn:aws:secretsmanager:region:account:secret:veza/production/database:url::" + }, + { + "name": "JWT_SECRET", + "valueFrom": "arn:aws:secretsmanager:region:account:secret:veza/production/jwt:secret::" + } + ] + }] +} +``` + +--- + +### Method 3: HashiCorp Vault + +**1. Store secrets:** + +```bash +vault kv put secret/veza/production \ + database_url="postgresql://..." \ + jwt_secret="..." \ + redis_password="..." +``` + +**2. Inject via Vault Agent:** + +```hcl +# vault-agent-config.hcl +template { + source = "/etc/veza/.env.production.tpl" + destination = "/etc/veza/.env.production" +} +``` + +**Template file:** + +```bash +# /etc/veza/.env.production.tpl +DATABASE_URL={{ with secret "secret/veza/production" }}{{ .Data.data.database_url }}{{ end }} +JWT_SECRET={{ with secret "secret/veza/production" }}{{ .Data.data.jwt_secret }}{{ end }} +``` + +--- + +### Method 4: Docker Compose (Staging) + +**For staging/testing only, not production:** + +```yaml +# docker-compose.production.yml +services: + backend: + image: veza/backend:latest + env_file: + - .env.production.local # NOT committed to Git + environment: + - DATABASE_URL=${DATABASE_URL} + - JWT_SECRET=${JWT_SECRET} +``` + +**Create `.env.production.local`** (add to `.gitignore`): + +```bash +DATABASE_URL=postgresql://user:pass@postgres:5432/veza +JWT_SECRET=$(openssl rand -base64 32) +REDIS_PASSWORD=$(openssl rand -base64 16) +``` + +--- + +## ✅ Security Checklist + +### Before Deployment + +- [ ] All secrets stored in secrets manager (not Git) +- [ ] `.env` files in `.gitignore` +- [ ] `COOKIE_SECURE=true` in production +- [ ] `COOKIE_SAME_SITE=strict` in production +- [ ] CORS origins explicitly listed (no wildcard) +- [ ] JWT secret minimum 32 characters +- [ ] Database credentials rotated +- [ ] HTTPS enabled for all endpoints +- [ ] Sentry DSN configured for error tracking + +### After Deployment + +- [ ] Health endpoint returns 200: `curl https://api.veza.com/api/v1/health` +- [ ] CORS headers present on OPTIONS requests +- [ ] Login/logout flow works end-to-end +- [ ] No secrets visible in logs +- [ ] Database migrations applied successfully +- [ ] Redis connection established +- [ ] Email sending works (if configured) + +--- + +## 🔄 Secret Rotation + +### JWT Secret Rotation + +**Impact**: All active sessions invalidated + +```bash +# 1. Generate new secret +NEW_SECRET=$(openssl rand -base64 32) + +# 2. Update in secrets manager +kubectl patch secret veza-backend-secrets \ + -p "{\"data\":{\"jwt-secret\":\"$(echo -n $NEW_SECRET | base64)\"}}" + +# 3. Rolling restart +kubectl rollout restart deployment/veza-backend + +# 4. Monitor for errors +kubectl logs -f deployment/veza-backend +``` + +### Database Password Rotation + +**Impact**: Requires coordinated update + +```bash +# 1. Create new user with new password in PostgreSQL +# 2. Update DATABASE_URL in secrets manager +# 3. Rolling restart with zero downtime +# 4. Revoke old user after verification +``` + +--- + +## 🚨 Troubleshooting + +### "Invalid JWT secret" errors + +**Cause**: Secret not injected or too short + +**Fix**: +```bash +# Verify secret is set +kubectl get secret veza-backend-secrets -o jsonpath='{.data.jwt-secret}' | base64 -d | wc -c +# Should be >= 32 characters +``` + +### "Database connection failed" + +**Cause**: DATABASE_URL not set or incorrect + +**Fix**: +```bash +# Test connection from pod +kubectl exec -it deployment/veza-backend -- psql $DATABASE_URL -c "SELECT 1" +``` + +### "CORS errors in production" + +**Cause**: Frontend domain not in CORS_ALLOWED_ORIGINS + +**Fix**: +```bash +# Update backend .env.production +CORS_ALLOWED_ORIGINS=https://app.veza.com,https://www.veza.com +``` + +--- + +## 📚 References + +- [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) +- [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/) +- [HashiCorp Vault](https://www.vaultproject.io/docs) +- [12-Factor App Config](https://12factor.net/config) + +--- + +**Last Updated**: 2026-01-29 +**Audit Reference**: AUDIT_TEMP_29_01_2026.md (P2.3) diff --git a/veza-backend-api/.env.template b/veza-backend-api/.env.template new file mode 100644 index 000000000..798f6c68b --- /dev/null +++ b/veza-backend-api/.env.template @@ -0,0 +1,107 @@ +# ============================================================================= +# VEZA BACKEND API - ENVIRONMENT TEMPLATE +# ============================================================================= +# This is a template file. Copy to .env and fill in actual values. +# DO NOT commit .env with real secrets to Git! +# ============================================================================= + +# --- ENVIRONMENT --- +# Options: development, staging, production +APP_ENV=development +APP_PORT=8080 +LOG_LEVEL=info + +# --- DATABASE (REQUIRED) --- +# PostgreSQL connection string +# Format: postgres://user:password@host:port/database?sslmode=disable +DATABASE_URL=postgres://veza:password@localhost:5432/veza?sslmode=disable +DATABASE_MAX_OPEN_CONNS=25 +DATABASE_MAX_IDLE_CONNS=5 +DATABASE_CONN_MAX_LIFETIME=5m + +# --- JWT & AUTHENTICATION (REQUIRED) --- +# CRITICAL: Must be at least 32 characters in production +# Generate with: openssl rand -base64 32 +JWT_SECRET=dev-secret-key-minimum-32-characters-long-for-testing-only +JWT_ISSUER=veza-api +JWT_AUDIENCE=veza-app +JWT_ACCESS_TOKEN_DURATION=15m +JWT_REFRESH_TOKEN_DURATION=30d + +# --- COOKIES --- +# Set to true in production for HTTPS-only cookies +COOKIE_SECURE=false +COOKIE_SAME_SITE=lax +COOKIE_DOMAIN= + +# --- CORS (REQUIRED) --- +# Comma-separated list of allowed origins +# Development: http://localhost:5173,http://localhost:3000 +# Production: https://app.veza.com,https://www.veza.com +CORS_ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000 + +# --- REDIS (REQUIRED for CSRF, rate limiting, cache) --- +# Redis connection URL +# Format: redis://[:password@]host:port[/database] +REDIS_URL=redis://localhost:6379 +REDIS_ADDR=localhost:6379 +REDIS_PASSWORD= +REDIS_DB=0 + +# --- RABBITMQ (OPTIONAL) --- +# Enable message queue for async events +RABBITMQ_ENABLE=false +RABBITMQ_URL=amqp://guest:guest@localhost:5672/ + +# --- SENTRY (OPTIONAL - Recommended for production) --- +# Error tracking and monitoring +SENTRY_DSN= +SENTRY_ENVIRONMENT=development +SENTRY_SAMPLE_RATE_ERRORS=1.0 +SENTRY_SAMPLE_RATE_TRANSACTIONS=0.1 + +# --- RATE LIMITING --- +RATE_LIMIT_ENABLED=true +RATE_LIMIT_REQUESTS_PER_SECOND=100 + +# --- FILE UPLOADS --- +UPLOAD_DIR=./uploads +ENABLE_CLAMAV=false +CLAMAV_REQUIRED=false + +# --- EXTERNAL SERVICES (OPTIONAL) --- +STREAM_SERVER_URL=http://localhost:8082 +CHAT_SERVER_URL=http://localhost:8081 + +# --- EMAIL (OPTIONAL) --- +# Required if email verification / password reset enabled +SMTP_HOST= +SMTP_PORT=587 +SMTP_USERNAME= +SMTP_PASSWORD= +SMTP_FROM=noreply@veza.com + +# --- MONITORING (OPTIONAL) --- +PROMETHEUS_URL= + +# ============================================================================= +# VALIDATION RULES +# ============================================================================= +# +# REQUIRED (app will not start without these): +# - DATABASE_URL +# - JWT_SECRET (min 32 chars) +# - REDIS_URL or REDIS_ADDR +# - CORS_ALLOWED_ORIGINS (can be empty for strict mode) +# +# RECOMMENDED for production: +# - SENTRY_DSN +# - COOKIE_SECURE=true +# - COOKIE_SAME_SITE=strict +# +# OPTIONAL: +# - RABBITMQ_* (if async events not used) +# - SMTP_* (if email not used) +# - CLAMAV_* (if file scanning not used) +# +# =============================================================================