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.

Documentation menu

Profiles

Hosting profiles
CapabilityCloudflare (launch)GCP (target)
HTTP and computeWorkers 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 stateD1 (SQLite).Cloud SQL for PostgreSQL (regional HA, point-in-time recovery).
Objects and auditR2 buckets for public artifacts and protected exports.Cloud Storage buckets.
SchedulesCron Triggers every minute.Cloud Scheduler → POST /internal/scheduled with Cloud Run invoker IAM.
SigningSimulator, 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.

  1. Infrastructure, as the separate PKI operator principal: a key ring in the approved region, EC_SIGN_P256_SHA256 keys with protection level HSM (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, and roles/cloudkms.signerVerifier for that principal on exactly those keys. The console shows the gcloud commands for your values; infra/gcp/kms has the same resources as Terraform.
  2. 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.
  3. Key versions: register each full immutable cryptoKeyVersions/N name; a second, independent staff member approves it before the signer can use it.
  4. 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.
Upload the mTLS certificate (PKI operator)
npx wrangler mtls-certificate upload --cert signer.pem --key signer-key.pem --name mt-signer-sts
services/signer/wrangler.jsonc
"mtls_certificates": [{ "binding": "GCP_STS", "certificate_id": "<id>" }], "vars": { "SIGNER_MODE": "gcp_kms", "DEPLOYMENT_ENV": "production" } // optional: "GCP_ALLOWED_KEY_VERSIONS" pins the allowlist further

What the KMS provider enforces

  • Only full, immutable key-version names approved under dual control on an active backend (and on GCP_ALLOWED_KEY_VERSIONS when set) can be used, inside the backend's location.
  • The key must report algorithm EC_SIGN_P256_SHA256 and protection level HSM, 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

Build the Cloud Run bundle
npm run build:gcp # → dist/gcp/server.mjs
Cloud Run environment
VariableMeaning
DATABASE_URLCloud SQL for PostgreSQL connection string.
SIGNER_URLPrivate signer service URL; calls carry an ID token from the metadata server.
ARTIFACTS_BUCKETCloud Storage bucket for public artifacts (policies, CRLs).
AUDIT_BUCKETCloud Storage bucket for protected exports.
SIGNER_CALLER_TOKENShared caller secret for the signer.
APP_ORIGINhttps://app.mutualtrust.io
API_ORIGINhttps://api.mutualtrust.io
DEPLOYMENT_ENVsandbox (default) or production.
OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRETCustomer sign-in; see Identity and access.
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_TEAM_10, STRIPE_PRICE_TEAM_50Billing (optional).
ALLOW_SANDBOX_GUESTtrue enables guest sandbox sign-in.
PORTListen port (default 8080).
  • Database: apply db/migrations/postgres/0001_init.sql on 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/scheduled every 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.