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
| Secret | Used by |
|---|---|
| PostgreSQL connection string (includes password) | All Java services |
| Clerk secret key | causet-saas-cloud, causet-cloud-control-plane |
| S3 / MinIO access key + secret | All services that access S3 |
| Causet API keys | Application backends calling the runtime |
| Redis auth token | causet-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=30Rotation 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 valuesA 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:8085Warning: Never commit
.envfiles to version control. The repository’s.gitignoreshould include.env. Use.env.examplewith 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:notenvironment:) - 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:GetSecretValuescoped 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