Skip to content

Commit 6c03bde

Browse files
feat(wrangler): add support for FedRAMP High compliance region (#9321)
* feat(wrangler): add support for FedRAMP High compliance region Now it is possible to target Wrangler at the FedRAMP High compliance region. There are two ways to signal to Wrangler to run in this mode: - set `"compliance_region": "fedramp_high"` in a Wrangler configuration - set `CLOUDFLARE_COMPLIANCE_REGION=fedramp_high` environment variable when running Wrangler If both are provided then the environment variable overrides the configured value. When in this mode OAuth authentication is not supported. It is necessary to authenticate using a Cloudflare API Token acquired from the Cloudflare FedRAMP High dashboard. Most bindings and commands are supported in this mode. - Unsupported commands may result in API requests that are not supported - possibly 422 Unprocessable Entity responses. - Unsupported bindings may work in local dev, as there is no local validation, but will fail at Worker deployment time. Resolves DEVX-1921. * update workers.dev domain for fed mode * error when env var and config don't line up * fix env-var factory regression from earlier commits in this PR * ensure that the local runtime controller passes through region to start mixed mode * Check the logged-in state in the remote runtime controller rather than at the start of `wrangler dev`. By this point we have the resolved config, so we can user the compliance region but also it handles the case where a user toggles remote mode on during a dev session. * Print compliance region in `wrangler whoami` * fixups * move ComplianceConfig type to misc-variables * more fixes * agh * tighten ComplianceConfig so that it must be defined * revert unnecessary change to `usingLocalNamespace` * fix up the tests * remove unnecessary empty line
1 parent 68a9008 commit 6c03bde

File tree

139 files changed

+2091
-684
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

139 files changed

+2091
-684
lines changed

.changeset/swift-bees-agree.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Add support for FedRAMP High compliance region
6+
7+
Now it is possible to target Wrangler at the FedRAMP High compliance region.
8+
There are two ways to signal to Wrangler to run in this mode:
9+
10+
- set `"compliance_region": "fedramp_high"` in a Wrangler configuration
11+
- set `CLOUDFLARE_COMPLIANCE_REGION=fedramp_high` environment variable when running Wrangler
12+
13+
If both are provided and the values do not match then Wrangler will exit with an error.
14+
15+
When in this mode OAuth authentication is not supported.
16+
It is necessary to authenticate using a Cloudflare API Token acquired from the Cloudflare FedRAMP High dashboard.
17+
18+
Most bindings and commands are supported in this mode.
19+
20+
- Unsupported commands may result in API requests that are not supported - possibly 422 Unprocessable Entity responses.
21+
- Unsupported bindings may work in local dev, as there is no local validation, but will fail at Worker deployment time.
22+
23+
Resolves DEVX-1921.

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"esbuild",
1515
"eslintcache",
1616
"execa",
17+
"fedramp",
1718
"filestat",
1819
"haikunate",
1920
"haikunator",

packages/wrangler/src/__tests__/ai.local.test.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { Request } from "miniflare";
22
import { HttpResponse } from "msw";
33
import { Headers } from "undici";
4-
import { AIFetcher } from "../ai/fetcher";
4+
import { getAIFetcher } from "../ai/fetcher";
55
import * as internal from "../cfetch/internal";
6+
import { COMPLIANCE_REGION_CONFIG_UNKNOWN } from "../environment-variables/misc-variables";
67
import * as user from "../user";
7-
import type { RequestInit } from "undici";
8+
9+
const AIFetcher = getAIFetcher(COMPLIANCE_REGION_CONFIG_UNKNOWN);
810

911
describe("ai", () => {
1012
describe("fetcher", () => {
@@ -16,7 +18,7 @@ describe("ai", () => {
1618
it("should send x-forwarded header", async () => {
1719
vi.spyOn(user, "getAccountId").mockImplementation(async () => "123");
1820
vi.spyOn(internal, "performApiFetch").mockImplementation(
19-
async (resource: string, init: RequestInit = {}) => {
21+
async (config, resource, init = {}) => {
2022
const headers = new Headers(init.headers);
2123
return HttpResponse.json({
2224
xForwarded: headers.get("X-Forwarded"),
@@ -44,7 +46,7 @@ describe("ai", () => {
4446
it("account id should be set", async () => {
4547
vi.spyOn(user, "getAccountId").mockImplementation(async () => "123");
4648
vi.spyOn(internal, "performApiFetch").mockImplementation(
47-
async (resource: string) => {
49+
async (config, resource) => {
4850
return HttpResponse.json({
4951
resource: resource,
5052
});

packages/wrangler/src/__tests__/api/startDevWorker/BundleController.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ function configDefaults(
4040
const persist = path.join(process.cwd(), ".wrangler/persist");
4141
return {
4242
name: "test-worker",
43+
complianceRegion: undefined,
4344
entrypoint: "NOT_REAL",
4445
projectRoot: "NOT_REAL",
4546
build: unusable<StartDevWorkerOptions["build"]>(),

packages/wrangler/src/__tests__/api/startDevWorker/LocalRuntimeController.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ function configDefaults(
128128
): StartDevWorkerOptions {
129129
return {
130130
name: "test-worker",
131+
complianceRegion: undefined,
131132
entrypoint: "NOT_REAL",
132133
projectRoot: "NOT_REAL",
133134
build: unusable<StartDevWorkerOptions["build"]>(),

packages/wrangler/src/__tests__/cert.test.ts

Lines changed: 75 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
uploadMTlsCertificateFromFs,
1111
} from "../api";
1212
import { type MTlsCertificateResponse } from "../api/mtls-certificate";
13+
import { COMPLIANCE_REGION_CONFIG_UNKNOWN } from "../environment-variables/misc-variables";
1314
import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
1415
import { mockConsoleMethods } from "./helpers/mock-console";
1516
import { mockConfirm } from "./helpers/mock-dialogs";
@@ -202,11 +203,15 @@ describe("wrangler", () => {
202203
expires_on: oneYearLater.toISOString(),
203204
});
204205

205-
const cert = await uploadMTlsCertificate("some-account-id", {
206-
certificateChain: "BEGIN CERTIFICATE...",
207-
privateKey: "BEGIN PRIVATE KEY...",
208-
name: "my_cert",
209-
});
206+
const cert = await uploadMTlsCertificate(
207+
COMPLIANCE_REGION_CONFIG_UNKNOWN,
208+
"some-account-id",
209+
{
210+
certificateChain: "BEGIN CERTIFICATE...",
211+
privateKey: "BEGIN PRIVATE KEY...",
212+
name: "my_cert",
213+
}
214+
);
210215

211216
expect(cert.id).toEqual("1234");
212217
expect(cert.issuer).toEqual("example.com...");
@@ -219,11 +224,15 @@ describe("wrangler", () => {
219224
describe("uploadMTlsCertificateFromFs", () => {
220225
it("should fail to read cert and key files when missing", async () => {
221226
await expect(
222-
uploadMTlsCertificateFromFs("some-account-id", {
223-
certificateChainFilename: "cert.pem",
224-
privateKeyFilename: "key.pem",
225-
name: "my_cert",
226-
})
227+
uploadMTlsCertificateFromFs(
228+
COMPLIANCE_REGION_CONFIG_UNKNOWN,
229+
"some-account-id",
230+
{
231+
certificateChainFilename: "cert.pem",
232+
privateKeyFilename: "key.pem",
233+
name: "my_cert",
234+
}
235+
)
227236
).rejects.toMatchInlineSnapshot(
228237
`[ParseError: Could not read file: cert.pem]`
229238
);
@@ -238,11 +247,15 @@ describe("wrangler", () => {
238247
writeFileSync("cert.pem", "BEGIN CERTIFICATE...");
239248
writeFileSync("key.pem", "BEGIN PRIVATE KEY...");
240249

241-
const cert = await uploadMTlsCertificateFromFs("some-account-id", {
242-
certificateChainFilename: "cert.pem",
243-
privateKeyFilename: "key.pem",
244-
name: "my_cert",
245-
});
250+
const cert = await uploadMTlsCertificateFromFs(
251+
COMPLIANCE_REGION_CONFIG_UNKNOWN,
252+
"some-account-id",
253+
{
254+
certificateChainFilename: "cert.pem",
255+
privateKeyFilename: "key.pem",
256+
name: "my_cert",
257+
}
258+
);
246259

247260
expect(cert.id).toEqual("1234");
248261
expect(cert.issuer).toEqual("example.com...");
@@ -255,11 +268,15 @@ describe("wrangler", () => {
255268
describe("uploadCaCertificateFromFs", () => {
256269
it("should fail to read ca cert when file is missing", async () => {
257270
await expect(
258-
uploadCaCertificateFromFs("some-account-id", {
259-
certificates: "caCert.pem",
260-
ca: true,
261-
name: "my_cert",
262-
})
271+
uploadCaCertificateFromFs(
272+
COMPLIANCE_REGION_CONFIG_UNKNOWN,
273+
"some-account-id",
274+
{
275+
certificates: "caCert.pem",
276+
ca: true,
277+
name: "my_cert",
278+
}
279+
)
263280
).rejects.toMatchInlineSnapshot(
264281
`[ParseError: Could not read file: caCert.pem]`
265282
);
@@ -273,11 +290,15 @@ describe("wrangler", () => {
273290

274291
writeFileSync("caCert.pem", "BEGIN CERTIFICATE...");
275292

276-
const cert = await uploadCaCertificateFromFs("some-account-id", {
277-
certificates: "caCert.pem",
278-
ca: true,
279-
name: "my_cert",
280-
});
293+
const cert = await uploadCaCertificateFromFs(
294+
COMPLIANCE_REGION_CONFIG_UNKNOWN,
295+
"some-account-id",
296+
{
297+
certificates: "caCert.pem",
298+
ca: true,
299+
name: "my_cert",
300+
}
301+
);
281302

282303
expect(cert.id).toEqual("1234");
283304
expect(cert.issuer).toEqual("example.com...");
@@ -309,7 +330,12 @@ describe("wrangler", () => {
309330
},
310331
]);
311332

312-
const certs = await listMTlsCertificates("some-account-id", {}, true);
333+
const certs = await listMTlsCertificates(
334+
COMPLIANCE_REGION_CONFIG_UNKNOWN,
335+
"some-account-id",
336+
{},
337+
true
338+
);
313339

314340
expect(certs).toHaveLength(2);
315341

@@ -334,7 +360,11 @@ describe("wrangler", () => {
334360
expires_on: oneYearLater.toISOString(),
335361
});
336362

337-
const cert = await getMTlsCertificate("some-account-id", "1234");
363+
const cert = await getMTlsCertificate(
364+
COMPLIANCE_REGION_CONFIG_UNKNOWN,
365+
"some-account-id",
366+
"1234"
367+
);
338368

339369
expect(cert.id).toEqual("1234");
340370
expect(cert.issuer).toEqual("example.com...");
@@ -358,6 +388,7 @@ describe("wrangler", () => {
358388
]);
359389

360390
const cert = await getMTlsCertificateByName(
391+
COMPLIANCE_REGION_CONFIG_UNKNOWN,
361392
"some-account-id",
362393
"cert one",
363394
true
@@ -374,7 +405,12 @@ describe("wrangler", () => {
374405
const mock = mockGetMTlsCertificates([]);
375406

376407
await expect(
377-
getMTlsCertificateByName("some-account-id", "cert one", true)
408+
getMTlsCertificateByName(
409+
COMPLIANCE_REGION_CONFIG_UNKNOWN,
410+
"some-account-id",
411+
"cert one",
412+
true
413+
)
378414
).rejects.toMatchInlineSnapshot(
379415
`[Error: certificate not found with name "cert one"]`
380416
);
@@ -403,7 +439,12 @@ describe("wrangler", () => {
403439
]);
404440

405441
await expect(
406-
getMTlsCertificateByName("some-account-id", "cert one", true)
442+
getMTlsCertificateByName(
443+
COMPLIANCE_REGION_CONFIG_UNKNOWN,
444+
"some-account-id",
445+
"cert one",
446+
true
447+
)
407448
).rejects.toMatchInlineSnapshot(
408449
`[Error: multiple certificates found with name "cert one"]`
409450
);
@@ -416,7 +457,11 @@ describe("wrangler", () => {
416457
test("calls delete mts_certificates endpoint", async () => {
417458
const mock = mockDeleteMTlsCertificate();
418459

419-
await deleteMTlsCertificate("some-account-id", "1234");
460+
await deleteMTlsCertificate(
461+
COMPLIANCE_REGION_CONFIG_UNKNOWN,
462+
"some-account-id",
463+
"1234"
464+
);
420465

421466
expect(mock.calls).toEqual(1);
422467
});

packages/wrangler/src/__tests__/core/command-registration.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,11 @@ describe("CommandRegistry", () => {
225225
registry.define([
226226
{
227227
command: "wrangler original-command",
228-
// @ts-expect-error missing definition
229228
definition: {
230229
metadata: {
231230
status: "stable",
231+
description: "Original command",
232+
owner: "Workers: Authoring and Testing",
232233
},
233234
},
234235
},

packages/wrangler/src/__tests__/d1/timeTravel.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { http, HttpResponse } from "msw";
22
import { throwIfDatabaseIsAlpha } from "../../d1/timeTravel/utils";
3+
import { COMPLIANCE_REGION_CONFIG_UNKNOWN } from "../../environment-variables/misc-variables";
34
import { mockAccountId, mockApiToken } from "../helpers/mock-account-id";
45
import { mockConsoleMethods } from "../helpers/mock-console";
56
import { useMockIsTTY } from "../helpers/mock-istty";
@@ -71,7 +72,11 @@ describe("time-travel", () => {
7172
})
7273
);
7374
await expect(
74-
throwIfDatabaseIsAlpha("1701", "d5b1d127-xxxx-xxxx-xxxx-cbc69f0a9e06")
75+
throwIfDatabaseIsAlpha(
76+
COMPLIANCE_REGION_CONFIG_UNKNOWN,
77+
"1701",
78+
"d5b1d127-xxxx-xxxx-xxxx-cbc69f0a9e06"
79+
)
7580
).rejects.toThrowError(
7681
"Time travel is not available for alpha D1 databases. You will need to migrate to a new database for access to this feature."
7782
);
@@ -107,6 +112,7 @@ describe("time-travel", () => {
107112
})
108113
);
109114
const result = await throwIfDatabaseIsAlpha(
115+
COMPLIANCE_REGION_CONFIG_UNKNOWN,
110116
"1701",
111117
"d5b1d127-xxxx-xxxx-xxxx-cbc69f0a9e06"
112118
);

packages/wrangler/src/__tests__/deploy.test.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,8 @@ describe("deploy", () => {
482482
Please ensure it has the correct permissions for this operation.
483483
484484
Getting User settings...
485-
ℹ️ The API Token is read from the CLOUDFLARE_API_TOKEN in your environment.
486485
👋 You are logged in with an API Token, associated with the email [email protected].
486+
ℹ️ The API Token is read from the CLOUDFLARE_API_TOKEN in your environment.
487487
┌─┬─┐
488488
│ Account Name │ Account ID │
489489
├─┼─┤
@@ -12866,6 +12866,73 @@ export default{
1286612866
`);
1286712867
});
1286812868
});
12869+
12870+
describe("compliance region support", () => {
12871+
it("should upload to the public region by default", async () => {
12872+
writeWranglerConfig({});
12873+
writeWorkerSource();
12874+
mockUploadWorkerRequest({
12875+
expectedBaseUrl: "api.cloudflare.com",
12876+
});
12877+
mockSubDomainRequest();
12878+
mockGetWorkerSubdomain({ enabled: true });
12879+
12880+
await runWrangler("deploy ./index.js");
12881+
});
12882+
12883+
it("should upload to the FedRAMP High region if set in config", async () => {
12884+
writeWranglerConfig({
12885+
compliance_region: "fedramp_high",
12886+
});
12887+
writeWorkerSource();
12888+
mockUploadWorkerRequest({
12889+
expectedBaseUrl: "api.fed.cloudflare.com",
12890+
});
12891+
mockSubDomainRequest();
12892+
mockGetWorkerSubdomain({ enabled: true });
12893+
12894+
await runWrangler("deploy ./index.js");
12895+
});
12896+
12897+
it("should upload to the FedRAMP High region if set in an env var", async () => {
12898+
vi.stubEnv("CLOUDFLARE_COMPLIANCE_REGION", "fedramp_high");
12899+
writeWranglerConfig({});
12900+
writeWorkerSource();
12901+
mockUploadWorkerRequest({
12902+
expectedBaseUrl: "api.fed.cloudflare.com",
12903+
});
12904+
mockSubDomainRequest();
12905+
mockGetWorkerSubdomain({ enabled: true });
12906+
12907+
await runWrangler("deploy ./index.js");
12908+
});
12909+
12910+
it("should error if the region is set in both env var and configured, and they conflict", async () => {
12911+
vi.stubEnv("CLOUDFLARE_COMPLIANCE_REGION", "public");
12912+
writeWranglerConfig({ compliance_region: "fedramp_high" });
12913+
writeWorkerSource();
12914+
12915+
await expect(runWrangler("deploy ./index.js")).rejects
12916+
.toThrowErrorMatchingInlineSnapshot(`
12917+
[Error: The compliance region has been set to different values in two places:
12918+
- \`CLOUDFLARE_COMPLIANCE_REGION\` environment variable: \`public\`
12919+
- \`compliance_region\` configuration property: \`fedramp_high\`]
12920+
`);
12921+
});
12922+
12923+
it("should not error if the region is set in both env var and configured, and they are the same", async () => {
12924+
vi.stubEnv("CLOUDFLARE_COMPLIANCE_REGION", "fedramp_high");
12925+
writeWranglerConfig({ compliance_region: "fedramp_high" });
12926+
writeWorkerSource();
12927+
mockUploadWorkerRequest({
12928+
expectedBaseUrl: "api.fed.cloudflare.com",
12929+
});
12930+
mockSubDomainRequest();
12931+
mockGetWorkerSubdomain({ enabled: true });
12932+
12933+
await runWrangler("deploy ./index.js");
12934+
});
12935+
});
1286912936
});
1287012937

1287112938
/** Write mock assets to the file system so they can be uploaded. */

0 commit comments

Comments
 (0)