GCP deployment profile and Cloud HSM
Hosting and signing are independent choices. The same domain rules, API contract, gateway protocol and event envelopes run on Cloudflare or on GCP, and either host can sign with GCP Cloud HSM.
Profiles
| Capability | Cloudflare (launch) | GCP (target) |
|---|---|---|
| HTTP and compute | Workers for the API, staff console and webhooks; a separate signer Worker reached by service binding. | The same handlers in a Node HTTP adapter on Cloud Run; a private, authenticated signer service. |
| Relational state | D1 (SQLite). | Cloud SQL for PostgreSQL (regional HA, point-in-time recovery). |
| Objects and audit | R2 buckets for public artifacts and protected exports. | Cloud Storage buckets. |
| Schedules | Cron Triggers every minute. | Cloud Scheduler → POST /internal/scheduled with Cloud Run invoker IAM. |
| Signing | Simulator, or Cloud HSM via STS over an outbound mTLS binding. | Simulator, or Cloud HSM with the attached service identity. |
Real HSM from Cloudflare
Cloud HSM is set up from the staff console (Signing backends, roles trust_ops and pki_key_custodian), which stores only non-secret configuration and walks through four steps. See Issuance backends for the model and both backend kinds.
- Infrastructure, as the separate PKI operator principal: a key ring in the approved region,
EC_SIGN_P256_SHA256keys with protection levelHSM(ca-issuance,manifest-signing; roots stay under the ceremony project), an X.509 workload identity pool provider whose trust store holds the signer deployment CA and whose attribute condition accepts only the signer certificate subject, androles/cloudkms.signerVerifierfor that principal on exactly those keys. The console shows thegcloudcommands for your values;infra/gcp/kmshas the same resources as Terraform. - Signer deployment certificate. The signer generates the P-256 key pair and returns a CSR; sign it with the deployment CA and register the certificate (its fingerprint and subject are recorded). Cloudflare's outbound mTLS binding holds the private key, so a PKI operator exports it once for
wrangler mtls-certificate upload; the signer destroys its copy. - Key versions: register each full immutable
cryptoKeyVersions/Nname; a second, independent staff member approves it before the signer can use it. - Test connection: the signer exchanges its certificate for a token at Google STS, fetches each public key, checks protection level
HSM, algorithm, location and the exact version name, signs a fresh challenge digest with CRC32C checks both ways and verifies it locally. The result is stored as signed, timestamped evidence. Activation needs a passing test of the current configuration from the last 24 hours and dual control.
npx wrangler mtls-certificate upload --cert signer.pem --key signer-key.pem --name mt-signer-sts"mtls_certificates": [{ "binding": "GCP_STS", "certificate_id": "<id>" }],
"vars": { "SIGNER_MODE": "gcp_kms", "DEPLOYMENT_ENV": "production" }
// optional: "GCP_ALLOWED_KEY_VERSIONS" pins the allowlist furtherWhat the KMS provider enforces
- Only full, immutable key-version names approved under dual control on an active backend (and on
GCP_ALLOWED_KEY_VERSIONSwhen set) can be used, inside the backend's location. - The key must report algorithm
EC_SIGN_P256_SHA256and protection levelHSM, and its public-key fingerprint must match the recorded one. - Digest, signature and public-key CRC32C are verified, and every response must name the expected key version. Federated tokens are bound to the deployment certificate, so Cloud KMS is called on its mTLS endpoint through the same binding.
- The Google endpoints are fixed in code; a production signer refuses any other base URL.
- Timeouts, quota errors and disabled keys fail closed. There is no software fallback, and key creation or state changes are not possible from the online signer.
Full GCP hosting
npm run build:gcp # → dist/gcp/server.mjs| Variable | Meaning |
|---|---|
DATABASE_URL | Cloud SQL for PostgreSQL connection string. |
SIGNER_URL | Private signer service URL; calls carry an ID token from the metadata server. |
ARTIFACTS_BUCKET | Cloud Storage bucket for public artifacts (policies, CRLs). |
AUDIT_BUCKET | Cloud Storage bucket for protected exports. |
SIGNER_CALLER_TOKEN | Shared caller secret for the signer. |
APP_ORIGIN | https://app.mutualtrust.io |
API_ORIGIN | https://api.mutualtrust.io |
DEPLOYMENT_ENV | sandbox (default) or production. |
OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET | Customer sign-in; see Identity and access. |
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_TEAM_10, STRIPE_PRICE_TEAM_50 | Billing (optional). |
ALLOW_SANDBOX_GUEST | true enables guest sandbox sign-in. |
PORT | Listen port (default 8080). |
- Database: apply
db/migrations/postgres/0001_init.sqlon Cloud SQL for PostgreSQL. - Signer: deploy as a private Cloud Run service with its attached service identity; invocation requires an ID token for its URL.
- Schedules: Cloud Scheduler calls
POST /internal/scheduledevery minute with Cloud Run invoker IAM.
Moving between hosts
A hosting move keeps customer URLs, trust anchors, certificates, grant IDs and payer relationships. CA key versions stay in place and the new signer identity is granted scoped access. Because a restored database can predate a withdrawal, gateways reject backward policy revisions, and an epoch reset requires an independently authenticated recovery action.