Skip to main content

Secrets Rotation

The CLI rotates per-instance secrets in place, updates the tenant .env, restarts the application once, verifies the new values, and optionally saves them to a password manager. New values are never printed — only masked hints (ab****cd).

Commands​

flo secrets status <id> # list rotatable secrets + state
flo secrets rotate <id> dbPassword oidcCerts # rotate specific keys
flo secrets rotate <id> --all # rotate everything
flo secrets rotate <id> --all --skip emailPassword --save-to 1password
flo secrets rotate <id> --all --dry-run # preview, change nothing
OptionDescription
--allRotate every secret for the instance
--dry-runPreview what would be rotated without changes
--skip <keys>With --all: comma-separated keys to skip (e.g. emailPassword,auditHashSalt)
--save-to <manager>Password manager: 1password or keepassxc (auto-detected if omitted)
--vault <name>Target vault/database name
--confirmSkip interactive confirmations — except emailPassword, which always prompts

Valid keys: dbPassword, oidcCerts, auditHashSalt, emailPassword, strapiApiKey.

What rotates​

Rotators run in a fixed order: DB → OIDC → audit salt → email → Strapi.

KeyLabelAutoWhat it doesVerification
dbPasswordDatabase passwordyesALTER USER … PASSWORD inside the Postgres container; updates DB_CONNECTION_STRING in .envpg_isready + test query after restart
oidcCertsOIDC certificatesyesGenerates new signing + encryption passwords together (they share a lifecycle) and regenerates the OIDC certs; updates OIDC_*_PASSWORD/.well-known/openid-configuration returns 200
auditHashSaltAudit log hash saltyesGenerates a new salt and appends it to the chain; writes AUDIT_HASH_SALTS=old,new + AuditLog__HashSalt=newno-op (chain semantics)
emailPasswordEmail provider passwordnoRequires secure manual input; updates EMAIL_SENDER_EMAIL_PSWmanual
strapiApiKeyStrapi API keysyesFor every linked Strapi instance: creates new read-only + blog-editor tokens, updates STRAPI_API_KEY / BLOG_EDITOR_STRAPI_API_KEY and the tenant registry, deletes the old tokens after verificationGET /api with the new token returns 200

Notes:

  • auditHashSalt rotation does not invalidate old audit hashes: the previous salts stay in the chain and the backend tries each one, so historical correlation keeps working.
  • Rotating oidcCerts invalidates previously issued OIDC tokens.
  • emailPassword has no automation path — the rotator prompts even with --confirm, and skips itself when the tenant has no email provider.

Rotation flow​

  1. The tenant registry is snapshotted for rollback.
  2. Each rotator generates the new value and applies it to the infrastructure (PostgreSQL, OIDC certs, Strapi API).
  3. The .env file is regenerated and merged so custom variables are preserved.
  4. The application container is restarted once with docker compose up -d --force-recreate.
  5. Every rotator verifies its secret (default 36 retries × 5 s ≈ 3 minutes).
  6. On failure the orchestrator rolls back: registry snapshot, previous .env, and restart — plus a best-effort infrastructure rollback (for example, changing the DB password back).

Because of the restart, expect a short (~5–10 s) application downtime per rotation.

Password manager sync​

  • 1Password: uses op; items are created as logins with one concealed field per secret and rotation metadata in the notes.
  • KeePassXC: uses keepassxc-cli; the database path comes from secrets.keepassDbPath in ~/.flo/config.json (or is prompted), fields are stored as custom attributes.
  • Auto-detection checks 1Password first, then KeePassXC. If no manager is available (or configured as none), the command warns that the secrets exist only in the encrypted registry.

Config keys: secrets.passwordManager (1password | keepassxc | none), secrets.vault, secrets.keepassDbPath.

Encryption at rest​

Tenant secrets are encrypted with AES-256-GCM before they are written to the registry or .env:

  • Format: enc:<salt>:<iv>:<authTag>:<ciphertext> (base64 parts), 32-byte salt and IV, 16-byte auth tag.
  • Key derivation: scrypt from the operator's ~/.flo/master.key (created at first run with mode 0600; permissions are verified and repaired).
  • Field detection is name-based (password, secret, key, token, apikey, plus db_connection_string and email_sender_email_psw), shared with the config env show masker so the two never drift.
  • Backup archives use the same algorithm with a separate FLOBK1 file format (see Backups).

Safety and caveats​

  • Always start with --dry-run on a production tenant.
  • Rotation requires the instance to be resolvable and reachable; a failed verification rolls back, but infrastructure rollback is best-effort — check flo secrets status and flo instance health after a failure.
  • Do not rotate while a DR failover incident is active; re-mirror secrets to the standby afterwards with flo failover sync-secrets <id> (see Failover and Disaster Recovery).
  • Secret values are never logged; adapters receive plaintext only for the save operation and CLI state is cleared afterwards.
  • VPS login passwords and SSH keys are a separate surface: flo vps rotate-password [vpsName] (--harden to disable password auth, --key-path to reuse a key, --save-to/--vault to store the result).