veza/k8s/secrets
senke 2aea1af361 docs(J2): align docs with reality — rewrite CLAUDE.md, fix README, purge chat-server refs
Completes Day 2 of the v1.0.3 → v1.0.4 cleanup sprint. The documentation
now describes the actual repo layout instead of a fictional one.

CLAUDE.md — complete rewrite
  Old version referenced paths that don't exist and a protocol aimed at
  implementing v0.11.0 (current tag: v1.0.3). The agent was following a
  map for a city that had been rebuilt.
  - backend/        → veza-backend-api/
  - frontend/       → apps/web/
  - ORIGIN/ (root)  → veza-docs/ORIGIN/
  - veza-chat-server → merged into backend-api (v0.502, commit 279a10d31)
  - apps/desktop/   → never existed
  Also refreshed: stack versions (Go 1.25, Vite 5, React 18.2, Axum 0.8),
  commands, conventions, hook bypasses (SKIP_TYPES/SKIP_TESTS/SKIP_E2E),
  scope rules kept as immutable (no AI/ML, no Web3, no gamification, no
  dark patterns, no public popularity metrics).

README.md — targeted fixes
  - "Version cible: v0.101" → "Version courante: v1.0.4"
  - "Development Setup (v0.9.3)" → "Development Setup"
  - Removed Desktop (Electron) section — never implemented
  - Removed veza-chat-server from structure — merged into backend
  - Removed deprecated compose files section (nothing is DEPRECATED now)

k8s runbooks — remove stale chat-server references
  The disaster-recovery runbooks still scaled/restarted a deployment
  that no longer exists. In a real failover these commands would have
  failed silently and blocked the procedure. Files patched:
    - k8s/disaster-recovery/runbooks/cluster-failover.md
    - k8s/disaster-recovery/runbooks/data-restore.md
    - k8s/disaster-recovery/runbooks/database-failover.md
    - k8s/disaster-recovery/runbooks/rollback-procedure.md
    - k8s/network-policies/README.md
    - k8s/secrets/README.md
    - k8s/secrets.yaml.example
  Each reference is replaced by a short inline note pointing to v0.502
  (commit 279a10d31) so future readers understand the history.

.env.example — remove CHAT_JWT_SECRET
  Legacy env var for the deleted chat server. Replaced by an explanatory
  comment.

