diff --git a/.changeset/every-wolves-roll.md b/.changeset/every-wolves-roll.md new file mode 100644 index 000000000000..c1f85f0e8d6d --- /dev/null +++ b/.changeset/every-wolves-roll.md @@ -0,0 +1,5 @@ +--- +"wrangler": minor +--- + +add sslmode to hyperdrive and update mtls flags diff --git a/packages/wrangler/src/__tests__/hyperdrive.test.ts b/packages/wrangler/src/__tests__/hyperdrive.test.ts index 020ea99ecb68..94a68875acf1 100644 --- a/packages/wrangler/src/__tests__/hyperdrive.test.ts +++ b/packages/wrangler/src/__tests__/hyperdrive.test.ts @@ -625,16 +625,17 @@ describe("hyperdrive commands", () => { `); }); - it("should create a hyperdrive with mtls config", async () => { + it("should successfully create a hyperdrive with mtls config and sslmode=verify-full", async () => { const reqProm = mockHyperdriveCreate(); await runWrangler( - "hyperdrive create test123 --host=example.com --database=neondb --user=test --password=password --port=1234 --ca-certificate-uuid=12345 --mtls-certificate-uuid=1234" + "hyperdrive create test123 --host=example.com --database=neondb --user=test --password=password --port=1234 --ca-certificate-id=12345 --mtls-certificate-id=1234 --sslmode=verify-full" ); await expect(reqProm).resolves.toMatchInlineSnapshot(` Object { "mtls": Object { - "ca_certificate_uuid": "12345", - "mtls_certificate_uuid": "1234", + "ca_certificate_id": "12345", + "mtls_certificate_id": "1234", + "sslmode": "verify-full", }, "name": "test123", "origin": Object { @@ -663,20 +664,112 @@ describe("hyperdrive commands", () => { `); }); + it("should successfully create a hyperdrive with mtls config and sslmode=require", async () => { + const reqProm = mockHyperdriveCreate(); + await runWrangler( + "hyperdrive create test123 --host=example.com --database=neondb --user=test --password=password --port=1234 --mtls-certificate-id=1234 --sslmode=require" + ); + await expect(reqProm).resolves.toMatchInlineSnapshot(` + Object { + "mtls": Object { + "mtls_certificate_id": "1234", + "sslmode": "require", + }, + "name": "test123", + "origin": Object { + "database": "neondb", + "host": "example.com", + "password": "password", + "port": 1234, + "scheme": "postgresql", + "user": "test", + }, + } + `); + expect(std.out).toMatchInlineSnapshot(` + "🚧 Creating 'test123' + ✅ Created new Hyperdrive PostgreSQL config: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + 📋 To start using your config from a Worker, add the following binding configuration to your Wrangler configuration file: + + { + \\"hyperdrive\\": [ + { + \\"binding\\": \\"HYPERDRIVE\\", + \\"id\\": \\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\\" + } + ] + }" + `); + }); + + it("should error on create hyperdrive with mtls config sslmode=require and CA flag set", async () => { + await expect(() => + runWrangler( + "hyperdrive create test123 --host=example.com --database=neondb --user=test --password=password --port=1234 --ca-certificate-id=1234 --sslmode=require" + ) + ).rejects.toThrow(); + expect(std.err).toMatchInlineSnapshot(` + "X [ERROR] CA not allowed when sslmode = 'require' is set + + " + `); + }); + + it("should error on create hyperdrive with mtls config sslmode=verify-ca missing CA", async () => { + await expect(() => + runWrangler( + "hyperdrive create test123 --host=example.com --database=neondb --user=test --password=password --port=1234 --mtls-certificate-id=1234 --sslmode=verify-ca" + ) + ).rejects.toThrow(); + expect(std.err).toMatchInlineSnapshot(` + "X [ERROR] CA required when sslmode = 'verify-ca' or 'verify-full' is set + + " + `); + }); + + it("should error on create hyperdrive with mtls config sslmode=verify-full missing CA", async () => { + await expect(() => + runWrangler( + "hyperdrive create test123 --host=example.com --database=neondb --user=test --password=password --port=1234 --mtls-certificate-id=1234 --sslmode=verify-full" + ) + ).rejects.toThrow(); + expect(std.err).toMatchInlineSnapshot(` + "X [ERROR] CA required when sslmode = 'verify-ca' or 'verify-full' is set + + " + `); + }); + + it("should error on create hyperdrive with mtls config sslmode=random", async () => { + await expect(() => + runWrangler( + "hyperdrive create test123 --host=example.com --database=neondb --user=test --password=password --port=1234 --mtls-certificate-id=1234 --sslmode=random" + ) + ).rejects.toThrow(); + expect(std.err).toMatchInlineSnapshot(` + "X [ERROR] Invalid values: + + Argument: sslmode, Given: \\"random\\", Choices: \\"require\\", \\"verify-ca\\", \\"verify-full\\" + + " + `); + }); + it("should handle listing configs", async () => { mockHyperdriveGetListOrDelete(); await runWrangler("hyperdrive list"); expect(std.out).toMatchInlineSnapshot(` "📋 Listing Hyperdrive configs - ┌──────────────────────────────────────┬─────────────┬─────────┬────────────────┬──────┬────────────┬───────────┬──────────┬───────────────────────────────────────────────────────────────┐ - │ id │ name │ user │ host │ port │ scheme │ database │ caching │ mtls │ - ├──────────────────────────────────────┼─────────────┼─────────┼────────────────┼──────┼────────────┼───────────┼──────────┼───────────────────────────────────────────────────────────────┤ - │ xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx │ test123 │ test │ example.com │ 5432 │ PostgreSQL │ neondb │ enabled │ │ - ├──────────────────────────────────────┼─────────────┼─────────┼────────────────┼──────┼────────────┼───────────┼──────────┼───────────────────────────────────────────────────────────────┤ - │ yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy │ new-db │ dbuser │ www.google.com │ 3211 │ PostgreSQL │ mydb │ disabled │ │ - ├──────────────────────────────────────┼─────────────┼─────────┼────────────────┼──────┼────────────┼───────────┼──────────┼───────────────────────────────────────────────────────────────┤ - │ zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz │ new-db-mtls │ pg-mtls │ www.mtls.com │ 3212 │ │ mydb-mtls │ enabled │ {\\"ca_certificate_uuid\\":\\"1234\\",\\"mtls_certificate_uuid\\":\\"1234\\"} │ - └──────────────────────────────────────┴─────────────┴─────────┴────────────────┴──────┴────────────┴───────────┴──────────┴───────────────────────────────────────────────────────────────┘" + ┌──────────────────────────────────────┬─────────────┬─────────┬────────────────┬──────┬────────────┬───────────┬──────────┬───────────────────────────────────────────────────────────────────────────────────┐ + │ id │ name │ user │ host │ port │ scheme │ database │ caching │ mtls │ + ├──────────────────────────────────────┼─────────────┼─────────┼────────────────┼──────┼────────────┼───────────┼──────────┼───────────────────────────────────────────────────────────────────────────────────┤ + │ xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx │ test123 │ test │ example.com │ 5432 │ PostgreSQL │ neondb │ enabled │ │ + ├──────────────────────────────────────┼─────────────┼─────────┼────────────────┼──────┼────────────┼───────────┼──────────┼───────────────────────────────────────────────────────────────────────────────────┤ + │ yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy │ new-db │ dbuser │ www.google.com │ 3211 │ PostgreSQL │ mydb │ disabled │ │ + ├──────────────────────────────────────┼─────────────┼─────────┼────────────────┼──────┼────────────┼───────────┼──────────┼───────────────────────────────────────────────────────────────────────────────────┤ + │ zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz │ new-db-mtls │ pg-mtls │ www.mtls.com │ 3212 │ │ mydb-mtls │ enabled │ {\\"ca_certificate_id\\":\\"1234\\",\\"mtls_certificate_id\\":\\"1234\\",\\"sslmode\\":\\"verify-full\\"} │ + └──────────────────────────────────────┴─────────────┴─────────┴────────────────┴──────┴────────────┴───────────┴──────────┴───────────────────────────────────────────────────────────────────────────────────┘" `); }); @@ -985,13 +1078,14 @@ describe("hyperdrive commands", () => { it("should handle updating a hyperdrive config's mtls configuration", async () => { const reqProm = mockHyperdriveUpdate(); await runWrangler( - "hyperdrive update xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --ca-certificate-uuid=2345 --mtls-certificate-uuid=234" + "hyperdrive update xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --ca-certificate-id=2345 --mtls-certificate-id=234 --sslmode=verify-full" ); await expect(reqProm).resolves.toMatchInlineSnapshot(` Object { "mtls": Object { - "ca_certificate_uuid": "2345", - "mtls_certificate_uuid": "234", + "ca_certificate_id": "2345", + "mtls_certificate_id": "234", + "sslmode": "verify-full", }, } `); @@ -1009,8 +1103,9 @@ describe("hyperdrive commands", () => { \\"user\\": \\"test\\" }, \\"mtls\\": { - \\"ca_certificate_uuid\\": \\"2345\\", - \\"mtls_certificate_uuid\\": \\"234\\" + \\"ca_certificate_id\\": \\"2345\\", + \\"mtls_certificate_id\\": \\"234\\", + \\"sslmode\\": \\"verify-full\\" } }" `); @@ -1078,8 +1173,9 @@ function mockHyperdriveGetListOrDelete() { scheme: "pg-mtls", }, mtls: { - ca_certificate_uuid: "1234", - mtls_certificate_uuid: "1234", + ca_certificate_id: "1234", + mtls_certificate_id: "1234", + sslmode: "verify-full", }, }, ], @@ -1131,8 +1227,8 @@ function mockHyperdriveUpdate(): Promise { } const mtls = defaultConfig.mtls; if (mtls && reqBody.mtls) { - mtls.ca_certificate_uuid = reqBody.mtls.ca_certificate_uuid; - mtls.mtls_certificate_uuid = reqBody.mtls.mtls_certificate_uuid; + mtls.ca_certificate_id = reqBody.mtls.ca_certificate_id; + mtls.mtls_certificate_id = reqBody.mtls.mtls_certificate_id; } return HttpResponse.json( diff --git a/packages/wrangler/src/hyperdrive/client.ts b/packages/wrangler/src/hyperdrive/client.ts index ccfed5479c50..c7086e177e85 100644 --- a/packages/wrangler/src/hyperdrive/client.ts +++ b/packages/wrangler/src/hyperdrive/client.ts @@ -85,10 +85,13 @@ export type PatchHyperdriveBody = { }; export type Mtls = { - ca_certificate_uuid?: string; - mtls_certificate_uuid?: string; + ca_certificate_id?: string; + mtls_certificate_id?: string; + sslmode?: string; }; +export const Sslmode = ["require", "verify-ca", "verify-full"]; + export async function createConfig( config: Config, body: CreateUpdateHyperdriveBody diff --git a/packages/wrangler/src/hyperdrive/index.ts b/packages/wrangler/src/hyperdrive/index.ts index bf87e067dbfd..d5ac4ee2350f 100644 --- a/packages/wrangler/src/hyperdrive/index.ts +++ b/packages/wrangler/src/hyperdrive/index.ts @@ -117,16 +117,23 @@ export function upsertOptions(yargs: Argv) { describe: "Indicates the number of seconds cache may serve the response after it becomes stale, cannot be set when caching is disabled", }, - "ca-certificate-uuid": { + "ca-certificate-id": { + alias: "ca-certificate-uuid", type: "string", describe: "Sets custom CA certificate when connecting to origin database. Must be valid UUID of already uploaded CA certificate.", }, - "mtls-certificate-uuid": { + "mtls-certificate-id": { + alias: "mtls-certificate-uuid", type: "string", describe: "Sets custom mTLS client certificates when connecting to origin database. Must be valid UUID of already uploaded public/private key certificates.", }, + sslmode: { + type: "string", + choices: ["require", "verify-ca", "verify-full"], + describe: "Sets CA sslmode for connecting to database.", + }, }) .group( ["connection-string"], @@ -321,13 +328,26 @@ export function getMtlsFromArgs( args: StrictYargsOptionsToInterface ): Mtls | undefined { const mtls = { - ca_certificate_uuid: args.caCertificateUuid, - mtls_certificate_uuid: args.mtlsCertificateUuid, + ca_certificate_id: args.caCertificateId, + mtls_certificate_id: args.mtlsCertificateId, + sslmode: args.sslmode, }; if (JSON.stringify(mtls) === "{}") { return undefined; } else { + if (mtls.sslmode == "require" && mtls.ca_certificate_id?.trim()) { + throw new UserError("CA not allowed when sslmode = 'require' is set"); + } + + if ( + (mtls.sslmode == "verify-ca" || mtls.sslmode == "verify-full") && + !mtls.ca_certificate_id?.trim() + ) { + throw new UserError( + "CA required when sslmode = 'verify-ca' or 'verify-full' is set" + ); + } return mtls; } }