Skip to content

Commit

Permalink
Support setting custom HTTPS certification paths from environment var…
Browse files Browse the repository at this point in the history
…iables (#6479)

* test: add http-options tests for providing explicit paths to files

* feat: allow HTTPS custom certificate paths to be provided by a environment variables

As well as providing paths to custom HTTPS certificate files, it is now possible to use WRANGLER_HTTPS_KEY_PATH and WRANGLER_HTTPS_CERT_PATH environment variables.

Specifying the file paths at the command line overrides specifying in environment variables.

Fixes #5997

* test: use `vi.stubEnv()` rather than manually hacking `process.env` in Wrangler tests

This helper from Vitest will automatically revert to the original process.env values after each test.
  • Loading branch information
petebacondarwin committed Aug 14, 2024
1 parent 0e36f89 commit 3c24d84
Show file tree
Hide file tree
Showing 24 changed files with 277 additions and 337 deletions.
11 changes: 11 additions & 0 deletions .changeset/tall-lions-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"wrangler": minor
---

feat: allow HTTPS custom certificate paths to be provided by a environment variables

As well as providing paths to custom HTTPS certificate files, it is now possible to use WRANGLER_HTTPS_KEY_PATH and WRANGLER_HTTPS_CERT_PATH environment variables.

Specifying the file paths at the command line overrides specifying in environment variables.

Fixes #5997
6 changes: 1 addition & 5 deletions packages/wrangler/src/__tests__/configuration.pages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,14 @@ describe("normalizeAndValidateConfig()", () => {
);

// supress Hyperdrive beta warnings
process.env.NO_HYPERDRIVE_WARNING = "true";
vi.stubEnv("NO_HYPERDRIVE_WARNING", "true");

// sanity checks
expect(pagesRawConfig.env).not.toBeUndefined();
expect(pagesRawConfig.env?.preview).not.toBeUndefined();
expect(pagesRawConfig.env?.production).not.toBeUndefined();
});

afterEach(() => {
process.env.NO_HYPERDRIVE_WARNING = undefined;
});

