Skip to content

Commit ee2f184

Browse files
Merge pull request #19792 from Budibase/merge-back/cloud-c61e3c8e7481
Merge Cloud changes back to master
2 parents 0f267b0 + c61e3c8 commit ee2f184

2 files changed

Lines changed: 130 additions & 2 deletions

File tree

‎packages/server/src/integrations/mongodb.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,17 @@ export class MongoIntegration implements IntegrationBase {
386386
}
387387

388388
async connect() {
389+
const { tlsCAFile, tlsCertificateKeyFile, tlsCRLFile } = this.client.options
390+
if (
391+
!environment.SELF_HOSTED &&
392+
(tlsCAFile !== undefined ||
393+
tlsCertificateKeyFile !== undefined ||
394+
tlsCRLFile !== undefined)
395+
) {
396+
throw new Error(
397+
"MongoDB TLS file options are only supported on self-hosted installations"
398+
)
399+
}
389400
return this.client.connect()
390401
}
391402

‎packages/server/src/integrations/tests/mongodb.spec.ts‎

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
import { buildMongoClientOptions, MongoDBConfig } from "../mongodb"
1+
import { promises as fs } from "fs"
2+
import { MongoClient } from "mongodb"
3+
import {
4+
buildMongoClientOptions,
5+
MongoIntegration,
6+
type MongoDBConfig,
7+
} from "../mongodb"
28
import { withEnv } from "../../environment"
39

410
describe("MongoDB Integration", () => {
511
const baseConfig: MongoDBConfig = {
6-
connectionString: "mongodb://localhost:27017",
12+
connectionString: "mongodb://example.com:27017",
713
db: "test",
814
tlsCertificateKeyFile: "/etc/passwd",
915
tlsCAFile: "/etc/shadow",
@@ -25,4 +31,115 @@ describe("MongoDB Integration", () => {
2531
})
2632
})
2733
})
34+
35+
describe("connection string TLS file options", () => {
36+
const fileOptions = ["tlsCAFile", "tlsCertificateKeyFile", "tlsCRLFile"]
37+
const policyError =
38+
"MongoDB TLS file options are only supported on self-hosted installations"
39+
40+
it.each([
41+
{ name: "mongodb with TLS", scheme: "mongodb", tls: "tls=true&" },
42+
{ name: "mongodb with SSL alias", scheme: "mongodb", tls: "ssl=true&" },
43+
{ name: "mongodb+srv with TLS", scheme: "mongodb+srv", tls: "tls=true&" },
44+
{ name: "mongodb+srv with implicit TLS", scheme: "mongodb+srv", tls: "" },
45+
])(
46+
"rejects normalized $name URI file options on cloud",
47+
async ({ scheme, tls }) => {
48+
await withEnv({ SELF_HOSTED: undefined }, async () => {
49+
for (const option of fileOptions) {
50+
const encoded = [...option]
51+
.map(
52+
character =>
53+
`%${character.charCodeAt(0).toString(16).padStart(2, "0")}`
54+
)
55+
.join("")
56+
for (const key of [option, option.toUpperCase(), encoded]) {
57+
const integration = new MongoIntegration({
58+
...baseConfig,
59+
connectionString: `${scheme}://example.com/test?${tls}${key}=/file.pem`,
60+
})
61+
await expect(integration.connect()).rejects.toThrow(policyError)
62+
}
63+
}
64+
})
65+
}
66+
)
67+
68+
it("returns the usual verification failure without reading a file or connecting", async () => {
69+
const readFile = jest.spyOn(fs, "readFile")
70+
const connect = jest.spyOn(MongoClient.prototype, "connect")
71+
try {
72+
await withEnv({ SELF_HOSTED: undefined }, async () => {
73+
const integration = new MongoIntegration({
74+
...baseConfig,
75+
connectionString:
76+
"mongodb://example.com/?tls=true&tlsCertificateKeyFile=/file.pem",
77+
})
78+
await expect(integration.testConnection()).resolves.toEqual({
79+
connected: false,
80+
error: policyError,
81+
})
82+
})
83+
expect(readFile).not.toHaveBeenCalled()
84+
expect(connect).not.toHaveBeenCalled()
85+
} finally {
86+
readFile.mockRestore()
87+
connect.mockRestore()
88+
}
89+
})
90+
91+
it("preserves ordinary cloud URI settings", async () => {
92+
await withEnv({ SELF_HOSTED: undefined }, async () => {
93+
const integration = new MongoIntegration({
94+
...baseConfig,
95+
connectionString:
96+
"mongodb://example.com/test?tls=true&replicaSet=rs0&appName=tlsCAFile%3D%2Ffile.pem&readPreference=secondary",
97+
})
98+
99+
expect(integration["client"].options).toMatchObject({
100+
tls: true,
101+
dbName: "test",
102+
replicaSet: "rs0",
103+
appName: "tlsCAFile=/file.pem",
104+
readPreference: { mode: "secondary" },
105+
})
106+
expect(integration["client"].options.tlsCAFile).toBeUndefined()
107+
expect(
108+
integration["client"].options.tlsCertificateKeyFile
109+
).toBeUndefined()
110+
expect(integration["client"].options.tlsCRLFile).toBeUndefined()
111+
112+
const connect = jest
113+
.spyOn(integration["client"], "connect")
114+
.mockResolvedValue(integration["client"])
115+
await integration.connect()
116+
expect(connect).toHaveBeenCalledTimes(1)
117+
})
118+
})
119+
120+
it("preserves self-hosted URI TLS file options", async () => {
121+
await withEnv({ SELF_HOSTED: "true" }, async () => {
122+
const integration = new MongoIntegration({
123+
...baseConfig,
124+
tlsCAFile: "",
125+
tlsCertificateKeyFile: "",
126+
connectionString:
127+
"mongodb://example.com/?tls=true&TLSCAFILE=/ca.pem&%74lsCertificateKeyFile=/key.pem&TlScRlFiLe=/crl.pem",
128+
})
129+
130+
expect(integration["client"].options).toMatchObject({
131+
tls: true,
132+
tlsCAFile: "/ca.pem",
133+
tlsCertificateKeyFile: "/key.pem",
134+
tlsCRLFile: "/crl.pem",
135+
})
136+
137+
const connect = jest
138+
.spyOn(integration["client"], "connect")
139+
.mockResolvedValue(integration["client"])
140+
await integration.connect()
141+
expect(connect).toHaveBeenCalledTimes(1)
142+
})
143+
})
144+
})
28145
})

0 commit comments

Comments
 (0)