Secrets

From canasta

This page describes how Canasta tracks secret state β€” database passwords, the MediaWiki secret key, backup credentials, and similar β€” for both orchestrators, and what your recovery pathways look like if a host or cluster is destroyed. Operators evaluating production deployments should read this alongside Help:Backup and restore (which covers what gets captured in Restic snapshots) and the orchestrator-specific pages.

What counts as secret state

Every Canasta instance holds a small set of secret values:

  • Database credentials β€” the database administrator account (MYSQL_USER / MYSQL_PASSWORD: root on the bundled database, the operator's account on an external one) and the account MediaWiki connects with (WIKI_DB_USER / WIKI_DB_PASSWORD; see Database accounts). On Kubernetes, the <id>-db-credentials Secret also holds the bundled database's root password as MYSQL_ROOT_PASSWORD.
  • MW_SECRET_KEY β€” MediaWiki's $wgSecretKey. Used for HMACs, session tokens, password-reset tokens, and CSRF tokens. Rotating it invalidates all of those across the wiki β€” treat it as a one-shot value.
  • Backup credentials (when configured) β€” RESTIC_PASSWORD and the cloud-provider keys for the chosen backend (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY for S3, AZURE_* for Azure Blob, etc.).
  • SMTP credentials (when email is configured).

These values originate in the envfile the operator passes to canasta create (or are generated by Canasta if not supplied). Where they live after creation is the part that differs by orchestrator.

Which keys are treated as secret

Canasta decides whether a .env key holds a secret from its name, using one rule everywhere. A key is secret when any of the following is true:

  • Its name contains PASSWORD, SECRET, TOKEN, KEY, or CREDENTIAL.
  • Its name contains PASS, PASSWD, PWD, DSN, or PRIVATE as a whole underscore-separated word β€” for example LDAP_BIND_PWD, SENTRY_DSN, or DB_PASS, but not BYPASS_CACHE. (Canasta CLI 4.21.0 and later.)
  • Its name starts with a credential prefix: AWS_, AZURE_, B2_, GOOGLE_, OS_, ST_, RCLONE_, or SMTP_.
  • It is RESTIC_REPOSITORY, since a repository URL can embed credentials.

A secret key's value is masked in the full canasta config get listing (name the key, or pass --show-secrets, to print it), is extracted into encrypted variables on a gitops-managed Compose instance, and on Kubernetes is left out of the copy of .env placed in the instance's ConfigMap.

Database accounts

As of Canasta CLI 4.22.0, a Docker Compose instance keeps two database accounts in .env:

Keys Account Used by
MYSQL_USER / MYSQL_PASSWORD The database administrator: root on the bundled database. The CLI and the db container, for backups, restores, export / import, and creating and dropping wiki databases.
WIKI_DB_USER / WIKI_DB_PASSWORD MediaWiki's own account, mediawiki by default. It has full access to the instance's wiki databases (each wiki's own database plus its declared extra databases) and nothing else. The web container, which receives them as MYSQL_USER / MYSQL_PASSWORD. It never sees the administrator's password.

The CLI creates the mediawiki account in the bundled database and keeps it in step with .env and the instance's wikis. It does this at every start, and whenever a wiki or a declared extra database is added or removed. So a restore onto a new host, or a rebuilt database volume, gets the account back at the next start. A full restore keeps this host's MYSQL_PASSWORD, WIKI_DB_USER and WIKI_DB_PASSWORD rather than the snapshot's, because both accounts live in this host's database server.

Some instances still have MediaWiki on the administrator account: those created before 4.22.0 that an upgrade has not yet moved, and those opted out with CANASTA_DB_ROOT_ACCOUNT=true. On those, WIKI_DB_USER names the administrator account, and WIKI_DB_PASSWORD is a copy of MYSQL_PASSWORD that is refreshed at every start. See Upgrading for how existing instances are moved.

On an external database, WIKI_DB_USER and WIKI_DB_PASSWORD default to the operator's account, and the CLI does not create accounts there. Kubernetes instances still run MediaWiki as the administrator account.

Source of truth shifts at create time

For both orchestrators, the operator's envfile is the input β€” but after canasta create finishes, the runtime view (what the wiki actually authenticates with) lives in different places:

Orchestrator Runtime view On-disk record
Docker Compose The instance directory's .env file (typically ~/canasta/<id>/.env, mode 0600). The bundled db container reads it at startup; the wiki reads it via PHP's getenv(). There is no indirection β€” the .env on disk IS what the wiki uses. (same file)
Kubernetes In-cluster K8s Secret resources: <id>-db-credentials (with MYSQL_PASSWORD and, for bundled DB, MYSQL_ROOT_PASSWORD) and <id>-mw-secrets (with MW_SECRET_KEY). The Helm chart's web and jobrunner pods reference them via secretKeyRef. ~/canasta/<id>/.env on the operator workstation. After create, this is a record/snapshot β€” pods don't read from it.

This divergence is the reason the two orchestrators behave differently when you try to rotate a secret.

Updating secrets via canasta config set

Compose

On Compose, canasta config set updates .env and restarts the instance, which recreates the containers so they re-read the env on startup.

Bundled database. As of Canasta CLI 4.21.0, canasta config set MYSQL_PASSWORD=<new> rotates the password end to end: it first changes the password of the database's root account inside the running bundled database, then writes the new value to .env (and, on a gitops-managed instance, to the host's vars.yaml) and restarts the instance so the wiki connects with it. No --force is needed, and you do not run ALTER USER yourself.

canasta config set -i <id> MYSQL_PASSWORD=<new-value>
  • The instance must be running: if the password cannot be changed inside the database, the command fails and MYSQL_PASSWORD is left unchanged.
  • The new value cannot be empty, and --no-restart is refused for this key, because the wiki must restart to use the new password.
  • If a later step fails after the database password has been changed, the instance is still restarted onto the new password before the error is reported.

External database. Rotation is operator-owned at the DB server (RDS, Aurora, etc.), since setting MYSQL_PASSWORD does not change it there. Change the password on the server first, then record it with --force; without --force, canasta config set refuses the key on an instance that uses an external database:

canasta config set -i <id> --force MYSQL_PASSWORD=<new-value>

MediaWiki's account. On an instance where MediaWiki has its own account, rotating MYSQL_PASSWORD does not affect MediaWiki's connection. To rotate MediaWiki's password, set WIKI_DB_PASSWORD. The restart that follows applies the new password to the account in the database:

canasta config set -i <id> WIKI_DB_PASSWORD=<new-value>

While MediaWiki still uses the administrator account, set MYSQL_PASSWORD instead; WIKI_DB_PASSWORD follows it.

Kubernetes

canasta config set refuses to change MYSQL_PASSWORD, WIKI_DB_PASSWORD, MYSQL_ROOT_PASSWORD, or MW_SECRET_KEY on a K8s instance. Updating .env alone wouldn't change what the running wiki authenticates with β€” the pods read from K8s Secrets, not from .env β€” so the command fails fast with the kubectl-based rotation recipe instead of silently no-op'ing.

To rotate one of these credentials on a K8s instance:

  1. Update the value at its source (e.g., the database server for MYSQL_PASSWORD; generate a fresh random value for MW_SECRET_KEY β€” and accept that all sessions and password-reset tokens will be invalidated).
  2. Update the K8s Secret in the cluster:
    kubectl create secret generic <id>-db-credentials \
      --from-literal=MYSQL_PASSWORD='<new-value>' \
      --dry-run=client -o yaml | kubectl apply -f -
    
    For MW_SECRET_KEY, target <id>-mw-secrets. To rotate multiple keys atomically, list them all in the same --from-literal call.
  3. Restart the wiki pods so they pick up the new value:
    canasta restart -i <id>
    

Other secret-bearing keys (backup credentials like RESTIC_PASSWORD, cloud-credential keys, SMTP_PASSWORD) flow through a different K8s Secret β€” canasta-<id>-backup-env β€” which Canasta rebuilds from .env on every canasta start / restart. Those keys are mutable via canasta config set followed by canasta restart on K8s.

Recovery pathways shipped today

Compose

Pathway Captures Restored by
Restic snapshot's .env The full instance .env (DB creds, MW_SECRET_KEY, RESTIC_*, AWS_*, ...) canasta backup restore
GitOps repo's hosts/<host>/vars.yaml (git-crypt encrypted) Same content, encrypted in a separate forge canasta create on the new host, then canasta gitops join with the git-crypt key (or, for a repo in GPG mode, a recipient's GPG key), which renders the .env from the repo; then canasta backup restore for the data

