diff --git a/.env.example b/.env.example index ddd7347..ea5e978 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,7 @@ LOG_LEVEL=INFO # Core Database (internal metadata DB) # ------------------------------ # Database that stores users, targets, metrics, query stats, and audit logs. +# DEV default only. Use strong unique credentials in production. DB_NAME=nexapg DB_USER=nexapg DB_PASSWORD=nexapg @@ -23,7 +24,7 @@ DB_PORT=5433 # ------------------------------ # Host port mapped to backend container port 8000. BACKEND_PORT=8000 -# JWT signing secret. Change this in every non-local environment. +# JWT signing secret. Never hardcode in source. Rotate regularly. JWT_SECRET_KEY=change_this_super_secret JWT_ALGORITHM=HS256 # Access token lifetime in minutes. @@ -31,6 +32,7 @@ JWT_ACCESS_TOKEN_MINUTES=15 # Refresh token lifetime in minutes (10080 = 7 days). JWT_REFRESH_TOKEN_MINUTES=10080 # Key used to encrypt monitored target passwords at rest. +# Never hardcode in source. Rotate with re-encryption plan. # Generate with: # python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" ENCRYPTION_KEY=REPLACE_WITH_FERNET_KEY diff --git a/README.md b/README.md index b787e60..e415272 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ It combines FastAPI, React, and PostgreSQL in a Docker Compose stack with RBAC, - [Production Proxy Profile](#production-proxy-profile) - [PostgreSQL Compatibility Smoke Test](#postgresql-compatibility-smoke-test) - [Dependency Exception Flow](#dependency-exception-flow) +- [Secret Management (Production)](#secret-management-production) - [Troubleshooting](#troubleshooting) - [Security Notes](#security-notes) @@ -414,6 +415,19 @@ Python dependency vulnerabilities are enforced by CI via `pip-audit`. - Full process and required metadata are documented in: - `docs/security/dependency-exceptions.md` +## Secret Management (Production) + +Secret handling guidance is documented in: + +- `docs/security/secret-management.md` + +It includes: + +- secure handling for `JWT_SECRET_KEY`, `ENCRYPTION_KEY`, `DB_PASSWORD`, and SMTP credentials +- clear **Do / Don't** rules +- recommended secret provider patterns (Vault/cloud/orchestrator/CI injection) +- practical rotation basics and operational checklist + ## Troubleshooting ### Backend container keeps restarting during `make migrate` @@ -448,3 +462,5 @@ Set target `sslmode` to `disable` (or correct SSL config on target DB). - RBAC enforced on protected endpoints - Audit logs for critical actions - Collector error logging includes throttling to reduce repeated noise +- Production secret handling and rotation guidance: + - `docs/security/secret-management.md` diff --git a/docs/security/secret-management.md b/docs/security/secret-management.md new file mode 100644 index 0000000..6dea2ed --- /dev/null +++ b/docs/security/secret-management.md @@ -0,0 +1,74 @@ +# Secret Management (Production) + +This guide defines secure handling for NexaPG secrets in production deployments. + +## In Scope Secrets + +- `JWT_SECRET_KEY` +- `ENCRYPTION_KEY` +- `DB_PASSWORD` +- SMTP credentials (configured in Admin Settings, encrypted at rest) + +## Do / Don't + +## Do + +- Use an external secret source (Vault, cloud secret manager, orchestrator secrets, or CI/CD secret injection). +- Keep secrets out of Git history and out of image layers. +- Use strong random values: + - JWT secret: at least 32+ bytes random + - Fernet key: generated via `Fernet.generate_key()` +- Restrict access to runtime secrets (least privilege). +- Rotate secrets on schedule and on incident. +- Store production `.env` with strict permissions if file-based injection is used: + - owner-only read/write (e.g., `chmod 600 .env`) +- Audit who can read/update secrets in your deployment platform. + +## Don't + +- Do **not** hardcode secrets in source code. +- Do **not** commit `.env` with real values. +- Do **not** bake production secrets into Dockerfiles or image build args. +- Do **not** share secrets in tickets, chat logs, or CI console output. +- Do **not** reuse the same secrets between environments. + +## Recommended Secret Providers + +Pick one of these models: + +1. Platform/Cloud secrets + - AWS Secrets Manager + - Azure Key Vault + - Google Secret Manager +2. HashiCorp Vault +3. CI/CD secret injection + - Inject as runtime env vars during deployment +4. Docker/Kubernetes secrets + - Prefer secret mounts or orchestrator-native secret stores + +If you use plain `.env` files, treat them as sensitive artifacts and protect at OS and backup level. + +## Rotation Basics + +Minimum baseline: + +1. `JWT_SECRET_KEY` + - Rotate on schedule (e.g., quarterly) and immediately after compromise. + - Expect existing sessions/tokens to become invalid after rotation. +2. `ENCRYPTION_KEY` + - Rotate with planned maintenance. + - Re-encrypt stored encrypted values (target passwords, SMTP password) during key transition. +3. `DB_PASSWORD` + - Rotate service account credentials regularly. + - Apply password changes in DB and deployment config atomically. +4. SMTP credentials + - Use dedicated sender account/app password. + - Rotate regularly and after provider-side security alerts. + +## Operational Checklist + +- [ ] No production secret in repository files. +- [ ] No production secret in container image metadata or build args. +- [ ] Runtime secret source documented for your environment. +- [ ] Secret rotation owner and schedule defined. +- [ ] Incident runbook includes emergency rotation steps. diff --git a/ops/.env.example b/ops/.env.example index 5963a29..1f8ec31 100644 --- a/ops/.env.example +++ b/ops/.env.example @@ -12,6 +12,7 @@ LOG_LEVEL=INFO # Core Database (internal metadata DB) # ------------------------------ # Database that stores users, targets, metrics, query stats, and audit logs. +# DEV default only. Use strong unique credentials in production. DB_NAME=nexapg DB_USER=nexapg DB_PASSWORD=nexapg @@ -23,7 +24,7 @@ DB_PORT=5433 # ------------------------------ # Host port mapped to backend container port 8000. BACKEND_PORT=8000 -# JWT signing secret. Change this in every non-local environment. +# JWT signing secret. Never hardcode in source. Rotate regularly. JWT_SECRET_KEY=change_this_super_secret JWT_ALGORITHM=HS256 # Access token lifetime in minutes. @@ -31,6 +32,7 @@ JWT_ACCESS_TOKEN_MINUTES=15 # Refresh token lifetime in minutes (10080 = 7 days). JWT_REFRESH_TOKEN_MINUTES=10080 # Key used to encrypt monitored target passwords at rest. +# Never hardcode in source. Rotate with re-encryption plan. # Generate with: # python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" ENCRYPTION_KEY=REPLACE_WITH_FERNET_KEY