Accounts & Credentials#

This page contains no secret values. It says where each credential is stored (a secrets.yaml path under sops, or a GitHub Actions repository secret), what depends on it, and how to rotate and verify it. Cells only Ben can fill are marked TODO(owner).

Two stores — do not confuse them:

  • sops (secrets.yaml) — decrypted on hosts at activation. Read a value with sops --decrypt --extract '["group"]["key"]' secrets.yaml; write one with sops set secrets.yaml '["group"]["key"]' '"value"' (JSON-encoded). A declared secret must exist before the host builds (eval-time check).
  • GitHub Actions secrets on tsunaminoai/nix-flake-final — used only by .github/workflows/*.yml. Set with gh secret set NAME -R tsunaminoai/nix-flake-final.

Summary#

Dependency Console Credential lives in Breaks when it lapses
Cloudflare (account sc2in, zone inaba.network) https://dash.cloudflare.com · Zero Trust team sc2in.cloudflareaccess.com sops cloudflare/terraform-token, cloudflare/inaba; Actions CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID *.inaba.network DNS/Access changes, ACME DNS-01 for inaba.network, docs publishing
Microsoft Entra (IdP behind Cloudflare Access) https://entra.microsoft.com Ben’s admin account — TODO(owner) Nobody can sign in to any *.inaba.network app or to Grafana
Tailscale (tailnet armadillo-banfish.ts.net) https://login.tailscale.com/admin Admin login TODO(owner); sops tailscale/auth-key; Tailnet Lock signing key on mokou New nodes cannot join or get no peer traffic; deploy-rs and SSH paths die
FlakeHub (Determinate Systems) https://flakehub.com sops nix/flakehub-token (rendered to a netrc by modules/flake/nix/flakehub-netrc.nix) nix flake update 401s; cache.flakehub.com silently stops substituting
GitHub — tsunaminoai (owner) https://github.com/tsunaminoai/nix-flake-final sops github/automation-token; Actions ACTIONS_PAT Auto-update PRs stop; docs-deploy status posts fail
GitHub — sc2ben (machine account) https://github.com/settings/keys as sc2ben Actions SC2_CI_SSH_KEY All three GitHub workflows fail to fetch the private corp input
BuyVM (myon) https://manage.buyvm.net (Stallion) Account login TODO(owner); root console password TODO(owner) A billing lapse deletes myon; acquisition egress stalls (kill switch, no leak)
Synology DSM (voile) https://voile:5001 · https://192.168.0.25:5001 Admin login TODO(owner) Backups, the Forgejo mirror (fleet stops converging), Kiwix, RADIUS Wi-Fi, media shares
BorgWarehouse (on voile) https://voile.armadillo-banfish.ts.net:7777 UI login TODO(owner); sops borgwarehouse/cronjob-key, borgwarehouse/api-key Status pushes fail (SystemdUnitFailed pages); no new repos
Forgejo mirror (on voile) http://voile.armadillo-banfish.ts.net:8418 Mirror-sync credential configured inside Forgejo — TODO(owner) release never advances on the mirror; every host’s guard fails closed
Bitwarden (Ben’s personal vault) TODO(owner) (bitwarden.com or self-hosted?) sops bitwarden/client-id, client-secret, topsecret, recovery-key Wi-Fi PSKs, the XCA passphrase, LUKS recovery passphrases become unreachable
Discord (notifications server) Channel settings → Integrations → Webhooks sops discord/notify-webhook (also merged into the Alertmanager env template) Auto-update failure notices and the critical-alert mirror go silent
healthchecks.io https://healthchecks.io HEALTHCHECKS_PING_URL inside sops telemetry/healthchecks-env; account TODO(owner) Whole-host death of ereshkigal is no longer noticed (dead-man’s switch)
Internal service tokens — sops paperless/api-token, jellyfin/api-token, openwebui/api-key, telemetry/*, step-ca/password-file, esphome/*, borgmatic/<host>/* Documented on each service page

Cloudflare#

  • Three different tokens. cloudflare/terraform-token (Zone:DNS:Edit on inaba.network plus Account:Zero Trust:Edit on sc2in) drives nix run .#terraform-cloudflare. cloudflare/inaba is the DNS-only ACME token. CLOUDFLARE_API_TOKEN (GitHub Actions) is the Pages deploy token for project master-documentation (docs.falseblue.com) in docs.yml. None is interchangeable.
  • Account, zone, Entra group, and IdP IDs are not secret; they are constants in modules/flake/terraform/default.nix.
  • Rotate: create a token with the same scope in the dashboard → sops set (or gh secret set) → for the terraform token run nix run .#terraform-cloudflare -- plan (must succeed with no auth error; apply is the owner’s call) → for the Pages token push a docs change and watch docs.yml.
  • Acceptance: curl -s -H "Authorization: Bearer $(sops --decrypt --extract '["cloudflare"]["terraform-token"]' secrets.yaml)" https://api.cloudflare.com/client/v4/user/tokens/verify contains "status":"active".
  • Access app tags must exist before apply (API error 12130); see Authentication and Family media.

Microsoft Entra#

  • Tenant sc2in; groups sg-voile-personal (family tier) and sg-voile-personal-admins (observability/admin tier). Their Object IDs are in modules/flake/terraform/default.nix.
  • Cloudflare Access emits group names as plain strings in the groups claim, and only when the groups scope is requested — see Grafana admin via Cloudflare Access.
  • TODO(owner): which Entra account is Global Administrator, and is there a break-glass account?

Tailscale#

  • Tailnet Lock is enabled. Sign a new node with sudo tailscale lock sign <nodekey> <tlpub> on mokou; the locked-out node prints the exact command in tailscale lock status. Symptom of an unsigned node: Online in the admin console, tailscale ping to every peer times out, journalctl -u tailscaled | grep tailnet-lock says “locked out”.
  • tailscale/auth-key is a reusable, tagged auth key consumed by tailscale-autoconnect on first boot (modules/nixos/tailscale/default.nix). Auth keys expire. A stale key makes a freshly flashed or installed node sit at “Logged out” with invalid key: API key does not exist. Mint a new one at Admin → Settings → Keys, sops set, rebuild the image or host. One-time fallback on the box: sudo tailscale up --ssh.
  • Acceptance: on the new node tailscale status lists peers and tailscale ping mokou answers.
  • Removing a host: Admin → Machines → remove (step 2 of the decommissioning runbook).

FlakeHub#

  • A ~90-day user token. It gates both flake-input fetches and the cache.flakehub.com substituter, so expiry shows up twice: nix flake update fails with HTTP error 401 — The provided token has expired, and host builds quietly build far more from source. TODO(owner): expiry date of the token currently in sops (a 2026-07 note said ~mid-September 2026).
  • Bypass to update the lock without a token: add --netrc-file /dev/null (anonymous access works for public flakes; the determinate input stays pinned).
  • Rotate: new token at flakehub.com → sops set secrets.yaml '["nix"]["flakehub-token"]' '"<token>"' → rebuild any host (the netrc is a sops template). The value must exist in secrets.yaml before rebuilding.
  • Acceptance: curl -s -o /dev/null -w '%{http_code}' -H "Authorization: Bearer $(sops --decrypt --extract '["nix"]["flakehub-token"]' secrets.yaml)" https://cache.flakehub.com/nix-cache-info prints 200.
  • GitHub Actions runners install Nix with DeterminateSystems/nix-installer-action and talk to FlakeHub too; a red “Nix Flake Checks” whose log says Failed to auth to FlakeHub is this token, not the code.

GitHub — tsunaminoai#

  • github/automation-token: fine-grained PAT (Contents RW, Pull requests RW, Metadata R) used only by the auto-update scheduler on ereshkigal (stage 1). Rotation and the acceptance curl (must print 200) are in the fleet-convergence gotchas; the 2026-08 rotation shipped a dead token because that check was skipped.
  • ACTIONS_PAT (Actions secret): the gitHubToken for cloudflare/pages-action in docs.yml, used to post deployment statuses on this repo. Not the automation token. TODO(owner): which account issued it and its scope.
  • github/ssh-key / github/ssh-pub: Ben’s personal fallback SSH key (modules/users/tsunami/ssh.nix), not a CI credential.
  • Branch protection on master must keep requiring “Nix Flake Checks”, and “Allow auto-merge” must stay on, or automation PRs never merge.
  • gh gotcha: if the active gh account is sc2ben, the personal repo 404s (“Could not resolve to a Repository”). Run gh auth switch --user tsunaminoai.

GitHub — sc2ben (machine account) and SC2_CI_SSH_KEY#

  • Why it exists: flake.nix input corp is git+ssh://git@github.com/sc2in/corp (transitively praxis and PolicyPress). Every flake input is placed in every host’s nix.registry (modules/flake/nix/nixos.nix), so evaluating any host fetches it. eval.yml, checks.yml, and docs.yml each write the key to ~/.ssh/id_ed25519 before running Nix.
  • The key is an account SSH key on sc2ben titled nff-ci (private flake inputs: corp/praxis/PolicyPress). It inherits sc2ben’s write access to those repos; the accepted mitigation is rotation on leak. TODO(owner): is the private half escrowed anywhere besides the Actions secret?
  • Rotate: 1. ssh-keygen -t ed25519 -f nff-ci -N '' 2. as sc2ben: gh ssh-key add nff-ci.pub --type authentication --title 'nff-ci (private flake inputs: corp/praxis/PolicyPress)' 3. gh secret set SC2_CI_SSH_KEY -R tsunaminoai/nix-flake-final < nff-ci 4. delete the old key from sc2ben; shred the local file.
  • Acceptance: GIT_SSH_COMMAND='ssh -i nff-ci -o IdentitiesOnly=yes' git ls-remote git@github.com:sc2in/corp HEAD prints a ref; then a branch push turns eval.yml green.
  • ereshkigal holds no copy of this key. Its om ci stubs corp with --override-input flake/corp path:./ci/stub-corp, so the real SC2 compliance module is validated only by GitHub CI. See SDLC §6.5.

BuyVM (myon)#

  • Stallion panel: https://manage.buyvm.net — console, rescue ISO library, reinstall. A root console password was set on 2026-08-07. TODO(owner): account email, and where the console password is stored.
  • Public IP 45.61.186.219; its reverse DNS still says mail.rdp.webportal.cyou (previous tenant). Set it to myon.falseblue.com in the panel when convenient.
  • Never install or build on the box: 1 GB OOMs. myon has the rescue-ISO lustrate procedure.

Synology DSM (voile) and BorgWarehouse#

  • DSM: https://voile:5001. TODO(owner): admin account, and where its password and 2FA recovery codes live.
  • BorgWarehouse has two keys: borgwarehouse/cronjob-key is the server’s CRONJOB_KEY (from the .env in /volume1/BorgWarehouse), used by the borgwarehouse-cron-* units; borgwarehouse/api-key is a read-only account API key (UI → Account → Integrations). Details: Backups.
  • Acceptance after rotating the cron key, on ereshkigal: systemctl start borgwarehouse-cron-status && systemctl status borgwarehouse-cron-status exits 0.
  • Forgejo mirror health: curl -fsS http://voile:8418/api/v1/version. If the mirror stops syncing from GitHub, hosts stop converging and AutoUpgradeCompareFailed fires (runbook). TODO(owner): which GitHub credential the Forgejo mirror uses to pull, and when it expires.

Bitwarden#

  • Holds: the FalseBlue-Basic and FalseBlue-IoT Wi-Fi PSKs, FalseBlue Root CA Key (XCA database passphrase), the /pool LUKS recovery passphrase on mokou. TODO(owner): also the DSM / BuyVM / FlakeHub / healthchecks logins?
  • The bw CLI on Ben’s hosts unlocks from sops-provisioned files (modules/home-manager/tools/bitwarden.nix): bitwarden/client-id and bitwarden/client-secret (API key) plus bitwarden/topsecret (master password file). bitwarden/recovery-key is the account recovery key.
  • TODO(owner): is Bitwarden Emergency Access configured for a successor, and for whom?

Discord and healthchecks.io#

  • discord/notify-webhook is read at runtime by the auto-update unit and is also interpolated into ereshkigal’s alertmanager-env sops template as DISCORD_WEBHOOK_URL (Alertmanager takes exactly one EnvironmentFile). Rotate in the channel’s webhook settings → sops set → redeploy ereshkigal. Acceptance: curl -s -o /dev/null -w '%{http_code}' -H 'Content-Type: application/json' -d '{"content":"webhook test"}' "$(sops --decrypt --extract '["discord"]["notify-webhook"]' secrets.yaml)" prints 204.
  • telemetry/healthchecks-env carries HEALTHCHECKS_PING_URL; the vmalert Watchdog rule pings it continuously and healthchecks.io alerts when the pings stop. Rotate by creating a new check and updating the env value. TODO(owner): the healthchecks.io account and who receives its alerts. Delivery details: Alerts.

Review cadence#

Review this page quarterly and on any change to the secrets model (SDLC §10). Add a row when a new external dependency lands; delete the sops key and the row when one is retired.