OWASP / OWASP/Nest

Migrate runtime secrets to AWS Secrets Manager

Open
#5,071 5 comments 2 reactions 1 assignee Claimed by @Mr-Rahul-Paul View on GitHub
Dominant language
Python
Stars
451
Forks
707
Avg merge
22h 59m
Merged PRs (30d)
91

Description

## Summary

OWASP Nest stores most runtime credentials in **SSM Parameter Store** and injects them into ECS at container startup. Some values are duplicated (e.g. DB password in both Secrets Manager and SSM), and runtime secrets may be synced from **GitHub Secrets during deploy**. This makes automated rotation difficult and creates multiple sources of truth.

We should migrate to **AWS Secrets Manager as the canonical store for runtime secrets**, keep **SSM for non-secret configuration**, preserve **local dev via `.env` files**, and lay the groundwork for automated rotation in a **follow-on effort**.

## Background

### Current state

- **Runtime secrets** live in SSM `SecureString` parameters (`infrastructure/modules/parameters/main.tf`), injected into ECS via `valueFrom` ARNs (`infrastructure/modules/service/main.tf`).
- **Non-secret config** (hosts, URLs, release version) also lives in SSM.
- **DB credentials** exist in both Secrets Manager (for RDS Proxy) and SSM (`DJANGO_DB_PASSWORD` for Django) — duplicated source of truth.
- Terraform generates one-time values for `DJANGO_SECRET_KEY`, `NEXTAUTH_SECRET`, DB password, and Redis auth token; third-party secrets use `to-be-set-in-aws-console` with `ignore_changes = [value]`.
- **ECS execution role** grants SSM read only — no Secrets Manager access yet.
- **No automated rotation** is configured (`aws_secretsmanager_secret_rotation` not present; KMS key rotation is already enabled).
- **Local dev** uses `backend/.env` and `frontend/.env` via Docker Compose — no AWS dependency today.
- **GitHub Secrets** are used for CI/CD (AWS creds, build-time `VITE_*` / `NEXT_PUBLIC_*`) and may also feed runtime secrets into SSM at deploy time (operational pattern to retire).

### Problems

1. No single source of truth for runtime secrets (SSM vs Secrets Manager vs GitHub).
2. Secrets injected only at ECS task launch — updating a store does not update running containers.
3. Automated rotation is not viable until Secrets Manager is the canonical store and duplicates are removed.
4. GitHub → AWS sync on deploy couples release pipeline to secret lifecycle.

---

## Goals (this issue)

- **Secrets Manager** is the single source of truth for all runtime credentials in staging and production.
- **SSM Parameter Store (`String`)** remains the source of truth for non-secret configuration only.
- **GitHub Secrets** are limited to CI/CD identity and build-time variables — not runtime app secrets.
- **Local development** continues to work with `.env` files and no AWS account requirement for basic setup.
- Eliminate duplicate secret storage (especially DB password).
- Infrastructure is **rotation-ready** (correct stores, IAM, ECS injection, no GitHub/SSM duplication) so automated rotation can be added next.

---

## Follow-on: automated secret rotation (next step — keep in mind)

This migration is a **prerequisite**, not the rotation work itself. Once Secrets Manager is in place, a **separate issue** should cover automated rotation, including:

| Secret | Rotation approach (follow-on) |
|--------|----------------------------|
| **RDS password** | Secrets Manager managed rotation |
| **Redis auth token** | Custom rotation Lambda + ECS redeploy |
| **`DJANGO_SECRET_KEY` / `NEXTAUTH_SECRET`** | Scheduled rotation + ECS redeploy (accept session invalidation in v1) |
| **Third-party keys** (OpenAI, Algolia, Slack, GitHub) | Custom Lambdas and/or vendor APIs + redeploy |
| **Slack tokens** | Enable `token_rotation_enabled` in app manifest + webhook handling |
| **GitHub App private key** | Multi-key overlap support during rotation |

**Rotation orchestration (follow-on):**

- EventBridge on `RotationSucceeded` → Lambda → `ecs:UpdateService` with `forceNewDeployment=true`
- CloudWatch alarms on rotation failure
- Runbooks for secrets that cannot be fully automated

