Secrets Management

Causet services require several credentials to operate: database passwords, Clerk secret keys, and API keys. This page describes how to manage them securely.


What qualifies as a secret

SecretUsed by
PostgreSQL connection string (includes password)All Java services
Clerk secret keycauset-saas-cloud, causet-cloud-control-plane
S3 / MinIO access key + secretAll services that access S3
Causet API keysApplication backends calling the runtime
Redis auth tokencauset-query-service (if Redis AUTH is configured)

Production: AWS Secrets Manager

Store all production credentials in AWS Secrets Manager. Do not put plaintext credentials in ECS task definitions, Docker images, or version control.

Storing a secret

# Store a database URL
aws secretsmanager create-secret \
  --name "causet/prod/runtime-db-url" \
  --secret-string "r2dbc:postgresql://user:password@rds-endpoint:5432/causet"
 
# Store Clerk secret key
aws secretsmanager create-secret \
  --name "causet/prod/clerk-secret-key" \
  --secret-string "sk_live_..."

Referencing secrets in ECS task definitions

"secrets": [
  {
    "name": "DATABASE_URL",
    "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789:secret:causet/prod/runtime-db-url"
  },
  {
    "name": "CLERK_SECRET_KEY",
    "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789:secret:causet/prod/clerk-secret-key"
  }
]

ECS fetches the secret value from Secrets Manager at task startup and injects it as an environment variable. The secret value is never stored in the task definition itself.

Required IAM permissions for ECS tasks

Each ECS task role needs permission to read its secrets:

{
  "Effect": "Allow",
  "Action": ["secretsmanager:GetSecretValue"],
  "Resource": "arn:aws:secretsmanager:us-east-1:123456789:secret:causet/prod/*"
}

Scope the Resource to only the secrets this specific service needs, not causet/* broadly.

Automatic rotation

For RDS credentials, use Secrets Manager’s built-in rotation lambda:

aws secretsmanager rotate-secret \
  --secret-id "causet/prod/runtime-db-url" \
  --rotation-lambda-arn arn:aws:lambda:us-east-1:123456789:function:SecretsManagerRDSPostgreSQLRotationSingleUser \
  --rotation-rules AutomaticallyAfterDays=30

Rotation updates the secret value and restarts the ECS service so new containers pick up the rotated credentials.


Local development: .env files

For local development, use .env files. The Docker Compose stack reads from .env in the repository root.

Create .env from the provided template:

cp .env.example .env
# Edit .env with your local values

A minimal .env for local development:

# PostgreSQL
DATABASE_URL=r2dbc:postgresql://postgres:5432/causet
PROJECTIONS_DB_URL=jdbc:postgresql://postgres:5432/projections
CONTROL_PLANE_DB_URL=jdbc:postgresql://postgres:5432/causet_control_plane
 
# Kafka
KAFKA_BOOTSTRAP_SERVERS=redpanda:9092
 
# MinIO (local S3)
S3_ENDPOINT=http://minio:9000
S3_BUCKET=causet-artifacts
AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=minioadmin
 
# Clerk (use a development instance)
CLERK_SECRET_KEY=sk_test_...
CLERK_PUBLISHABLE_KEY=pk_test_...
 
# Internal service URLs
CAUSET_SAAS_URL=http://causet-saas-cloud:8085

Warning: Never commit .env files to version control. The repository’s .gitignore should include .env. Use .env.example with placeholder values as the committed template.


Secrets in IR artifacts and DSL files

Do not put secrets in DSL files or IR artifacts. IR artifacts are stored in S3 and fetched by services at runtime — they are not a secrets store.

If you need to reference a runtime value in your DSL (e.g., an external API endpoint), use an environment variable in the service and pass it through configuration, not through the DSL.


Secret hygiene checklist

  • No credentials in version control (check .gitignore, audit commit history)
  • No credentials in Docker image layers (check docker history)
  • No credentials in ECS task definition environment variables (use secrets: not environment:)
  • No credentials in IR artifacts (causet.runtime.json, causet.projections.json)
  • Secrets are rotated at least annually (or immediately on suspected compromise)
  • Each service has a separate DB user with least-privilege grants
  • Production and staging use separate credentials
  • IAM task roles have secretsmanager:GetSecretValue scoped to their specific secrets

Database user permissions

Create separate PostgreSQL users per service with minimum required permissions:

-- causet-runtime-service: reads/writes ledger_events and entity state
CREATE USER causet_runtime WITH PASSWORD '...';
GRANT SELECT, INSERT ON ALL TABLES IN SCHEMA causet TO causet_runtime;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA causet TO causet_runtime;
 
-- causet-projection-worker: writes to projection tables + creates schemas
CREATE USER causet_worker WITH PASSWORD '...';
GRANT CREATE ON DATABASE projections TO causet_worker;
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO causet_worker;
 
-- causet-query-service: reads projection tables only
CREATE USER causet_query WITH PASSWORD '...';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO causet_query;
-- Grant SELECT on specific tenant schemas as they are created