Not in this commit (user handles on Forgejo):
  - Closing the 5 open dependabot PRs on veza-chat-server/* branches
  - Deleting those 5 remote branches after the PRs are closed

Refs: AUDIT_REPORT.md §5.1, §7.1, §10 P1, §10 P4
2026-04-14 17:23:50 +02:00
..
external-secrets [INFRA-009] infra: Set up secrets management 2025-12-25 21:38:32 +01:00
secret-stores [INFRA-009] infra: Set up secrets management 2025-12-25 21:38:32 +01:00
external-secrets-operator.yaml [INFRA-009] infra: Set up secrets management 2025-12-25 21:38:32 +01:00
README.md docs(J2): align docs with reality — rewrite CLAUDE.md, fix README, purge chat-server refs 2026-04-14 17:23:50 +02:00
secrets-rotation.yaml release(v0.903): Vault - ORDER BY whitelist, rate limiter, VERSION sync, chat-server cleanup, Go 1.24 2026-02-27 09:43:25 +01:00
vault-integration.yaml [INFRA-009] infra: Set up secrets management 2025-12-25 21:38:32 +01:00

Secrets Management for Veza Platform

This directory contains configurations and documentation for managing secrets in the Veza platform using Kubernetes Secrets, External Secrets Operator, and optionally HashiCorp Vault.

Overview

Veza uses a multi-layered approach to secrets management:

  1. Kubernetes Secrets (Basic): For simple, static secrets
  2. External Secrets Operator (Recommended): For dynamic secrets from external providers
  3. HashiCorp Vault (Advanced): For enterprise-grade secrets management with rotation

Architecture

┌─────────────────────────────────────────────────────────┐
│                    External Providers                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌────────┐ │
│  │   Vault  │  │   AWS    │  │  GCP     │  │  Azure │ │
│  │          │  │ Secrets  │  │ Secrets  │  │ KeyVault│ │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬────┘ │
└───────┼─────────────┼─────────────┼─────────────┼──────┘
        │             │             │             │
        └─────────────┴─────────────┴─────────────┘
                      │
        ┌─────────────▼─────────────┐
        │  External Secrets Operator │
        └─────────────┬─────────────┘
                      │
        ┌─────────────▼─────────────┐
        │    Kubernetes Secrets      │
        └─────────────┬─────────────┘
                      │
        ┌─────────────▼─────────────┐
        │      Veza Pods            │
        │  (Backend, Frontend, etc.) │
        └───────────────────────────┘

Quick Start

Option 1: Basic Kubernetes Secrets (Development)

# Create secrets manually
kubectl create secret generic veza-secrets \
  --from-literal=database-url='postgresql://user:pass@host:5432/db' \
  --from-literal=jwt-secret='your-secret-key-min-32-chars' \
  --from-literal=redis-url='redis://host:6379' \
  -n veza-development

# Or from file
kubectl create secret generic veza-secrets \
  --from-env-file=secrets.env \
  -n veza-development
# 1. Install External Secrets Operator
kubectl apply -f k8s/secrets/external-secrets-operator.yaml

# 2. Configure secret store (e.g., Vault)
kubectl apply -f k8s/secrets/secret-stores/vault-store.yaml

# 3. Create ExternalSecret resources
kubectl apply -f k8s/secrets/external-secrets/veza-secrets.yaml

Secret Stores

HashiCorp Vault

Vault provides enterprise-grade secrets management with:

  • Automatic secret rotation
  • Audit logging
  • Fine-grained access control
  • Dynamic secrets

Setup:

# 1. Install Vault (if not already installed)
helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault hashicorp/vault -n vault-system --create-namespace

# 2. Configure Vault store for External Secrets
kubectl apply -f k8s/secrets/secret-stores/vault-store.yaml

# 3. Create secrets in Vault
vault kv put secret/veza/production \
  database-url="postgresql://..." \
  jwt-secret="..." \
  redis-url="redis://..."

# 4. Create ExternalSecret
kubectl apply -f k8s/secrets/external-secrets/veza-secrets.yaml

AWS Secrets Manager

# 1. Configure AWS store
kubectl apply -f k8s/secrets/secret-stores/aws-store.yaml

# 2. Create secrets in AWS Secrets Manager
aws secretsmanager create-secret \
  --name veza/production/database-url \
  --secret-string "postgresql://..."

# 3. Create ExternalSecret
kubectl apply -f k8s/secrets/external-secrets/veza-secrets-aws.yaml

Google Cloud Secret Manager

# 1. Configure GCP store
kubectl apply -f k8s/secrets/secret-stores/gcp-store.yaml

# 2. Create secrets in GCP
gcloud secrets create veza-database-url --data-file=- <<< "postgresql://..."

# 3. Create ExternalSecret
kubectl apply -f k8s/secrets/external-secrets/veza-secrets-gcp.yaml

Secret Structure

Required Secrets

All Veza services require the following secrets:

Secret Key Description Example
database-url PostgreSQL connection string postgresql://user:pass@host:5432/veza?sslmode=require
redis-url Redis connection string redis://host:6379/0
jwt-secret JWT signing secret (min 32 chars) your-super-secret-jwt-key-min-32-chars-long

Backend API Additional Secrets

Secret Key Description Example
stripe-api-key Stripe API key for payments sk_live_...
stripe-webhook-secret Stripe webhook signing secret whsec_...
smtp-password SMTP password for emails password
s3-access-key AWS S3 access key AKIA...
s3-secret-key AWS S3 secret key ...

Stream Server Additional Secrets

Secret Key Description Example
stream-server-secret Secret for stream server authentication stream-secret-key

Environment-Specific Secrets

Secrets are organized by environment:

secret/
├── veza/
│   ├── development/
│   │   ├── database-url
│   │   ├── jwt-secret
│   │   └── redis-url
│   ├── staging/
│   │   ├── database-url
│   │   ├── jwt-secret
│   │   └── redis-url
│   └── production/
│       ├── database-url
│       ├── jwt-secret
│       └── redis-url

Secret Rotation

Automatic Rotation with Vault

Vault can automatically rotate secrets:

# Enable automatic rotation for database credentials
vault write database/config/veza \
  plugin_name=postgresql-database-plugin \
  allowed_roles="veza-role" \
  connection_url="postgresql://{{username}}:{{password}}@postgres:5432/veza" \
  username="vault" \
  password="vault-password" \
  rotation_period="24h"

Manual Rotation

# 1. Update secret in source (Vault, AWS, etc.)
vault kv put secret/veza/production/jwt-secret value="new-secret-key"

# 2. External Secrets Operator will automatically sync
# Or force sync:
kubectl annotate externalsecret veza-secrets \
  force-sync=$(date +%s) \
  -n veza-production

# 3. Restart pods to pick up new secrets
kubectl rollout restart deployment/veza-backend-api -n veza-production

Rotation CronJob

A CronJob is provided to rotate secrets periodically:

kubectl apply -f k8s/secrets/secrets-rotation.yaml

Security Best Practices

  1. Never commit secrets to Git

    • Use .gitignore for secret files
    • Use secrets.yaml.example as template
  2. Use separate secrets per environment

    • Different secrets for dev, staging, production
    • Use namespaces to isolate environments
  3. Rotate secrets regularly

    • JWT secrets: Every 90 days
    • Database passwords: Every 180 days
    • API keys: As per provider recommendations
  4. Limit access with RBAC

    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
        name: secrets-reader
        namespace: veza-production
    rules:
        - apiGroups: [""]
          resources: ["secrets"]
          verbs: ["get", "list"]
    
  5. Enable audit logging

    • Log all secret access
    • Monitor for unauthorized access
  6. Use encryption at rest

    • Enable encryption for etcd (Kubernetes secrets)
    • Use encrypted volumes for Vault
  7. Principle of least privilege

    • Only grant access to secrets that are needed
    • Use service accounts with minimal permissions

Troubleshooting

Secret not syncing

# Check ExternalSecret status
kubectl describe externalsecret veza-secrets -n veza-production

# Check External Secrets Operator logs
kubectl logs -n external-secrets-system deployment/external-secrets

# Check SecretStore connection
kubectl describe secretstore vault-store -n veza-production

Secret not found in pod

# Verify secret exists
kubectl get secret veza-secrets -n veza-production

# Check pod environment variables
kubectl exec -it deployment/veza-backend-api -n veza-production -- env | grep -i secret

# Verify secret is mounted
kubectl describe pod -l app=veza-backend-api -n veza-production | grep -A 5 "Mounts:"

Permission denied

# Check RBAC permissions
kubectl auth can-i get secrets --namespace=veza-production

# Check service account
kubectl get serviceaccount -n veza-production
kubectl describe serviceaccount veza-backend-api -n veza-production

Migration Guide

From Kubernetes Secrets to External Secrets

  1. Install External Secrets Operator

    kubectl apply -f k8s/secrets/external-secrets-operator.yaml
    
  2. Configure secret store

    kubectl apply -f k8s/secrets/secret-stores/vault-store.yaml
    
  3. Create ExternalSecret resources

    kubectl apply -f k8s/secrets/external-secrets/veza-secrets.yaml
    
  4. Verify secrets are synced

    kubectl get externalsecret -n veza-production
    kubectl get secret veza-secrets -n veza-production
    
  5. Update deployments (if needed, External Secrets creates the same secret name)

  6. Remove old manual secrets (optional, after verification)

References