Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/every-wolves-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

add sslmode to hyperdrive and update mtls flags
140 changes: 118 additions & 22 deletions packages/wrangler/src/__tests__/hyperdrive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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\\"} │
└──────────────────────────────────────┴─────────────┴─────────┴────────────────┴──────┴────────────┴───────────┴──────────┴───────────────────────────────────────────────────────────────────────────────────┘"
`);
});

Expand Down Expand Up @@ -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",
},
}
`);
Expand All @@ -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\\"
}
}"
`);
Expand Down Expand Up @@ -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",
},
},
],
Expand Down Expand Up @@ -1131,8 +1227,8 @@ function mockHyperdriveUpdate(): Promise<PatchHyperdriveBody> {
}
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(
Expand Down
7 changes: 5 additions & 2 deletions packages/wrangler/src/hyperdrive/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 24 additions & 4 deletions packages/wrangler/src/hyperdrive/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,23 @@ export function upsertOptions<T>(yargs: Argv<T>) {
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"],
Expand Down Expand Up @@ -321,13 +328,26 @@ export function getMtlsFromArgs(
args: StrictYargsOptionsToInterface<typeof upsertOptions>
): 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;
}
}
Loading