ChainLaunch

Pro Feature

Encryption at Rest

Master-key encryption at rest requires ChainLaunch Pro. Learn more about Pro features.

ChainLaunch Pro Feature

Master-key encryption at rest requires ChainLaunch Pro. Learn more about Pro features.

ChainLaunch Pro encrypts every secret stored in its database using a single master encryption key. This protects you from cold-storage attacks — if someone exfiltrates a copy of chainlaunch.db, they get nothing useful without the master key. This page explains how the system works, what's encrypted, how to manage the key safely, and how to rotate it.

What gets encrypted

The encryption service (pkg/encryption) is used to seal every long-lived secret before it touches SQLite. Specifically:

Data Where it lives Why it's encrypted
SAML SP private keys sso_providers.saml_sp_key_encrypted Used to sign AuthnRequests; theft = forge ChainLaunch logins
OIDC client secrets sso_providers.oidc_client_secret_encrypted Allows impersonation of the ChainLaunch app at the IdP
OIDC refresh tokens sso_user_tokens.refresh_token_encrypted Steal a refresh token, impersonate the user
HashiCorp Vault credentials vault_configs.token_encrypted Theft grants access to all keys held in Vault
AWS KMS credentials aws_kms_configs.*_encrypted Static creds, role-assumption sessions, profile names
SCIM bearer tokens scim_tokens.token_hash (hashed, plus encrypted secondary index) IdP-pushed user/group changes
Webhook secrets webhooks.secret_encrypted HMAC-signing keys for outbound calls
Backup target credentials backup_targets.config (JSON-encrypted blob) S3 keys, Restic repo passwords, EBS auth contexts
Notification provider secrets notification_providers.config Email passwords, Slack webhook URLs, etc.

Not encrypted (intentional):

  • Network and node configurations — these are public per the consortium and need to be queryable.
  • Audit log entries — must be tamper-evident and human-readable.
  • User profile fields (username, email, role) — required for joins and search.
  • Public-key material (certs, MSPs, validator addresses).

How it works

The encryption is AES-256-GCM with a per-message random nonce, base64-encoded for storage. From the service's perspective:

ciphertext, err := encryption.Encrypt("secret")
plaintext,  err := encryption.Decrypt(ciphertext)

GCM gives you:

  • Confidentiality (AES-256).
  • Integrity (any tampering with the ciphertext is detected on decrypt).
  • Random nonces (the same plaintext encrypts to a different ciphertext every time, preventing rainbow-table attacks).

There's exactly one master key per ChainLaunch instance. ChainLaunch never derives sub-keys, never stores the master key on disk, and never logs it.

Setting the master key

The master key is supplied via the KEY_ENCRYPTION_KEY environment variable on every ChainLaunch process (serve, encrypt, decrypt, crypto).

Format

ChainLaunch accepts the key in three forms:

  1. Base64-encoded 32-byte key (recommended for production):
    openssl rand -base64 32
    # → "ji5G6w4P7y3KqXr8N2QzL5M0V/uWp9Y7B+T1c4D6E0Q="
    export KEY_ENCRYPTION_KEY="ji5G6w4P7y3KqXr8N2QzL5M0V/uWp9Y7B+T1c4D6E0Q="
  2. Arbitrary string — ChainLaunch SHA-256 hashes it down to 32 bytes:
    export KEY_ENCRYPTION_KEY="my-very-long-passphrase-32+chars-please"
  3. Unset — ChainLaunch refuses to start. The empty-key case is treated as a fatal misconfiguration, not a silent fallback.

Form (1) is preferred because it gives you the full 256 bits of entropy. Form (2) is only as strong as the passphrase you pick.

Setting it safely

Environment How to inject the key
systemd EnvironmentFile=/etc/chainlaunch/secrets.env (chmod 600, owned by chainlaunch user)
Docker --env-file secrets.env or Docker secrets (/run/secrets/chainlaunch_master_key)
Kubernetes envFrom: secretRef: name: chainlaunch-master-key, with a sealed-secret or external-secrets operator
AWS Pull from Secrets Manager at startup, never bake into the AMI
Hetzner / bare VM Cloud-init pulls from your secret store on first boot, writes to /etc/chainlaunch/env, chmod 600

Never:

  • Hard-code the key in docker-compose.yml, chart values, or git.
  • Echo it into logs (ChainLaunch redacts KEY_ENCRYPTION_KEY in its own log output, but your wrappers might not).
  • Use a value derived only from the hostname — any attacker who reads /etc/hostname recovers your key.
  • Reuse the master key across environments. Dev, staging, and prod must each have their own.