Design and implement this migration so rotation can be added **without another store migration or env var rename**.

---

## Non-goals (this issue)

- Implementing automated rotation schedules/Lambdas (follow-on issue).
- Runtime secret fetching inside Django/Next.js (startup injection is sufficient for v1).
- Automated rotation of user-issued Nest API keys.
- Full CI/CD migration to GitHub OIDC.

---

## Proposed architecture

```
┌─────────────────────────────────────────────────────────────┐
│ Local dev │ Staging / Production │
├────────────────────┼────────────────────────────────────────┤
│ backend/.env │ AWS Secrets Manager (runtime secrets) │
│ frontend/.env │ ↓ │
│ │ ECS task definition (valueFrom) │
│ │ ↓ │
│ │ env vars → django-configurations / │
│ │ Next.js │
│ │ │
│ │ SSM String params (non-secret config) │
└────────────────────┴────────────────────────────────────────┘

GitHub Secrets → CI/CD + build-time vars only
PostgreSQL → user Nest API keys (unchanged)
KMS → encryption (already enabled)

Follow-on: RotationSucceeded → redeploy ECS services
```

### Secret inventory (migrate to Secrets Manager)

| Secret | Notes |
|--------|-------|
| `DJANGO_SECRET_KEY` | Rotation deferred to follow-on issue |
| `NEXTAUTH_SECRET` | Rotation deferred to follow-on issue |
| DB credentials | Consolidate — remove SSM duplicate; use existing SM secret |
| Redis auth token | Rotation deferred to follow-on issue |
| `DJANGO_OPEN_AI_SECRET_KEY` | Rotation deferred to follow-on issue |
| `DJANGO_ALGOLIA_WRITE_API_KEY` | Rotation deferred to follow-on issue |
| `DJANGO_SLACK_*`, `SLACK_BOT_TOKEN_*` | Rotation deferred to follow-on issue |
| `GITHUB_TOKEN`, `NEST_GITHUB_APP_PRIVATE_KEY` | Rotation deferred to follow-on issue |
| `NEXT_SERVER_GITHUB_CLIENT_SECRET` | Rotation deferred to follow-on issue |
| `DJANGO_SENTRY_DSN` | Low priority |

### Keep in SSM as `String`

`DJANGO_DB_HOST`, `DJANGO_DB_NAME`, `DJANGO_DB_PORT`, `DJANGO_DB_USER`, `DJANGO_ALLOWED_HOSTS`, `DJANGO_ALLOWED_ORIGINS`, `DJANGO_REDIS_HOST`, `DJANGO_REDIS_USE_TLS`, `DJANGO_RELEASE_VERSION`, `DJANGO_SETTINGS_MODULE`, `DJANGO_CONFIGURATION`, `NEXTAUTH_URL`, `NEXT_SERVER_*` URLs, GitHub OAuth client ID, GitHub App ID, etc.

## Local dev compatibility (must not regress)

- Continue using `backend/.env` and `frontend/.env` via Docker Compose.
- Do **not** add Secrets Manager fetching to `settings/base.py` or `settings/local.py`.
- Test env files remain file-based.
- Same env var names across local and AWS — only the provider differs.

---

## Acceptance criteria

- [ ] All runtime secrets in staging and production live in Secrets Manager only (no SSM SecureString duplicates, no GitHub Secrets used for runtime).
- [ ] ECS backend, frontend, and tasks inject secrets from Secrets Manager.
- [ ] DB password has a single source of truth in Secrets Manager (no SSM mirror).
- [ ] `docker compose -f docker-compose/local/compose.yaml up` works with only `.env` files.
- [ ] `backend/.env.example` remains the local dev schema.
- [ ] Follow-on issue filed for automated rotation with clear scope and dependency on this work.

---

## Risks & mitigations

| Risk | Mitigation |
|------|------------|
| Doing rotation and migration in one PR | Split: this issue = store migration; follow-on = rotation |
| Terraform state holding secret values | `ignore_changes` on values; bootstrap outside apply |
| Migration downtime | Validate in staging first; ECS rolling deploy |
| Secrets Manager cost | ~$0.40/secret/month; acceptable for security posture |

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.