describe("named environments", () => {
it("should return config corresponding to the top-level environment, if no named environment is provided", () => {
const { config, diagnostics } = normalizeAndValidateConfig(
Expand Down
20 changes: 8 additions & 12 deletions packages/wrangler/src/__tests__/configuration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3346,20 +3346,16 @@ describe("normalizeAndValidateConfig()", () => {
});

it("should not provide an unsafe warning when the environment variable is specified", () => {
try {
process.env.WRANGLER_DISABLE_EXPERIMENTAL_WARNING = "1";
vi.stubEnv("WRANGLER_DISABLE_EXPERIMENTAL_WARNING", "1");

const { diagnostics } = normalizeAndValidateConfig(
{ unsafe: { bindings: [] } } as unknown as RawConfig,
undefined,
{ env: undefined }
);
const { diagnostics } = normalizeAndValidateConfig(
{ unsafe: { bindings: [] } } as unknown as RawConfig,
undefined,
{ env: undefined }
);

expect(diagnostics.hasWarnings()).toBe(false);
expect(diagnostics.hasErrors()).toBe(false);
} finally {
delete process.env.WRANGLER_DISABLE_EXPERIMENTAL_WARNING;
}
expect(diagnostics.hasWarnings()).toBe(false);
expect(diagnostics.hasErrors()).toBe(false);
});
});

Expand Down
3 changes: 1 addition & 2 deletions packages/wrangler/src/__tests__/d1/migrate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,7 @@ Your database may not be available to serve requests during the migration, conti
});

it("should try to read D1 config from wrangler.toml when logged in", async () => {
// no need to clear this env var as it's implicitly cleared by mockApiToken in afterEach
process.env.CLOUDFLARE_API_TOKEN = "api-token";
vi.stubEnv("CLOUDFLARE_API_TOKEN", "api-token");
reinitialiseAuthTokens();
setIsTTY(false);
writeWranglerToml();
Expand Down
70 changes: 18 additions & 52 deletions packages/wrangler/src/__tests__/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,17 +442,8 @@ describe("deploy", () => {
});

describe("non-TTY", () => {
const ENV_COPY = process.env;

afterEach(() => {
process.env = ENV_COPY;
});

it("should not throw an error in non-TTY if 'CLOUDFLARE_API_TOKEN' & 'account_id' are in scope", async () => {
process.env = {
...process.env,
CLOUDFLARE_API_TOKEN: "123456789",
};
vi.stubEnv("CLOUDFLARE_API_TOKEN", "123456789");
setIsTTY(false);
writeWranglerToml({
account_id: "some-account-id",
Expand Down Expand Up @@ -480,11 +471,8 @@ describe("deploy", () => {
});

it("should not throw an error if 'CLOUDFLARE_ACCOUNT_ID' & 'CLOUDFLARE_API_TOKEN' are in scope", async () => {
process.env = {
...process.env,
CLOUDFLARE_API_TOKEN: "hunter2",
CLOUDFLARE_ACCOUNT_ID: "some-account-id",
};
vi.stubEnv("CLOUDFLARE_API_TOKEN", "hunter2");
vi.stubEnv("CLOUDFLARE_ACCOUNT_ID", "some-account-id");
setIsTTY(false);
writeWranglerToml();
writeWorkerSource();
Expand Down Expand Up @@ -512,11 +500,8 @@ describe("deploy", () => {

it("should throw an error in non-TTY & there is more than one account associated with API token", async () => {
setIsTTY(false);
process.env = {
...process.env,
CLOUDFLARE_API_TOKEN: "hunter2",
CLOUDFLARE_ACCOUNT_ID: undefined,
};
vi.stubEnv("CLOUDFLARE_API_TOKEN", "hunter2");
vi.stubEnv("CLOUDFLARE_ACCOUNT_ID", "");
writeWranglerToml({
account_id: undefined,
});
Expand Down Expand Up @@ -544,11 +529,8 @@ describe("deploy", () => {
writeWranglerToml({
account_id: undefined,
});
process.env = {
...process.env,
CLOUDFLARE_API_TOKEN: undefined,
CLOUDFLARE_ACCOUNT_ID: "badwolf",
};
vi.stubEnv("CLOUDFLARE_API_TOKEN", "");
vi.stubEnv("CLOUDFLARE_ACCOUNT_ID", "badwolf");
writeWorkerSource();
mockSubDomainRequest();
mockUploadWorkerRequest();
Expand All @@ -571,11 +553,8 @@ describe("deploy", () => {
writeWranglerToml({
account_id: undefined,
});
process.env = {
...process.env,
CLOUDFLARE_API_TOKEN: "picard",
CLOUDFLARE_ACCOUNT_ID: undefined,
};
vi.stubEnv("CLOUDFLARE_API_TOKEN", "picard");
vi.stubEnv("CLOUDFLARE_ACCOUNT_ID", "");
writeWorkerSource();
mockSubDomainRequest();
mockUploadWorkerRequest();
Expand Down Expand Up @@ -8878,14 +8857,10 @@ addEventListener('fetch', event => {});`
});

describe("inject process.env.NODE_ENV", () => {
let actualProcessEnvNodeEnv: string | undefined;
beforeEach(() => {
actualProcessEnvNodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = "some-node-env";
});
afterEach(() => {
process.env.NODE_ENV = actualProcessEnvNodeEnv;
vi.stubEnv("NODE_ENV", "some-node-env");
});

it("should replace `process.env.NODE_ENV` in scripts", async () => {
writeWranglerToml();
fs.writeFileSync(
Expand Down Expand Up @@ -9325,7 +9300,7 @@ export default{
};
export class SomeClass {};`
);
process.env.CLOUDFLARE_ACCOUNT_ID = "";
vi.stubEnv("CLOUDFLARE_ACCOUNT_ID", "");
await runWrangler("deploy index.js --dry-run");
expect(std).toMatchInlineSnapshot(`
Object {
Expand Down Expand Up @@ -10967,11 +10942,8 @@ export default{

describe("--keep-vars", () => {
it("should send keepVars when keep-vars is passed in", async () => {
process.env = {
...process.env,
CLOUDFLARE_API_TOKEN: "hunter2",
CLOUDFLARE_ACCOUNT_ID: "some-account-id",
};
vi.stubEnv("CLOUDFLARE_API_TOKEN", "hunter2");
vi.stubEnv("CLOUDFLARE_ACCOUNT_ID", "some-account-id");
setIsTTY(false);
writeWranglerToml();
writeWorkerSource();
Expand All @@ -10998,11 +10970,8 @@ export default{
});

it("should not send keepVars by default", async () => {
process.env = {
...process.env,
CLOUDFLARE_API_TOKEN: "hunter2",
CLOUDFLARE_ACCOUNT_ID: "some-account-id",
};
vi.stubEnv("CLOUDFLARE_API_TOKEN", "hunter2");
vi.stubEnv("CLOUDFLARE_ACCOUNT_ID", "some-account-id");
setIsTTY(false);
writeWranglerToml();
writeWorkerSource();
Expand All @@ -11029,11 +10998,8 @@ export default{
});

it("should send keepVars when `keep_vars = true`", async () => {
process.env = {
...process.env,
CLOUDFLARE_API_TOKEN: "hunter2",
CLOUDFLARE_ACCOUNT_ID: "some-account-id",
};
vi.stubEnv("CLOUDFLARE_API_TOKEN", "hunter2");
vi.stubEnv("CLOUDFLARE_ACCOUNT_ID", "some-account-id");
setIsTTY(false);
writeWranglerToml({
keep_vars: true,
Expand Down
3 changes: 2 additions & 1 deletion packages/wrangler/src/__tests__/dev.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -754,8 +754,9 @@ describe("wrangler dev", () => {
});

// We won't overwrite existing process.env keys with .env values (to
// allow .env overrides to specified on then shell), so make sure this
// allow .env overrides to be specified on the shell), so make sure this
// key definitely doesn't exist.
vi.stubEnv("CUSTOM_BUILD_VAR", "");
delete process.env.CUSTOM_BUILD_VAR;
});

Expand Down
14 changes: 4 additions & 10 deletions packages/wrangler/src/__tests__/helpers/mock-account-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,18 @@ import { reinitialiseAuthTokens } from "../../user";
export function mockApiToken({
apiToken = "some-api-token",
}: { apiToken?: string | null } = {}) {
const ORIGINAL_CLOUDFLARE_API_TOKEN = process.env.CLOUDFLARE_API_TOKEN;
beforeEach(() => {
if (apiToken === null) {
// stubEnv doesn't support removing env vars
// So we fake it by initially stubbing it with an empty string and then deleting it
vi.stubEnv("CLOUDFLARE_API_TOKEN", "");
delete process.env.CLOUDFLARE_API_TOKEN;
} else {
process.env.CLOUDFLARE_API_TOKEN = apiToken;
vi.stubEnv("CLOUDFLARE_API_TOKEN", apiToken);
}
// Now we have updated the environment, we must reinitialize the user auth state.
reinitialiseAuthTokens();
});
afterEach(() => {
if (ORIGINAL_CLOUDFLARE_API_TOKEN === undefined) {
// `process.env`'s assigned property values are coerced to strings
delete process.env.CLOUDFLARE_API_TOKEN;
} else {
process.env.CLOUDFLARE_API_TOKEN = ORIGINAL_CLOUDFLARE_API_TOKEN;
}
});
}

/**
Expand Down
69 changes: 69 additions & 0 deletions packages/wrangler/src/__tests__/https-options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,75 @@ describe("getHttpsOptions()", () => {
`);
expect(std.err).toMatchInlineSnapshot(`""`);
});

it("should read the certs from the paths if provided", async () => {
fs.mkdirSync("./certs");
await fs.promises.writeFile("./certs/test.key", "xxxxx");
await fs.promises.writeFile("./certs/test.pem", "yyyyy");
const options = getHttpsOptions("./certs/test.key", "./certs/test.pem");
expect(options.key).toEqual("xxxxx");
expect(options.cert).toEqual("yyyyy");
});

it("should error if only one of the two paths is provided", async () => {
expect(() =>
getHttpsOptions("./certs/test.key", undefined)
).toThrowErrorMatchingInlineSnapshot(
`[Error: Must specify both certificate path and key path to use a Custom Certificate.]`
);
expect(() =>
getHttpsOptions(undefined, "./certs/test.pem")
).toThrowErrorMatchingInlineSnapshot(
`[Error: Must specify both certificate path and key path to use a Custom Certificate.]`
);
});

it("should error if the key file does not exist", async () => {
fs.mkdirSync("./certs");
await fs.promises.writeFile("./certs/test.pem", "yyyyy");
expect(() =>
getHttpsOptions("./certs/test.key", "./certs/test.pem")
).toThrowErrorMatchingInlineSnapshot(
`[Error: Missing Custom Certificate Key at ./certs/test.key]`
);
});

it("should error if the cert file does not exist", async () => {
fs.mkdirSync("./certs");
await fs.promises.writeFile("./certs/test.key", "xxxxx");
expect(() =>
getHttpsOptions("./certs/test.key", "./certs/test.pem")
).toThrowErrorMatchingInlineSnapshot(
`[Error: Missing Custom Certificate File at ./certs/test.pem]`
);
});

it("should read the certs from the paths in env vars", async () => {
fs.mkdirSync("./certs");
await fs.promises.writeFile("./certs/test.key", "xxxxx");
await fs.promises.writeFile("./certs/test.pem", "yyyyy");
vi.stubEnv("WRANGLER_HTTPS_KEY_PATH", "./certs/test.key");
vi.stubEnv("WRANGLER_HTTPS_CERT_PATH", "./certs/test.pem");
const options = getHttpsOptions();
expect(options.key).toEqual("xxxxx");
expect(options.cert).toEqual("yyyyy");
});

it("should read the certs from the param paths rather than paths in env vars", async () => {
fs.mkdirSync("./certs");
await fs.promises.writeFile("./certs/test-param.key", "xxxxx-param");
await fs.promises.writeFile("./certs/test-param.pem", "yyyyy-param");
await fs.promises.writeFile("./certs/test-env.key", "xxxxx-env");
await fs.promises.writeFile("./certs/test-env.pem", "yyyyy-env");
vi.stubEnv("WRANGLER_HTTPS_KEY_PATH", "./certs/test-env.key");
vi.stubEnv("WRANGLER_HTTPS_CERT_PATH", "./certs/test-env.pem");
const options = getHttpsOptions(
"./certs/test-param.key",
"./certs/test-param.pem"
);
expect(options.key).toEqual("xxxxx-param");
expect(options.cert).toEqual("yyyyy-param");
});
});

async function mockWriteFileSyncThrow(matcher: RegExp) {
Expand Down
11 changes: 1 addition & 10 deletions packages/wrangler/src/__tests__/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,8 @@ describe("init", () => {
});

describe("with custom C3 command", () => {
const ORIGINAL_ENV = process.env;

beforeEach(() => {
process.env = {
...ORIGINAL_ENV,
WRANGLER_C3_COMMAND: "run create-cloudflare",
};
});

afterEach(() => {
process.env = ORIGINAL_ENV;
vi.stubEnv("WRANGLER_C3_COMMAND", "run create-cloudflare");
});

test("shows deprecation message and delegates to C3", async () => {
Expand Down
13 changes: 5 additions & 8 deletions packages/wrangler/src/__tests__/logger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,7 @@ describe("logger", () => {

describe("loggerLevelFromEnvVar=error", () => {
beforeEach(() => {
process.env.WRANGLER_LOG = "error";
});
afterEach(() => {
process.env.WRANGLER_LOG = undefined;
vi.stubEnv("WRANGLER_LOG", "error");
});

it("should render messages that are at or above the log level set in the env var", () => {
Expand All @@ -146,10 +143,10 @@ describe("logger", () => {

describe("loggerLevelFromEnvVar case-insensitive", () => {
beforeEach(() => {
process.env.WRANGLER_LOG = "wARn";
vi.stubEnv("WRANGLER_LOG", "wARn");
});
afterEach(() => {
process.env.WRANGLER_LOG = undefined;
vi.stubEnv("WRANGLER_LOG", "");
});

it("should render messages that are at or above the log level set in the env var", () => {
Expand All @@ -176,10 +173,10 @@ describe("logger", () => {

describe("loggerLevelFromEnvVar falls back to log on invalid level", () => {
beforeEach(() => {
process.env.WRANGLER_LOG = "everything";
vi.stubEnv("WRANGLER_LOG", "everything");
});
afterEach(() => {
process.env.WRANGLER_LOG = undefined;
vi.stubEnv("WRANGLER_LOG", "");
});

it("should render messages that are at or above the log level set in the env var", () => {
Expand Down
Loading

0 comments on commit 3c24d84

Please sign in to comment.