Manual encrypt/decrypt CLI

Pro ships three CLI helpers for ops work — useful when migrating credentials in/out of ChainLaunch:

# Encrypt a value (reads KEY_ENCRYPTION_KEY env or -k flag)
chainlaunch encrypt --plaintext "vault-token-xxxx"
# → "AAA...base64..."
 
# Decrypt
chainlaunch decrypt --ciphertext "AAA...base64..."
# → "vault-token-xxxx"
 
# Generate a new master key
chainlaunch crypto generate-key
# → "ji5G6w4P7y3KqXr8N2QzL5M0V/uWp9Y7B+T1c4D6E0Q="

These never hit the database — they're pure stdin/stdout. Safe to use in pipelines.

Key rotation

Rotating the master key is not a KEY_ENCRYPTION_KEY=newvalue restart. Every encrypted column on disk was sealed under the old key; flipping the env without re-sealing yields decryption errors at the first read.

The supported rotation procedure:

  1. Take a database backup. This is non-negotiable. See Backups.
  2. Stop ChainLaunch.
  3. Export and re-encrypt every secret with the migration helper:
    chainlaunch crypto rotate-master-key \
      --old-key "$OLD_KEY" \
      --new-key "$NEW_KEY" \
      --db /var/lib/chainlaunch/chainlaunch.db
    The helper iterates every encrypted column, decrypts with the old key, re-encrypts with the new key, and writes it back in a single transaction. Crash-safe.
  4. Update KEY_ENCRYPTION_KEY in your environment to the new value.
  5. Start ChainLaunch. Verify by hitting any endpoint that touches a secret (e.g. GET /api/v1/sso/providers).
  6. Securely destroy the old key.

For most teams, rotate annually or immediately on suspected compromise. The key never expires automatically.

What an attacker can and can't do

With only a copy of chainlaunch.db

  • ❌ Read SAML/OIDC client secrets, vault tokens, KMS creds.
  • ✅ See network topology, user emails, audit log content.

For most threat models, the network topology and audit log are also sensitive. Encryption at rest doesn't replace database file permissions, full-disk encryption, and access controls — it adds a layer.

With the database and the master key

  • ✅ Game over. The attacker now has every secret.

This is why the master key must live in a different security domain than the database file. Use a secrets manager, KMS, or sealed-secret system that requires its own credential to retrieve.

With access to the running process

  • ✅ The decrypted master key is in process memory and reachable.

Mitigate by running ChainLaunch as a non-root user, isolating with systemd unit hardening (ProtectHome=true, PrivateTmp=true, NoNewPrivileges=true), and limiting SSH/console access.

Disaster recovery

If you lose the master key, every encrypted column is unrecoverable. There is no backdoor. Concrete consequences:

  • All SSO providers must be reconfigured from scratch (re-paste secrets/upload IdP metadata).
  • Vault and AWS KMS integrations must be re-bound (existing key references survive — only the credentials are gone).
  • SCIM tokens must be reissued; IdPs need the new tokens.
  • Backup targets must be re-credentialed.

Network and node data is not lost — ChainLaunch can keep operating networks while the integrations are restored.

Mitigation: treat the master key like the root password — store at least two copies in independent secret managers (e.g. AWS Secrets Manager + 1Password Vault), document the recovery flow, run a recovery drill annually.

Verification

# Confirm the key is set
test -n "$KEY_ENCRYPTION_KEY" && echo "Key is set (length: ${#KEY_ENCRYPTION_KEY})"
 
# Smoke test: encrypt+decrypt round-trip
echo "$(chainlaunch encrypt --plaintext hello | chainlaunch decrypt --stdin)"
# → "hello"

Limits & gotchas

  • No HSM-backed master key (yet). The master key is a symmetric secret in the host's environment. KMS-wrapping the master key is on the roadmap; if you need it today, wrap secrets externally and feed pre-encrypted values via the chainlaunch encrypt CLI.
  • KEY_ENCRYPTION_KEY cannot be hot-rotated. Restart is required after rotation.
  • Backup files are not separately encrypted by ChainLaunch — encrypt your backup target (S3 SSE, EBS encryption, restic encryption). See Backups.
  • Per-tenant keys are not currently supported — one key per ChainLaunch instance. Run separate instances if you need cryptographic separation between tenants.

Next steps