The two pathways are independent: if the Restic backend is unreachable, the gitops repo + git-crypt key (or GPG key) brings everything back. If the gitops repo (or the key that unlocks it) is gone, Restic still has it.

Kubernetes

Pathway Captures Restored by
Restic snapshot's secrets-<id>.yaml <id>-db-credentials and <id>-mw-secrets as kubectl apply-able YAML canasta backup restore re-applies them via the web pod's kubectl
SOPS-encrypted manifests in the gitops repo every Opaque Secret Canasta owns in the instance namespace, encrypted to the operator age key Argo CD's repo-server decrypts at render time; canasta gitops join --key re-provisions the cluster's decryption key

The second pathway is opt-in: it exists only when the instance was initialized with canasta gitops init --encrypt-secrets (see GitOps: encrypted secrets with SOPS). Without it, a Kubernetes gitops repo is cleartext and carries no secrets at all, leaving the Restic snapshot as the only in-Canasta pathway. Note that the operator age key itself is not captured by canasta backup β€” it lives in the controller's config directory, so the exported <key>.age copy has to be stored off-host for this pathway to be recoverable. Operators who want a further independent pathway should layer their preferred secrets manager on top (see below).

Adding a third independent pathway via a secrets manager

For deployments where the threat model includes "Restic backend AND host or cluster destroyed simultaneously," operators should layer a corporate secrets manager. This applies to both orchestrators.

