Cloudflare Tokens
Cloudflare Tokens
Section titled “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.
The two tokens
Section titled “The two tokens”| 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.
From a project to a token
Section titled “From a project to a token”One resolver, resolveAccount in scripts/cloudflare/cloudflare.js:
- The account. The project’s manifest
_meta.cloudflare_account, elsedefault_accountin the operator repo’sconfig/cloudflare-accounts.json(the same rule as_cf_accountinscripts/deploy/adapters/cloudflare.sh).--account <key>overrides both. - The credentials. The account’s entry names them by their
operator.jsonsecrets[]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.
bob cloudflare mint
Section titled “bob cloudflare mint”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.
--machinemust 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.jsonname (CLOUDFLARE_ROOT_TOKEN);bob secrets checkreports 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 markerbob-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.
bob cloudflare verify
Section titled “bob cloudflare verify”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:
- it creates a KV namespace named
bob-verify-<timestamp>(UTC,YYYYMMDD-HHMMSS) in the account and deletes it again; - 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.
The checks that guard it
Section titled “The checks that guard it”node scripts/checks/check-operator.js <operator-root>gradesconfig/cloudflare-accounts.json: every account has a 32-character account id,default_accountnames an account, no value has the shape of a secret, and everycredential/root_credentialan account names is declared inoperator.jsonsecrets[]. An undeclared one fails, naming the account and the credential.make check-secrets-inventorypasses 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 startscfat_; no file in the BoB checkout or the operator repo holds one.