Jurisdiction support for GDPR & FedRAMP
Purpose
This code allows users to deploy their durable objects in a manner that adheres to GDPR protocol. Values you can choose for JURISDICTION in your wrangler file include:
eufedramp
If no entry exists for JURISDICTION then it will fallback to using the REGION as a suggested deployment region, and if no region is specified then a normal deployment happens where the first request origin location to the DO is likely where the DO will exist.
Tasks
- [ ] Verify deployment succeeds
- [ ] Verify
colovalue adheres toeuandfedramplocations in the following cURL
Verify
curl --location 'https://starbasedb.YOUR-IDENTIFIER.workers.dev/status/trace' \
--header 'Authorization: Bearer ABC123' \
--header 'Content-Type: application/json' \
--data ''
Currently it appears we are receiving errors when deploying the durable object and attempting to access it with the above cURL. Here is the stacktrace:
{
"truncated": false,
"executionModel": "stateless",
"outcome": "exception",
"scriptVersion": {
"id": "f2ce52cf-ff42-4a92-8f0b-a568577fd195"
},
"scriptName": "starbasedb",
"diagnosticsChannelEvents": [],
"exceptions": [
{
"stack": " at async Object.fetch (index.js:1247:12)",
"name": "Error",
"message": "Internal error while starting up Durable Object storage caused object to be reset.",
"timestamp": 1730909659548
}
],
"logs": [],
"eventTimestamp": 1730909659249,
"event": {
"request": {
"url": "https://starbasedb.YOUR-IDENTIFIER.dev/status/trace",
"method": "GET",
"headers": {
"accept": "*/*",
"accept-encoding": "gzip, br",
"authorization": "REDACTED",
"cache-control": "no-cache",
"cf-connecting-ip": "24.112.251.244",
"cf-ipcountry": "US",
"cf-ray": "8de6603a4ec4dda6",
"cf-visitor": "{\"scheme\":\"https\"}",
"connection": "Keep-Alive",
"content-type": "application/json",
"host": "starbasedb.YOUR-IDENTIFIER.workers.dev",
"postman-token": "REDACTED",
"user-agent": "PostmanRuntime/7.39.1",
"x-forwarded-proto": "https",
"x-outerbase-source-token": "REDACTED",
"x-real-ip": "24.112.251.244",
"x-starbase-source": "external"
},
"cf": {
"clientTcpRtt": 36,
"longitude": "-80.13670",
"httpProtocol": "HTTP/1.1",
"tlsCipher": "AEAD-AES128-GCM-SHA256",
"continent": "NA",
"asn": 27364,
"clientAcceptEncoding": "gzip, deflate, br",
"country": "US",
"verifiedBotCategory": "",
"tlsClientAuth": {
"certIssuerDNLegacy": "",
"certIssuerSKI": "",
"certSubjectDNRFC2253": "",
"certSubjectDNLegacy": "",
"certFingerprintSHA256": "",
"certNotBefore": "",
"certSKI": "",
"certSerial": "",
"certIssuerDN": "",
"certVerified": "NONE",
"certNotAfter": "",
"certSubjectDN": "",
"certPresented": "0",
"certRevoked": "0",
"certIssuerSerial": "",
"certIssuerDNRFC2253": "",
"certFingerprintSHA1": ""
},
"tlsExportedAuthenticator": {
"clientFinished": "924b6c6998b8b57fd1222e23d3f0722a08648d4b56b79f5057962a19282bdd14",
"clientHandshake": "1209b74b97c80d47aac6eddc5f30d23257d83c9c206548b4189c4e0f20c40769",
"serverHandshake": "97f7bf3ed27d78a04ca5aed0b288c3bcbc14617b4aece30ddeb9b820ca4811e2",
"serverFinished": "a5542f301964c95bca55df64a955ddb6d72d322b1f52bd4e55270eeddaa083d5"
},
"tlsVersion": "TLSv1.3",
"colo": "IAD",
"tlsClientHelloLength": "508",
"edgeRequestKeepAliveStatus": 1,
"requestPriority": "",
"tlsClientExtensionsSha1": "/KdboeBKvsYpmQ6za4zdVuBsiNI=",
"tlsClientRandom": "49SOmhKB4z6XYh6xv0ohhV7snfpqZ9rLtRpV1lT5M8U="
}
},
"response": {
"status": 500
}
},
"id": 0
}
Before
After
Keep in mind that jurisdiction restrictions are not implemented inworkerd.
Keep in mind that
jurisdictionrestrictions are not implemented inworkerd.
Well, this explains so much. Thanks @daliborgogic!
Keep in mind that
jurisdictionrestrictions are not implemented inworkerd.Well, this explains so much. Thanks @daliborgogic!
Maybe I spoke a little too quickly before consuming coffee. After taking another look at the code, am I not already trying to apply the jurisdiction to the Durable Object?
const namespace = env.DATABASE_DURABLE_OBJECT.jurisdiction(env.JURISDICTION as Jurisdiction);
id = namespace.idFromName(DURABLE_OBJECT_ID);
stub = namespace.get(id);
Unless I'm missing a bigger piece here. Thoughts @daliborgogic?
Need to check if jurisdiction is in cf
if (env.JURISDICTION && request.cf.jurisdiction) {
// If jurisdiction is specified, it takes precedence over region
} else {
// Fall back to region-based routing if no jurisdiction is specified
}
edit: regarding GDPR & FedRAMP imho is better to use Regional Services. Because Workers may still access Durable Objects constrained to a jurisdiction from anywhere in the world.
Need to check if
jurisdictionis incfif (env.JURISDICTION && request.cf.jurisdiction) { // If jurisdiction is specified, it takes precedence over region } else { // Fall back to region-based routing if no jurisdiction is specified }edit: regarding GDPR & FedRAMP imho is better to use Regional Services. Because Workers may still access Durable Objects constrained to a jurisdiction from anywhere in the world.
Thanks for pointing that out @daliborgogic :)
I don't disagree that Regional Services is the better approach for GDPR and FedRAMP compliance – however, there's no good way to enforce those rules via Wrangler. It would be wonderful if I could declare it in my wrangler.toml file like something below and the infra would just know this worker type needs to be routed to an EU center. Not sure how this is handled on the Cloudflare side of things either so could just simply not be possible now or in the future.
[[data_localization]]
region = "eu"
Going back to your previous suggestion... When I do as suggested, I still don't see the colo value of a request made within the Durable Object to be in the EU region with the code I have below. To be fair though when I look at the logs in CF of a request made I don't see an entry for cf.jurisdiction.
let id: DurableObjectId;
let stub: DurableObjectStub;
if (env.JURISDICTION && request.cf?.jurisdiction) {
// If jurisdiction is specified, it takes precedence over region
const namespace = env.DATABASE_DURABLE_OBJECT.jurisdiction(env.JURISDICTION as Jurisdiction);
id = namespace.idFromName(DURABLE_OBJECT_ID);
stub = namespace.get(id);
} else {
// Fall back to region-based routing if no jurisdiction is specified
id = env.DATABASE_DURABLE_OBJECT.idFromName(DURABLE_OBJECT_ID);
const region = env.REGION ?? RegionLocationHint.AUTO;
stub = region !== RegionLocationHint.AUTO
? env.DATABASE_DURABLE_OBJECT.get(id, { locationHint: region as DurableObjectLocationHint })
: env.DATABASE_DURABLE_OBJECT.get(id);
}
"Durable Objects do not currently change locations after they are created."
cf.jurisdiction
My mistake, sorry. I'll try to do a minimal reproduction
@Brayden not sure if this works with Durable Objects but I know it does with D1, but Smart Placement might help: https://developers.cloudflare.com/workers/configuration/smart-placement/
When I do as suggested, I still don't see the colo value of a request made within the Durable Object to be in the EU region with the code I have below
Do you mean the request originates from the Durable Object? Or it's a request passed to the DO?