Skip to content

Commit 2de2ac8

Browse files
authored
Merge pull request #2001 from Automattic/feat/1971-administrator-claim
Add one-time administrator claims
2 parents e44ec70 + c621e7e commit 2de2ac8

6 files changed

Lines changed: 125 additions & 12 deletions

File tree

docs/cloudflare-provisioning-api.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Cloudflare provisioning API core
1+
# Cloudflare provisioning API
22

33
The D1 Worker exposes a versioned control-plane API before hostname-based WordPress routing. `/v1/*` never boots or falls through to WordPress.
44

@@ -18,7 +18,8 @@ The Worker compares SHA-256 token digests and never accepts plaintext API creden
1818
- `GET /v1/sites/{siteId}` requires `sites:read`.
1919
- `POST /v1/sites/{siteId}/imports` requires `sites:import` and uses the existing bounded static-artifact request.
2020
- `GET /v1/sites/{siteId}/operations/{operationId}` requires `operations:read`.
21+
- `POST /v1/sites/{siteId}/administrator-claim` exchanges its one-time bearer capability for the persistent site-scoped `admin` credential after provisioning succeeds. It does not accept an API bearer token.
2122

2223
All responses use versioned WP Codebox provisioning schemas. Site IDs are allocated only from `WORDPRESS_SITE_CONTEXTS`; caller hostnames, DNS, and Cloudflare control APIs are not inputs. The allocation transaction reserves the same shipped D1 site identity used by legacy/operator operations, so both interfaces contend on one hostname and an existing site cannot be taken over. D1 stores allocation ownership, immutable artifact identity and import options, and API operation links. The scheduler resumes incomplete allocations for its selected site before it runs an operation; it verifies staged bytes and converges the conditional destination copy, operation, and API link without blocking publication or cron when recovery fails.
2324

24-
This is the API core portion of #1971. Administrator claims are intentionally a follow-up and are not represented by a route, secret, D1 field, resource field, documentation contract, or test in this PR.
25+
`POST /v1/sites` validates `WORDPRESS_ADMIN_CLAIM_SECRET` and `WORDPRESS_ADMIN_PASSWORD` before allocation. Its create/replay response includes the deterministic pending capability while the configured root still derives the stored digest. Site reads expose only claim state, expiry, and the fixed endpoint. D1 stores capability and derived-credential digests, never either plaintext value. Scheduled allocation recovery issues a missing claim before provisioning work can run. Redemption requires the current site-scoped credential to match the digest pinned before bootstrap, then atomically consumes the capability exactly once and transfers that persistent administrator credential; it is not a one-time browser session. Rotating either root preserves the pending record but requires restoring the matching root before replay or redemption.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
"smoke": "tsx scripts/run-smoke.ts",
108108
"test:redaction": "tsx tests/redaction.test.ts",
109109
"test:cloudflare-runtime": "node --test tests/cloudflare-d1-provisioner.test.mjs && tsx tests/cloudflare-site-context.test.ts && tsx tests/cloudflare-coordinator-site-partitioning.test.ts && tsx tests/cloudflare-d1-operation-repository.test.ts && tsx tests/cloudflare-provisioning-api.test.ts && tsx tests/cloudflare-runtime.test.ts && node ./node_modules/typescript/bin/tsc -p packages/runtime-cloudflare --noEmit",
110+
"test:cloudflare-administrator-claim": "tsx tests/cloudflare-provisioning-api.test.ts",
110111
"test:cloudflare-wordpress-auth": "tsx tests/cloudflare-wordpress-auth.test.ts",
111112
"test:cloudflare-wordpress-archive-corpus": "tsx tests/cloudflare-wordpress-archive-corpus.test.ts",
112113
"test:agent-task-contracts": "tsx tests/agent-task-contracts.test.ts && tsx tests/agent-task-canonical-evidence.test.ts && npm run test:agent-task-workflow-interface && npm run test:runtime-sources-materialization && tsx tests/agent-task-reusable-workflow.test.ts",

packages/runtime-cloudflare/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ Use authenticated `GET ?phase=operator-fence-status` after acquisition to export
4040

4141
For lossless D1-to-Durable-Object rollback, keep D1 deployed as the active Worker, acquire its fence, and capture its coherent status. Address the retained Durable Object only with `coordinator=durable-object` on authenticated `operator-fence-*` and `operator-adopt` requests. Fence that selected coordinator first, then adopt the D1 pointer and version with its matching `fenceToken`; selected-coordinator adoption fails unless that fence is active and matches. Require its selected status envelope to be coherent and identical, then promote the Durable Object Worker before the D1 fence expires and release its fence. Its first mutation must commit at `N+1`. The Durable Object binding and historical `WordPressStateCoordinator` export remain in the D1 profile for this procedure. Non-operator requests always retain the active coordinator, and selectors on unsupported or unknown operator routes fail closed.
4242

43+
## Administrator Claim
44+
45+
`POST /v1/sites/{siteId}/administrator-claim` is intercepted before WordPress dispatch and uses only `Authorization: Bearer <capability>`. It is not an API-token endpoint. A successful provision create response includes its pending `administratorClaim` capability exactly for the create/replay window; site reads expose only its fixed endpoint URL, state, and expiry. The capability is a deterministic 256-bit HMAC derived from `WORDPRESS_ADMIN_CLAIM_SECRET` and immutable allocation identity. D1 retains capability and derived-credential digests, expiry, and lifecycle state, never plaintext values. Scheduled recovery creates a missing claim before provisioning can run, and redemption refuses to consume it when either configured root no longer matches the pinned derivation.
46+
47+
Configure both `WORDPRESS_ADMIN_CLAIM_SECRET` and `WORDPRESS_ADMIN_PASSWORD` before provisioning. Redemption waits for the linked provision operation to succeed, atomically consumes the claim, and returns `{ "username": "admin", "password": "..." }`, where the password is the persistent site-scoped administrator credential derived from `WORDPRESS_ADMIN_PASSWORD`. This transfers that credential; it is not a one-time browser session.
48+
4349
## Static Artifact Import
4450

4551
An authenticated `POST ?phase=operator-static-artifact-import` materializes a verified website artifact into the existing canonical site. The request is limited to 16 KiB and references an immutable JSON object at `sites/{siteId}/import-artifacts/<sha256>.json`; the Worker requires the declared R2 key, size, and SHA-256 to agree before creating work. The canonical `blocks-engine/php-transformer/site-artifact/v1` payload is limited to 4 MiB serialized, 500 safe unique files, 8 MiB per decoded file, and 32 MiB decoded in aggregate.

0 commit comments

Comments
 (0)