Skip to content

Cloudflare Tokens

BoB works in the operator’s Cloudflare account with two tokens (#3583, cap #3581). The how-to is Give This Machine Its Cloudflare Token.

Token What it is Where it lives Who makes it
Root token Mints the working tokens. Holds the API Tokens permission. The keychain of macbook-pro-16, service CLOUDFLARE_ROOT_TOKEN. No other machine holds a copy. The operator, once, in the Cloudflare dashboard (todo #3589).
Working token What BoB uses for every Cloudflare call from one machine. Named bob-<machine id>. The keychain of the machine it is for, service CLOUDFLARE_WORKING_TOKEN, account bob-<machine id>. BoB: bob cloudflare mint. Never typed or copied.

Both are declared by name in the operator repo’s operator.json secrets[] (bob secrets check reports each present or missing) and in secrets-inventory.json (ids cloudflare-root-token and cloudflare-working-<machine id>). No file holds a value.

A token is read from the environment variable of its name first, then from the keychain item of that name. The keychain is read inside the engine (scripts/cloudflare/cloudflare.js), never by a shell command. No command prints a token value.

One resolver, resolveAccount in scripts/cloudflare/cloudflare.js:

  1. The account. The project’s manifest _meta.cloudflare_account, else default_account in the operator repo’s config/cloudflare-accounts.json (the same rule as _cf_account in scripts/deploy/adapters/cloudflare.sh). --account <key> overrides both.
  2. The credentials. The account’s entry names them by their operator.json secrets[] names:
"default": {
"account_id": "bb4c9b919e317b42c003591ab2cc4ecd",
"label": "Paul Irving (default)",
"credential": "CLOUDFLARE_WORKING_TOKEN",
"root_credential": "CLOUDFLARE_ROOT_TOKEN"
}

An account that names neither uses those two names. node scripts/cloudflare/cloudflare.js resolve prints the account and the credential names for the current directory.

Terminal window
bob cloudflare mint --machine <id> [--account <key>] [--apply] [--replace] [--json]

Creates the working token bob-<machine id> with the root token and stores it in this machine’s keychain. Without --apply it reports what it would create.

  • --machine must be the machine this runs on: a token is stored in the keychain of the machine that uses it.
  • A token of that name that already exists is left alone. If this machine does not hold its value, the command exits 3 and names --replace, which deletes the old token and mints a new one. A value cannot be read back from Cloudflare.
  • A token that was created but could not be stored is deleted again.
  • With the root token absent it exits 3 and names the secret by its operator.json name (CLOUDFLARE_ROOT_TOKEN); bob secrets check reports the same secret missing.
  • A token can only grant what it holds. When Cloudflare refuses to let the root token grant some permission groups, mint exits 4, names the refused groups and files one todo (through the admission gate) with the steps to build the token in the dashboard. The todo carries the marker bob-cloudflare-mint:<account id>:<machine>; while one with that marker is open, a second run names it and files nothing.

Exit: 0 minted, present or a dry run · 1 the API failed or the token could not be stored · 2 usage, unknown machine or account, or not this machine · 3 the root token is missing or rejected, or a token exists whose value this machine lacks · 4 Cloudflare refused some groups (the todo is filed or already open).

What full access means, and what it excludes

Section titled “What full access means, and what it excludes”

The working token is scoped to one account and all its zones. It holds every permission group that GET /accounts/{account_id}/tokens/permission_groups returns, split into two policies:

  • account-scoped groups on com.cloudflare.api.account.<account_id>;
  • zone-scoped groups on com.cloudflare.api.account.<account_id> → com.cloudflare.api.account.zone.*.

It never holds:

  • the API Tokens groups (any group whose name contains API Tokens): a working token cannot mint, read or edit tokens, so it cannot make itself a root token. For the same reason it cannot read its own name.
  • any other account: a call to another account with it is refused.
  • groups whose scope is neither account nor zone (user-level groups, for example). The mint output lists each one it left out.
Terminal window
bob cloudflare verify [--cwd <dir>] [--account <key>] [--write] [--json]

Resolves the project’s account and working token, and calls GET /accounts/{account_id}/tokens/verify. It prints three lines: the account id, the token name and its status. The name is read with the root token when this machine holds it; otherwise it is bob-<this machine>, marked as by convention.

--write proves the access as well:

  1. it creates a KV namespace named bob-verify-<timestamp> (UTC, YYYYMMDD-HHMMSS) in the account and deletes it again;
  2. it reads every other account declared in config/cloudflare-accounts.json (GET /accounts/<id>) with the same token, and shows each read was refused.

Exit: 0 the token is active (and, with --write, both proofs held) · 1 the API failed · 2 usage or unknown account · 3 no working token here, or Cloudflare rejected it · 4 --write: the token could not write in its account, or another account answered it.

  • node scripts/checks/check-operator.js <operator-root> grades config/cloudflare-accounts.json: every account has a 32-character account id, default_account names an account, no value has the shape of a secret, and every credential / root_credential an account names is declared in operator.json secrets[]. An undeclared one fails, naming the account and the credential.
  • make check-secrets-inventory passes with both tokens listed by name, location and consumer (cloudflare-root-token, cloudflare-working-macbook-pro-16), and refuses any secret-shaped value. A Cloudflare token value starts cfat_; no file in the BoB checkout or the operator repo holds one.