Secrets & Network Policy
Security architecture for secret management, network isolation, and data protection across all phases of GospeLib's infrastructure.
Secret Management
Architecture
AWS Secrets Manager
└── gospelib/staging/
│ ├── database (PG URL, FalkorDB URL, Redis URL)
│ ├── auth (Clerk keys)
│ ├── billing (Stripe keys)
│ ├── ai (Anthropic, OpenAI keys)
│ ├── notifications (Resend, FCM, APNS keys)
│ └── search (Typesense API key)
└── gospelib/production/
├── database
├── auth
├── billing
├── ai
├── notifications
└── search
Phase Progression
| Phase | Strategy | Details |
|---|---|---|
| Phase 1 | AWS Secrets Manager | One secret per service per environment |
| Phase 2 | + External Secrets Operator | K8s CRDs auto-sync from AWS Secrets Manager → K8s Secrets |
| Phase 3 | + HashiCorp Vault | Dynamic DB credentials (lease-based), PKI for mTLS, audit trail |
Env Var Naming
All environment variables follow the convention: GOSPELIB_{SERVICE}_{KEY}
| Secret | Env Var | Service |
|---|---|---|
| FalkorDB URL | GOSPELIB_FALKORDB_URL | Content |
| PostgreSQL URL | GOSPELIB_PG_URL | Auth, Billing |
| Redis URL | GOSPELIB_REDIS_URL | All |
| Clerk secret key | CLERK_SECRET_KEY | Auth |
| Clerk webhook secret | CLERK_WEBHOOK_SECRET | Auth |
| Stripe secret key | STRIPE_SECRET_KEY | Billing |
| Stripe webhook secret | STRIPE_WEBHOOK_SECRET | Billing |
| Anthropic API key | ANTHROPIC_API_KEY | AI |
| OpenAI API key | OPENAI_API_KEY | AI |
| Resend API key | RESEND_API_KEY | Notifications |
Kubernetes Secret Sync (Phase 2+)
External Secrets Operator syncs AWS Secrets Manager into Kubernetes Secrets:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: gospelib-database
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: gospelib-secrets
creationPolicy: Owner
data:
- secretKey: pg-url
remoteRef:
key: gospelib/production/database
property: PG_URL
Secret Rotation
- Update the secret in AWS Secrets Manager
- External Secrets Operator picks up the change within
refreshInterval(1h default) - Kubernetes Secrets are updated automatically
- Restart affected deployments to pick up new values
No secrets in git, ever. The .env.example file documents all variables with placeholder values. .env.local (git-ignored) holds actual local development values.
Network Security
Network Topology
graph TB
Internet["Public Internet"]
ALB["ALB<br/>(TLS termination)"]
Ingress["Nginx Ingress"]
GW["Gateway"]
subgraph internal["Internal Only"]
Content["Content"]
Auth["Auth"]
Billing["Billing"]
AI["AI"]
Notif["Notifications"]
end
subgraph data["Data Tier (no public endpoint)"]
FDB["FalkorDB"]
PG["PostgreSQL"]
Redis["Redis"]
end
Internet --> ALB --> Ingress --> GW
GW --> Content & Auth & Billing & AI & Notif
Content & Auth & Billing & AI & Notif --> FDB & PG & Redis
Key Rules
- Gateway is the only public endpoint — all other services are internal-only
- Data stores have no public endpoints — only reachable from within the cluster
- TLS everywhere — TLS 1.3 for external traffic, internal HTTP within the cluster
- Phase 3 adds mTLS via service mesh (Istio or Linkerd) for inter-service encryption
CORS Policy
The gateway configures CORS to allow only known origins:
| Origin | Environment |
|---|---|
https://gospelib.com | Production |
https://admin.gospelib.com | Production |
https://staging.gospelib.com | Staging |
http://localhost:3000-3002 | Development |
Webhook Signature Verification
All incoming webhooks are verified before processing:
Stripe Webhooks
// Verify Stripe webhook signature
event, err := stripe.ConstructEvent(payload, sigHeader, cfg.WebhookSecret)
Clerk Webhooks
// Verify Clerk webhook signature
if !clerk.VerifySignature(r, cfg.ClerkWebhookSecret) {
// reject
}
Both handlers also check idempotency keys before processing to prevent duplicate event handling.
Data Security
| Measure | Details |
|---|---|
| Encryption at rest | AWS managed keys (Phase 1-2), CMK (Phase 3) |
| Encryption in transit | TLS 1.3 for all external traffic |
| User data ownership | Study data (highlights, notes) is private by default |
| GDPR compliance | User data export (GET /api/v1/users/me/export) and deletion |
| PII in logs | Automatically redacted by structured logging middleware |
GitHub Actions Secrets
Required secrets for CI/CD:
| Secret | Environment | Description |
|---|---|---|
AWS_ROLE_ARN | staging, production | IAM OIDC role for GitHub Actions |
ECR_URL | staging, production | ECR registry URL |
CLERK_SECRET_KEY | staging, production | Clerk API key (per env) |
STRIPE_SECRET_KEY | staging, production | Stripe API key (per env) |
ANTHROPIC_API_KEY | staging, production | Anthropic API key |
OPENAI_API_KEY | staging, production | OpenAI API key |
RESEND_API_KEY | staging, production | Resend API key |
EXPO_TOKEN | staging, production | Expo/EAS access token |
NX_CLOUD_ACCESS_TOKEN | all | Nx Cloud cache token |
GitHub Actions authenticates to AWS using OIDC (no long-lived credentials), configured during the AWS account bootstrap. See the Operations > Infrastructure page for details.
Related Pages
- Authentication Flow — How Clerk keys are used
- Entitlements — How Stripe keys drive plan gating
- CI/CD Pipeline — How secrets are used in deployment
- Scaling Strategy — Phase-specific infrastructure changes