Secrets Management
Qarion supports pluggable secrets backends for secure storage of application secrets. By default, secrets are read from environment variables (.env file), but production deployments can use HashiCorp Vault, AWS Secrets Manager, or GCP Secret Manager.
Quick Start
Set the SECRETS_BACKEND environment variable to select a backend:
# Default — reads from environment variables
SECRETS_BACKEND=env
# HashiCorp Vault KV v2
SECRETS_BACKEND=vault
# AWS Secrets Manager
SECRETS_BACKEND=aws_sm
# Google Cloud Secret Manager
SECRETS_BACKEND=gcp_sm
Backend Configuration
Environment Variables (default)
No additional configuration needed. All secrets are read from os.environ:
SECRETS_BACKEND=env
SECRET_KEY=your-jwt-signing-key
POSTGRES_PASSWORD=your-db-password
INSTANCE_ENCRYPTION_KEY=your-fernet-key
HashiCorp Vault
Reads from a Vault KV v2 engine. Requires the hvac package:
pip install hvac
SECRETS_BACKEND=vault
VAULT_ADDR=https://vault.example.com
VAULT_TOKEN=hvs.your-vault-token
VAULT_SECRET_PATH=secret/data/qarion
SECRETS_CACHE_TTL=300 # Cache TTL in seconds (default: 300)
The secret at VAULT_SECRET_PATH should be a JSON object:
{
"SECRET_KEY": "your-jwt-key",
"POSTGRES_PASSWORD": "db-password",
"INSTANCE_ENCRYPTION_KEY": "fernet-key"
}
AWS Secrets Manager
Reads from an AWS Secrets Manager secret. Requires boto3:
pip install boto3
SECRETS_BACKEND=aws_sm
AWS_SECRET_ARN=arn:aws:secretsmanager:eu-west-1:123456789:secret:qarion-prod
AWS_REGION=eu-west-1 # optional, falls back to boto3 defaults
SECRETS_CACHE_TTL=300
The secret should contain a JSON string with key-value pairs.
Google Cloud Secret Manager
Reads from a GCP Secret Manager secret. Requires google-cloud-secret-manager:
pip install google-cloud-secret-manager
SECRETS_BACKEND=gcp_sm
GCP_SECRET_PROJECT=my-gcp-project
GCP_SECRET_NAME=qarion-secrets # default
GCP_SECRET_VERSION=latest # default
SECRETS_CACHE_TTL=300
Managed Secret Fields
The following settings are resolved from the secrets backend:
| Field | Purpose |
|---|---|
SECRET_KEY | JWT signing key |
SECRET_KEY_PREVIOUS | Previous JWT key (dual-key rotation) |
POSTGRES_PASSWORD | Database password |
INSTANCE_ENCRYPTION_KEY | Fernet key for field-level encryption |
CONTROL_PLANE_SECRET_KEY | Multi-instance control plane key |
SENDGRID_API_KEY | SendGrid email API key |
SCIM_BEARER_TOKEN | SCIM provisioning token |
SMTP_PASSWORD | SMTP email password |
STORAGE_S3_SECRET_KEY | S3 storage secret key |
STORAGE_GCS_CREDENTIALS_JSON | GCS credentials |
STORAGE_AZURE_CONNECTION_STRING | Azure storage connection string |
Key Rotation
JWT Signing Key
Qarion supports dual-key JWT rotation for zero-downtime key changes:
- Set
SECRET_KEY_PREVIOUSto the currentSECRET_KEYvalue. - Update
SECRET_KEYto the new key. - Deploy — both old and new tokens will be accepted.
- After the token TTL expires (default 30 min), remove
SECRET_KEY_PREVIOUS.
Encryption Key Rotation
To rotate INSTANCE_ENCRYPTION_KEY, use the built-in migration utility:
import asyncio
from app.core.secrets.key_rotation import rotate_encryption_key
asyncio.run(rotate_encryption_key(
old_key="<current-key>",
new_key="<new-key>",
))
This re-encrypts all Fernet-encrypted data (instance DB URLs and export config credentials) in a single database transaction.
Log Scrubbing
All secret values are automatically scrubbed from application logs. Any log event containing a known secret value will have it replaced with ***.
Architecture
┌──────────────────────┐
│ SecretProvider │ ← Strategy pattern
│ (abstract base) │
└──────────┬───────────┘
│
┌──────┼──────┬─────────────┐
▼ ▼ ▼ ▼
.env Vault AWS SM GCP SM
The SECRETS_BACKEND setting selects which provider is used. Only one backend is active at a time. The selected provider's SDK is loaded lazily, so unused providers don't add import overhead.