Skip to content

Commit e7b690b

Browse files
authored
Delegate HTTPS handling to Miniflare (#11449)
* Delegate HTTPS handling to Miniflare * Create spotty-phones-pull.md * fix lint * fix test * fix lint * fix lint
1 parent 5e68e76 commit e7b690b

File tree

8 files changed

+71
-345
lines changed

8 files changed

+71
-345
lines changed

.changeset/spotty-phones-pull.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Delegate generation of HTTPS certificates to Miniflare

packages/miniflare/src/http/cert.ts

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,21 @@ mCWw+vbGTBwIr/9X1S4UL1/f3zDICC7YSA==
3434
// emailAddress = optional
3535

3636
// [ req_distinguished_name ]
37-
// countryName = US
37+
// countryName = Country Name
3838
// countryName_default = US
3939
// countryName_min = 2
4040
// countryName_max = 2
41-
// stateOrProvinceName = Texas
41+
// stateOrProvinceName = State or Province Name
4242
// stateOrProvinceName_default = Texas
43-
// localityName = Austin
44-
// localityName_default = Austin ## This is the default value
45-
// 0.organizationName = Cloudflare ## Print this message
46-
// 0.organizationName_default = Cloudflare ## This is the default value
47-
// organizationalUnitName = Workers ## Print this message
48-
// organizationalUnitName_default = Workers## This is the default value
49-
// commonName = localhost
43+
// localityName = Locality
44+
// localityName_default = Austin
45+
// 0.organizationName = Organization Name
46+
// 0.organizationName_default = Cloudflare
47+
// organizationalUnitName = Organizational Unit Name
48+
// organizationalUnitName_default = Workers
49+
// commonName = Common Name
5050
// commonName_max = 64
51-
// emailAddress = [email protected]
51+
// emailAddress = Email Address
5252
// emailAddress_max = 64
5353

5454
// [ v3_ca ]
@@ -58,21 +58,38 @@ mCWw+vbGTBwIr/9X1S4UL1/f3zDICC7YSA==
5858
// nsComment = "OpenSSL Generated Certificate"
5959
// keyUsage = keyCertSign,digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment
6060
// extendedKeyUsage = serverAuth,clientAuth,codeSigning,timeStamping
61+
// subjectAltName = @alt_names
62+
63+
// [alt_names]
64+
// DNS.0 = localhost
65+
// IP.1 = 127.0.0.1
66+
67+
// Interactive answers:
68+
// Country Name [US]:
69+
// State or Province Name [Texas]:
70+
// Locality [Austin]:
71+
// Organization Name [Cloudflare]:
72+
// Organizational Unit Name [Workers]:
73+
// Common Name []:localhost
74+
// Email Address []:wrangler@cloudflare.com
6175
export const CERT = `
6276
-----BEGIN CERTIFICATE-----
63-
MIICcDCCAhegAwIBAgIUE97EcbEWw3YZMN/ucGBSzJ/5qA4wCgYIKoZIzj0EAwIw
64-
VTELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMQ8wDQYDVQQHDAZBdXN0aW4x
65-
EzARBgNVBAoMCkNsb3VkZmxhcmUxEDAOBgNVBAsMB1dvcmtlcnMwIBcNMjMwNjIy
66-
MTg1ODQ3WhgPMjEyMzA1MjkxODU4NDdaMFUxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI
67-
DAVUZXhhczEPMA0GA1UEBwwGQXVzdGluMRMwEQYDVQQKDApDbG91ZGZsYXJlMRAw
68-
DgYDVQQLDAdXb3JrZXJzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtrIEgzog
69-
jrUHIvB4qgjg/cT7blhWuLUfSUp6H62NCo21NrVWgPtCmCWw+vbGTBwIr/9X1S4U
70-
L1/f3zDICC7YSKOBwjCBvzAdBgNVHQ4EFgQUSXahTksi00c6KhUECHIY4FLW7Sow
71-
HwYDVR0jBBgwFoAUSXahTksi00c6KhUECHIY4FLW7SowDwYDVR0TAQH/BAUwAwEB
72-
/zAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUw
73-
CwYDVR0PBAQDAgL0MDEGA1UdJQQqMCgGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYB
74-
BQUHAwMGCCsGAQUFBwMIMAoGCCqGSM49BAMCA0cAMEQCIE2qnXbKTHQ8wtwI+9XR
75-
h4ivDyz7w7iGxn3+ccmj/CQqAiApdX/Iz/jGRzi04xFlE4GoPVG/zaMi64ckmIpE
76-
ez/dHA==
77+
MIIDBzCCAq2gAwIBAgIUaEibZTawMcz6xQ/0rGNlEBKwkUowCgYIKoZIzj0EAwIw
78+
gZExCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVzdGlu
79+
MRMwEQYDVQQKDApDbG91ZGZsYXJlMRAwDgYDVQQLDAdXb3JrZXJzMRIwEAYDVQQD
80+
DAlsb2NhbGhvc3QxJjAkBgkqhkiG9w0BCQEWF3dyYW5nbGVyQGNsb3VkZmxhcmUu
81+
Y29tMCAXDTI1MTAwMjEzMzQ1MloYDzIxMjUwOTA4MTMzNDUyWjCBkTELMAkGA1UE
82+
BhMCVVMxDjAMBgNVBAgMBVRleGFzMQ8wDQYDVQQHDAZBdXN0aW4xEzARBgNVBAoM
83+
CkNsb3VkZmxhcmUxEDAOBgNVBAsMB1dvcmtlcnMxEjAQBgNVBAMMCWxvY2FsaG9z
84+
dDEmMCQGCSqGSIb3DQEJARYXd3JhbmdsZXJAY2xvdWRmbGFyZS5jb20wWTATBgcq
85+
hkjOPQIBBggqhkjOPQMBBwNCAAS2sgSDOiCOtQci8HiqCOD9xPtuWFa4tR9JSnof
86+
rY0KjbU2tVaA+0KYJbD69sZMHAiv/1fVLhQvX9/fMMgILthIo4HeMIHbMB0GA1Ud
87+
DgQWBBRJdqFOSyLTRzoqFQQIchjgUtbtKjAfBgNVHSMEGDAWgBRJdqFOSyLTRzoq
88+
FQQIchjgUtbtKjAPBgNVHRMBAf8EBTADAQH/MCwGCWCGSAGG+EIBDQQfFh1PcGVu
89+
U1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTALBgNVHQ8EBAMCAvQwMQYDVR0lBCow
90+
KAYIKwYBBQUHAwEGCCsGAQUFBwMCBggrBgEFBQcDAwYIKwYBBQUHAwgwGgYDVR0R
91+
BBMwEYIJbG9jYWxob3N0hwR/AAABMAoGCCqGSM49BAMCA0gAMEUCIQDNxEiZc6Q6
92+
8hK0q3y/9lDWc+dHr74gAnBHVJZEo5uyRQIgW6eL31hH7qouqUi9+efWU1N85n0z
93+
X3kip4YDAFo8ozE=
7794
-----END CERTIFICATE-----
7895
`;

packages/wrangler/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@
148148
"prompts": "^2.4.2",
149149
"resolve": "^1.22.8",
150150
"rimraf": "catalog:default",
151-
"selfsigned": "^2.0.1",
152151
"semiver": "^1.1.0",
153152
"shell-quote": "^1.8.1",
154153
"signal-exit": "^3.0.7",
Lines changed: 18 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -1,179 +1,36 @@
11
import * as fs from "node:fs";
2-
import os from "node:os";
3-
import path from "node:path";
4-
import { getGlobalWranglerConfigPath } from "@cloudflare/workers-utils";
5-
import { getHttpsOptions } from "../https-options";
6-
import { mockConsoleMethods } from "./helpers/mock-console";
2+
import { validateHttpsOptions } from "../https-options";
73
import { runInTempDir } from "./helpers/run-in-tmp";
84

9-
vi.mock("node:fs", async (importOriginal) => {
10-
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
11-
const fsOriginal = await importOriginal<typeof import("node:fs")>();
12-
return { ...fsOriginal };
13-
});
14-
15-
describe("getHttpsOptions()", () => {
5+
describe("validateHttpsOptions()", () => {
166
runInTempDir();
17-
const std = mockConsoleMethods();
187

19-
it("should use cached values if they have not expired", async () => {
20-
fs.mkdirSync(path.resolve(getGlobalWranglerConfigPath(), "local-cert"), {
21-
recursive: true,
22-
});
23-
fs.writeFileSync(
24-
path.resolve(getGlobalWranglerConfigPath(), "local-cert/key.pem"),
25-
"PRIVATE KEY"
26-
);
27-
fs.writeFileSync(
28-
path.resolve(getGlobalWranglerConfigPath(), "local-cert/cert.pem"),
29-
"PUBLIC KEY"
30-
);
31-
const result = await getHttpsOptions();
32-
expect(result.key).toEqual("PRIVATE KEY");
33-
expect(result.cert).toEqual("PUBLIC KEY");
34-
expect(std.out).toMatchInlineSnapshot(`""`);
35-
expect(std.warn).toMatchInlineSnapshot(`""`);
36-
expect(std.err).toMatchInlineSnapshot(`""`);
37-
});
38-
39-
it("should generate and cache new keys if none are cached", async () => {
40-
const result = await getHttpsOptions();
41-
const key = fs.readFileSync(
42-
path.resolve(getGlobalWranglerConfigPath(), "local-cert/key.pem"),
43-
"utf8"
44-
);
45-
const cert = fs.readFileSync(
46-
path.resolve(getGlobalWranglerConfigPath(), "local-cert/cert.pem"),
47-
"utf8"
48-
);
49-
expect(result.key).toEqual(key);
50-
expect(result.cert).toEqual(cert);
51-
expect(std.out).toMatchInlineSnapshot(
52-
`"Generating new self-signed certificate..."`
53-
);
54-
expect(std.warn).toMatchInlineSnapshot(`""`);
55-
expect(std.err).toMatchInlineSnapshot(`""`);
56-
});
57-
58-
it("should generate and cache new keys if cached files have expired", async () => {
59-
fs.mkdirSync(path.resolve(getGlobalWranglerConfigPath(), "local-cert"), {
60-
recursive: true,
61-
});
62-
const ORIGINAL_KEY = "EXPIRED PRIVATE KEY";
63-
const ORIGINAL_CERT = "EXPIRED PUBLIC KEY";
64-
65-
const old = new Date(2000);
66-
fs.writeFileSync(
67-
path.resolve(getGlobalWranglerConfigPath(), "local-cert/key.pem"),
68-
ORIGINAL_KEY
69-
);
70-
fs.utimesSync(
71-
path.resolve(getGlobalWranglerConfigPath(), "local-cert/key.pem"),
72-
old,
73-
old
74-
);
75-
fs.writeFileSync(
76-
path.resolve(getGlobalWranglerConfigPath(), "local-cert/cert.pem"),
77-
ORIGINAL_CERT
78-
);
79-
fs.utimesSync(
80-
path.resolve(getGlobalWranglerConfigPath(), "local-cert/cert.pem"),
81-
old,
82-
old
83-
);
84-
85-
const result = await getHttpsOptions();
86-
const key = fs.readFileSync(
87-
path.resolve(getGlobalWranglerConfigPath(), "local-cert/key.pem"),
88-
"utf8"
89-
);
90-
const cert = fs.readFileSync(
91-
path.resolve(getGlobalWranglerConfigPath(), "local-cert/cert.pem"),
92-
"utf8"
93-
);
94-
expect(key).not.toEqual(ORIGINAL_KEY);
95-
expect(cert).not.toEqual(ORIGINAL_CERT);
96-
expect(result.key).toEqual(key);
97-
expect(result.cert).toEqual(cert);
98-
expect(std.out).toMatchInlineSnapshot(
99-
`"Generating new self-signed certificate..."`
100-
);
101-
expect(std.warn).toMatchInlineSnapshot(`""`);
102-
expect(std.err).toMatchInlineSnapshot(`""`);
103-
});
104-
105-
it("should warn if not able to write to the cache (legacy config path)", async () => {
106-
fs.mkdirSync(path.join(os.homedir(), ".wrangler"));
107-
await mockWriteFileSyncThrow(/\.pem$/);
108-
await getHttpsOptions();
109-
expect(
110-
fs.existsSync(
111-
path.resolve(getGlobalWranglerConfigPath(), "local-cert/key.pem")
112-
)
113-
).toBe(false);
114-
expect(
115-
fs.existsSync(
116-
path.resolve(getGlobalWranglerConfigPath(), "local-cert/cert.pem")
117-
)
118-
).toBe(false);
119-
expect(std.out).toMatchInlineSnapshot(
120-
`"Generating new self-signed certificate..."`
121-
);
122-
expect(std.warn).toMatchInlineSnapshot(`
123-
"▲ [WARNING] Unable to cache generated self-signed certificate in home/.wrangler/local-cert.
124-
125-
ERROR: Cannot write file
126-
127-
"
128-
`);
129-
expect(std.err).toMatchInlineSnapshot(`""`);
130-
fs.rmSync(path.join(os.homedir(), ".wrangler"), { recursive: true });
131-
});
132-
133-
it("should warn if not able to write to the cache", async () => {
134-
await mockWriteFileSyncThrow(/\.pem$/);
135-
136-
await getHttpsOptions();
137-
expect(
138-
fs.existsSync(
139-
path.resolve(getGlobalWranglerConfigPath(), "local-cert/key.pem")
140-
)
141-
).toBe(false);
142-
expect(
143-
fs.existsSync(
144-
path.resolve(getGlobalWranglerConfigPath(), "local-cert/cert.pem")
145-
)
146-
).toBe(false);
147-
expect(std.out).toMatchInlineSnapshot(
148-
`"Generating new self-signed certificate..."`
149-
);
150-
expect(std.warn).toMatchInlineSnapshot(`
151-
"▲ [WARNING] Unable to cache generated self-signed certificate in home/.config/.wrangler/local-cert.
152-
153-
ERROR: Cannot write file
154-
155-
"
156-
`);
157-
expect(std.err).toMatchInlineSnapshot(`""`);
8+
it("should return undefined if nothing is passed in", async () => {
9+
const result = await validateHttpsOptions();
10+
expect(result).toBeUndefined();
15811
});
15912

16013
it("should read the certs from the paths if provided", async () => {
16114
fs.mkdirSync("./certs");
16215
await fs.promises.writeFile("./certs/test.key", "xxxxx");
16316
await fs.promises.writeFile("./certs/test.pem", "yyyyy");
164-
const options = getHttpsOptions("./certs/test.key", "./certs/test.pem");
17+
const options = validateHttpsOptions(
18+
"./certs/test.key",
19+
"./certs/test.pem"
20+
);
21+
assert(options);
16522
expect(options.key).toEqual("xxxxx");
16623
expect(options.cert).toEqual("yyyyy");
16724
});
16825

16926
it("should error if only one of the two paths is provided", async () => {
17027
expect(() =>
171-
getHttpsOptions("./certs/test.key", undefined)
28+
validateHttpsOptions("./certs/test.key", undefined)
17229
).toThrowErrorMatchingInlineSnapshot(
17330
`[Error: Must specify both certificate path and key path to use a Custom Certificate.]`
17431
);
17532
expect(() =>
176-
getHttpsOptions(undefined, "./certs/test.pem")
33+
validateHttpsOptions(undefined, "./certs/test.pem")
17734
).toThrowErrorMatchingInlineSnapshot(
17835
`[Error: Must specify both certificate path and key path to use a Custom Certificate.]`
17936
);
@@ -183,7 +40,7 @@ describe("getHttpsOptions()", () => {
18340
fs.mkdirSync("./certs");
18441
await fs.promises.writeFile("./certs/test.pem", "yyyyy");
18542
expect(() =>
186-
getHttpsOptions("./certs/test.key", "./certs/test.pem")
43+
validateHttpsOptions("./certs/test.key", "./certs/test.pem")
18744
).toThrowErrorMatchingInlineSnapshot(
18845
`[Error: Missing Custom Certificate Key at ./certs/test.key]`
18946
);
@@ -193,7 +50,7 @@ describe("getHttpsOptions()", () => {
19350
fs.mkdirSync("./certs");
19451
await fs.promises.writeFile("./certs/test.key", "xxxxx");
19552
expect(() =>
196-
getHttpsOptions("./certs/test.key", "./certs/test.pem")
53+
validateHttpsOptions("./certs/test.key", "./certs/test.pem")
19754
).toThrowErrorMatchingInlineSnapshot(
19855
`[Error: Missing Custom Certificate File at ./certs/test.pem]`
19956
);
@@ -205,7 +62,8 @@ describe("getHttpsOptions()", () => {
20562
await fs.promises.writeFile("./certs/test.pem", "yyyyy");
20663
vi.stubEnv("WRANGLER_HTTPS_KEY_PATH", "./certs/test.key");
20764
vi.stubEnv("WRANGLER_HTTPS_CERT_PATH", "./certs/test.pem");
208-
const options = getHttpsOptions();
65+
const options = validateHttpsOptions();
66+
assert(options);
20967
expect(options.key).toEqual("xxxxx");
21068
expect(options.cert).toEqual("yyyyy");
21169
});
@@ -218,26 +76,12 @@ describe("getHttpsOptions()", () => {
21876
await fs.promises.writeFile("./certs/test-env.pem", "yyyyy-env");
21977
vi.stubEnv("WRANGLER_HTTPS_KEY_PATH", "./certs/test-env.key");
22078
vi.stubEnv("WRANGLER_HTTPS_CERT_PATH", "./certs/test-env.pem");
221-
const options = getHttpsOptions(
79+
const options = validateHttpsOptions(
22280
"./certs/test-param.key",
22381
"./certs/test-param.pem"
22482
);
83+
assert(options);
22584
expect(options.key).toEqual("xxxxx-param");
22685
expect(options.cert).toEqual("yyyyy-param");
22786
});
22887
});
229-
230-
async function mockWriteFileSyncThrow(matcher: RegExp) {
231-
const originalWriteFileSync =
232-
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
233-
(await vi.importActual<typeof import("node:fs")>("node:fs")).writeFileSync;
234-
vi.spyOn(fs, "writeFileSync").mockImplementation(
235-
(filePath, data, options) => {
236-
if (matcher.test(filePath.toString())) {
237-
throw new Error("ERROR: Cannot write file");
238-
} else {
239-
return originalWriteFileSync(filePath, data, options);
240-
}
241-
}
242-
);
243-
}

packages/wrangler/src/api/startDevWorker/ProxyController.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
handleStructuredLogs,
1717
WranglerLog,
1818
} from "../../dev/miniflare";
19-
import { getHttpsOptions } from "../../https-options";
19+
import { validateHttpsOptions } from "../../https-options";
2020
import { logger } from "../../logger";
2121
import { getSourceMappedStack } from "../../sourcemap";
2222
import { Controller } from "./BaseController";
@@ -67,7 +67,7 @@ export class ProxyController extends Controller {
6767
(this.inspectorEnabled &&
6868
this.latestConfig.dev?.inspector &&
6969
this.latestConfig.dev?.inspector?.secure)
70-
? getHttpsOptions(
70+
? validateHttpsOptions(
7171
this.latestConfig.dev.server?.httpsKeyPath,
7272
this.latestConfig.dev.server?.httpsCertPath
7373
)

0 commit comments

Comments
 (0)