Brief pointers, in rough order of integration cost:

  • Manual convention. Paste the instance's secret material into a 1Password / Vault / Bitwarden / etc. Secure Note titled Canasta: <instance-id>. Recovery is a manual copy/paste back into a new envfile and canasta create -e envfile. Zero Canasta-side code; works today.
  • AWS Secrets Manager (K8s with external-secrets-operator) β€” operator stores secrets in AWS, ESO reconciles them into cluster Secrets matching Canasta's expected names (<id>-db-credentials, <id>-mw-secrets).
  • HashiCorp Vault (K8s with ESO or Vault Agent injector) β€” same shape, vendor-neutral.
  • sealed-secrets (K8s only) β€” encrypt the canasta-managed K8s Secrets with the cluster's public key, commit the sealed manifests anywhere (including the gitops repo); the in-cluster controller decrypts them. Useful when you want secrets in version control without a separate vault service.
  • 1Password Connect, Doppler, Akeyless, etc. β€” similar shape, vendor-managed.

Canasta doesn't endorse one of these β€” it documents what the runtime locations contain and lets operators choose escrow that fits their compliance posture.

What a secrets manager does and doesn't change

For Kubernetes, layering a secrets manager that reconciles into <id>-db-credentials / <id>-mw-secrets does shift the source of truth β€” the secrets-manager operator becomes the upstream, and canasta create's initial Secret population is overwritten on the next reconcile. That's a coherent operating model with real benefits (rotation, audit log, role-based access).

For Docker Compose, the equivalent does not change the in-use threat model. Docker Compose reads env vars from .env on the host filesystem; whatever secrets the wiki actually uses must live in plaintext on the host while the wiki is running. Anyone with root on the host can cat ~/canasta/<id>/.env regardless of whether the off-host backup lives in 1Password, in the gitops repo (encrypted), or both.

What a secrets manager does improve for Compose:

  • Audit log β€” who accessed which credentials when. git-crypt provides none.
  • Access control β€” role-based sharing rather than the all-or-nothing of "send a teammate the git-crypt key file."
  • Rotation tooling β€” vault-side workflows for periodic credential rotation.
  • No git-crypt key escrow burden β€” one less key to manage off-line.

So 1Password (or any equivalent) is "additionally available" for Compose rather than "filling a gap." git-crypt does most of what's needed for the secret-storage part; the secrets manager adds operator hygiene around audit, sharing, and rotation.

Disaster recovery

Compose

If the host is destroyed:

  1. Provision a fresh host that meets the target-host requirements.
  2. Restore one of the off-host secret pathways:
    • From Restic snapshot: canasta backup restore against the new host (will pull the .env contents along with the rest of the instance state).
    • From gitops repo: run canasta create on the new host, then canasta gitops join with the git-crypt key (or, for a repo in GPG mode, a recipient's GPG key). Join renders the .env from env.template with hosts/_shared/vars.yaml and the host's hosts/<host>/vars.yaml; pass --reinit to re-attach under the host name the lost host was registered with. Values the new instance generated itself (its database and admin passwords and secret key) replace that host's committed ones. Then restore the data with canasta backup restore. See Backup and restore: GitOps-managed instances.
    • From a secrets manager: pull the secret material into a fresh envfile, then canasta create -e envfile -i <id> on the new host, followed by import of any backed-up data.
  3. Verify the wiki is reachable and authenticates correctly. See Help:Troubleshooting for common post-restore symptoms.

Kubernetes

If the cluster is destroyed:

  1. Provision a fresh cluster (or re-provision via your cluster-as-code tooling). See Help:Multi-node Kubernetes for the topology requirements and Help:User journeys/Canasta on AWS EKS with RDS for an EKS worked example.
  2. Set up cluster-scoped infrastructure: ingress controller, cert-manager, EBS / NFS / EFS CSI driver, Argo CD if you use GitOps.
  3. Restore the canasta-managed Secrets via one of:
    • Restic snapshot (the in-Canasta pathway): canasta backup restore will re-apply secrets-<id>.yaml as part of the restore flow.
    • SOPS-encrypted gitops repo (if the instance used --encrypt-secrets): restore the operator age key from its off-host copy and run canasta gitops join --key <path>, which adopts the repo's existing recipient and provisions the cluster's sops-age Secret so Argo CD can render the encrypted Secrets.
    • Secrets manager (an independent pathway): use your operator (ESO, sealed-secrets controller, etc.) to materialize <id>-db-credentials and <id>-mw-secrets in the new instance namespace.
  4. Run canasta create -i <id> --orchestrator k8s with the same envfile content as the original. The Secrets you restored in step 3 will be picked up rather than overwritten if their names match.
  5. Verify pods come up, certificate issues, ingress is reachable, wiki authenticates.

See also

  • Help:Backup and restore β€” what Restic snapshots capture and how restore works.
  • Help:External database β€” points Canasta at a managed DB; the password rotation flow there involves the DB server you control.
  • Help:GitOps β€” version-controlled multi-environment config management; carries the Compose git-crypt and Kubernetes SOPS recovery pathways.
  • Help:Multi-node Kubernetes β€” multi-replica web requires shared (RWM) storage; the secret-flow described here applies regardless of replica count.
  • Help:Storage β€” persistent volume model; secrets are independent of storage classes.