feat(mongodb): add TLS connection options in connection tab (#2515)

Add MongoDB TLS options (`tlsAllowInvalidCertificates`, `retryWrites`, `tlsCAFile`) to the connection form with backend URL normalization and DocumentDB/SSH tunnel guidance, while preserving legacy `tlsCAFile` in `url_params` for existing DocumentDB users, hardening auth failure hint matching, and clarifying TLS bypass security labels.
This commit is contained in:
gggdsg
2026-07-03 19:25:01 +08:00
committed by GitHub
parent f826cb5d38
commit b60f8b2039
11 changed files with 270 additions and 6 deletions
@@ -30,7 +30,7 @@ import { h2ConnectionModeForConfig, h2FileJdbcUrlWithPath, h2FilePathFromJdbcUrl
import { firstZooKeeperEndpoint, normalizeZooKeeperConnectString } from "@/lib/zookeeperConnection";
import { isLocalFileTypeDb } from "@/lib/connectionFile";
import { MQ_PINNED_VERSION_OPTIONS, pinnedVersionToSelection, selectionToPinnedVersion } from "@/lib/mqPinnedVersionOptions";
import { mongodbAuthFailureHint, mongoUrlParam, setMongoUrlParam } from "@/lib/mongoConnectionOptions";
import { mongodbAuthFailureHint, mongoUrlParam, mongoUrlParamIsTrue, normalizeMongoTlsFormState, setMongoUrlParam, setMongoUrlParamBoolean } from "@/lib/mongoConnectionOptions";
import { mysqlCleartextPasswordAuthEnabled, setMysqlCleartextPasswordAuthEnabled } from "@/lib/mysqlConnectionOptions";
import { copyToClipboard } from "@/lib/clipboard";
import { agentDriverInstallKey, appendAgentDriverUpdateHint, hasAgentDriverUpdate, showAgentDriverInstallHint, type AgentDriverInstallState } from "@/lib/agentDriverInstallHint";
@@ -1786,6 +1786,20 @@ const mongoAuthMechanism = computed({
form.value.url_params = setMongoUrlParam(form.value.url_params, "authMechanism", value === "default" ? "" : value);
},
});
const mongoTlsAllowInvalidCertificates = computed({
get: () => mongoUrlParamIsTrue(form.value.url_params, "tlsAllowInvalidCertificates"),
set: (value: boolean) => {
let next = setMongoUrlParamBoolean(form.value.url_params, "tlsAllowInvalidCertificates", value);
next = setMongoUrlParam(next, "tlsAllowInvalidHostnames", "");
form.value.url_params = next;
},
});
const mongoRetryWrites = computed({
get: () => mongoUrlParamIsTrue(form.value.url_params, "retryWrites", true),
set: (value: boolean) => {
form.value.url_params = setMongoUrlParamBoolean(form.value.url_params, "retryWrites", value, true);
},
});
const mongoDriverMode = computed({
get: () => (form.value.driver_profile === "mongodb-legacy" ? "legacy" : "auto"),
set: (value: string) => {
@@ -2058,6 +2072,11 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
config.driver_profile = "mongodb";
config.driver_label = "MongoDB";
}
if (config.db_type === "mongodb") {
const mongoTls = normalizeMongoTlsFormState(!!config.ssl, config.url_params, config.ca_cert_path);
config.url_params = mongoTls.urlParams;
config.ca_cert_path = mongoTls.caCertPath;
}
if (config.db_type !== "oracle") {
config.sysdba = undefined;
config.oracle_connection_type = undefined;
@@ -2140,7 +2159,7 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
config.client_cert_path = undefined;
config.client_key_path = undefined;
}
if (config.db_type !== "mysql" && config.db_type !== "clickhouse" && config.db_type !== "etcd" && config.db_type !== "starrocks") {
if (config.db_type !== "mysql" && config.db_type !== "clickhouse" && config.db_type !== "etcd" && config.db_type !== "starrocks" && config.db_type !== "mongodb") {
config.ca_cert_path = undefined;
} else {
config.ca_cert_path = config.ca_cert_path?.trim() || "";
@@ -3984,6 +4003,40 @@ function openExternalUrl(url: string) {
<span>{{ t("connection.sslEnable") }}</span>
</label>
</div>
<template v-if="form.ssl">
<div class="grid grid-cols-4 items-start gap-4">
<Label :class="connectionLabelClass">{{ t("connection.mongoTlsAllowInvalidCertificates") }}</Label>
<label class="col-span-3 flex items-start gap-2 cursor-pointer">
<input v-model="mongoTlsAllowInvalidCertificates" type="checkbox" class="mr-0 mt-0.5" />
<span class="text-xs leading-5 text-muted-foreground">
{{ t("connection.mongoTlsAllowInvalidCertificatesHint") }}
</span>
</label>
</div>
<div class="grid grid-cols-4 items-start gap-4">
<Label :class="connectionLabelClass">{{ t("connection.mongoRetryWrites") }}</Label>
<label class="col-span-3 flex items-start gap-2 cursor-pointer">
<input v-model="mongoRetryWrites" type="checkbox" class="mr-0 mt-0.5" />
<span class="text-xs leading-5 text-muted-foreground">
{{ t("connection.mongoRetryWritesHint") }}
</span>
</label>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label :class="connectionLabelClass">{{ t("connection.caCertPath") }}</Label>
<div class="col-span-3 flex items-center gap-1">
<Input v-model="form.ca_cert_path" class="flex-1" :placeholder="t('connection.caCertPathPlaceholder')" />
<Tooltip v-if="isDesktop">
<TooltipTrigger as-child>
<Button variant="outline" size="icon" class="h-9 w-9 shrink-0" @click="browseCaCertPath">
<FolderOpen class="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>{{ t("connection.caCertPathBrowse") }}</TooltipContent>
</Tooltip>
</div>
</div>
</template>
<div class="grid grid-cols-4 items-center gap-4">
<Label :class="connectionLabelClass">{{ t("connection.user") }}</Label>
<Input v-model="form.username" class="col-span-3" />
@@ -4015,7 +4068,7 @@ function openExternalUrl(url: string) {
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label :class="connectionLabelClass">{{ t("connection.urlParams") }}</Label>
<Input v-model="form.url_params" class="col-span-3" placeholder="authSource=admin&authMechanism=SCRAM-SHA-1" />
<Input v-model="form.url_params" class="col-span-3" placeholder="replicaSet=rs0&authSource=admin" />
</div>
</template>
</template>
+4
View File
@@ -385,6 +385,10 @@ export default {
dmCompatHint: "Requires DM8 ODBC driver installed on your system.",
dmDownload: "Download from Dameng",
mongoLegacyHint: "Use Legacy for older MongoDB servers, especially versions below 4.2. If authentication fails and the user was created in admin, set Auth DB to admin.",
mongoTlsAllowInvalidCertificates: "Disable TLS certificate verification",
mongoTlsAllowInvalidCertificatesHint: "Maps to tlsAllowInvalidCertificates=true and disables all certificate validation, not just hostname checks. This increases MITM risk. Commonly required for AWS DocumentDB over an SSH tunnel.",
mongoRetryWrites: "Retry writes",
mongoRetryWritesHint: "Maps to retryWrites. Disable for AWS DocumentDB, which does not support retryable writes.",
compatible: "Compatible",
mainstream: "Popular",
color: "Color",
+4
View File
@@ -388,6 +388,10 @@ export default withEnglishFallback({
dmCompatHint: "Requiere el driver ODBC DM8 instalado en el sistema.",
dmDownload: "Descargar desde Dameng",
mongoLegacyHint: "Usa Legacy para servidores MongoDB antiguos, especialmente versiones anteriores a 4.2. Si falla la autenticación y el usuario se creó en admin, define Auth DB como admin.",
mongoTlsAllowInvalidCertificates: "Desactivar verificación de certificado TLS",
mongoTlsAllowInvalidCertificatesHint: "Se asigna a tlsAllowInvalidCertificates=true y desactiva toda la validación de certificados, no solo la del nombre de host. Aumenta el riesgo de MITM. Suele ser necesario para AWS DocumentDB a través de un túnel SSH.",
mongoRetryWrites: "Reintentar escrituras",
mongoRetryWritesHint: "Se asigna a retryWrites. Desactívalo para AWS DocumentDB, que no admite escrituras reintentables.",
compatible: "Compatible",
mainstream: "Popular",
color: "Color",
+4
View File
@@ -386,6 +386,10 @@ export default withEnglishFallback({
dmCompatHint: "Richiede il driver ODBC DM8 installato sul sistema.",
dmDownload: "Scarica da Dameng",
mongoLegacyHint: "Usa Legacy per server MongoDB meno recenti, specialmente versioni precedenti alla 4.2. Se l'autenticazione fallisce e l'utente è stato creato in admin, imposta DB di Autenticazione su admin.",
mongoTlsAllowInvalidCertificates: "Disabilita verifica certificato TLS",
mongoTlsAllowInvalidCertificatesHint: "Corrisponde a tlsAllowInvalidCertificates=true e disabilita tutta la validazione del certificato, non solo il controllo dell'hostname. Aumenta il rischio MITM. Di solito è necessario per AWS DocumentDB tramite tunnel SSH.",
mongoRetryWrites: "Riprova scritture",
mongoRetryWritesHint: "Corrisponde a retryWrites. Disabilitalo per AWS DocumentDB, che non supporta scritture ripetibili.",
compatible: "Compatibile",
mainstream: "Popolare",
color: "Colore",
+4
View File
@@ -380,6 +380,10 @@ export default withEnglishFallback({
dmCompatHint: "システムにDM8 ODBCドライバーがインストールされている必要があります。",
dmDownload: "達夢からダウンロード",
mongoLegacyHint: "古いMongoDBサーバー(特に4.2未満)ではレガシーを使用してください。認証に失敗しユーザーがadminで作成された場合は、認証DBをadminに設定してください。",
mongoTlsAllowInvalidCertificates: "TLS 証明書検証を無効化",
mongoTlsAllowInvalidCertificatesHint: "tlsAllowInvalidCertificates=true に対応し、ホスト名だけでなくすべての証明書検証を無効化します。MITM 攻撃のリスクが高まります。SSH トンネル経由の AWS DocumentDB では通常必要です。",
mongoRetryWrites: "書き込み再試行",
mongoRetryWritesHint: "retryWrites に対応します。AWS DocumentDB は再試行可能な書き込みをサポートしないため、無効にしてください。",
compatible: "互換",
mainstream: "人気",
color: "色",
+4
View File
@@ -387,6 +387,10 @@ export default withEnglishFallback({
dmCompatHint: "Requer o driver ODBC DM8 instalado no seu sistema.",
dmDownload: "Baixar da Dameng",
mongoLegacyHint: "Use Legacy para servidores MongoDB antigos, especialmente versões anteriores à 4.2. Se a autenticação falhar e o usuário foi criado em admin, defina o BD de autenticação como admin.",
mongoTlsAllowInvalidCertificates: "Desativar verificação de certificado TLS",
mongoTlsAllowInvalidCertificatesHint: "Corresponde a tlsAllowInvalidCertificates=true e desativa toda a validação de certificados, não apenas a do hostname. Aumenta o risco de MITM. Geralmente é necessário para AWS DocumentDB via túnel SSH.",
mongoRetryWrites: "Repetir gravações",
mongoRetryWritesHint: "Corresponde a retryWrites. Desative para AWS DocumentDB, que não suporta gravações repetíveis.",
compatible: "Compatível",
mainstream: "Popular",
color: "Cor",
+4
View File
@@ -387,6 +387,10 @@ export default withEnglishFallback({
dmCompatHint: "需要在系统上安装达梦 DM8 ODBC 驱动程序。",
dmDownload: "前往达梦官网下载",
mongoLegacyHint: "旧版 MongoDB(尤其是 4.2 以下版本)请选择旧版兼容。若账号创建在 admin 且认证失败,请将认证库设为 admin。",
mongoTlsAllowInvalidCertificates: "禁用 TLS 证书校验",
mongoTlsAllowInvalidCertificatesHint: "对应 tlsAllowInvalidCertificates=true,会禁用全部证书校验(不仅是主机名),存在中间人攻击风险。经 SSH 隧道访问 AWS DocumentDB 时通常需要开启。",
mongoRetryWrites: "重试写入",
mongoRetryWritesHint: "对应 retryWrites。AWS DocumentDB 不支持可重试写入,请关闭。",
compatible: "兼容",
mainstream: "主流",
color: "颜色",
+4
View File
@@ -387,6 +387,10 @@ export default withEnglishFallback({
dmCompatHint: "需要在系統上安裝達夢 DM8 ODBC 驅動程式。",
dmDownload: "前往達夢官網下載",
mongoLegacyHint: "舊版 MongoDB(尤其是 4.2 以下版本)請選擇舊版相容。若帳號建立在 admin 且認證失敗,請將認證庫設為 admin。",
mongoTlsAllowInvalidCertificates: "停用 TLS 憑證校驗",
mongoTlsAllowInvalidCertificatesHint: "對應 tlsAllowInvalidCertificates=true,會停用全部憑證校驗(不只是主機名),存在中間人攻擊風險。經 SSH 隧道存取 AWS DocumentDB 時通常需要開啟。",
mongoRetryWrites: "重試寫入",
mongoRetryWritesHint: "對應 retryWrites。AWS DocumentDB 不支援可重試寫入,請關閉。",
compatible: "相容",
mainstream: "熱門",
color: "顏色",
@@ -14,7 +14,42 @@ export function setMongoUrlParam(urlParams: string | undefined, key: string, val
return params.toString();
}
export function mongoUrlParamIsTrue(urlParams: string | undefined, key: string, defaultValue = false): boolean {
const value = mongoUrlParam(urlParams, key);
if (!value) return defaultValue;
return ["true", "1", "yes"].includes(value.toLowerCase());
}
export function setMongoUrlParamBoolean(urlParams: string | undefined, key: string, value: boolean, defaultWhenEnabled = false): string {
if (value === defaultWhenEnabled) {
return setMongoUrlParam(urlParams, key, "");
}
return setMongoUrlParam(urlParams, key, value ? "true" : "false");
}
export function normalizeMongoTlsFormState(ssl: boolean, urlParams: string | undefined, caCertPath: string | undefined): { urlParams: string; caCertPath: string } {
if (ssl) {
const nextCaCertPath = caCertPath?.trim() || "";
let nextUrlParams = urlParams || "";
if (nextCaCertPath) {
nextUrlParams = setMongoUrlParam(nextUrlParams, "tlsCAFile", "");
}
return { urlParams: nextUrlParams, caCertPath: nextCaCertPath };
}
const next = setMongoUrlParam(setMongoUrlParam(setMongoUrlParam(urlParams, "tlsAllowInvalidCertificates", ""), "tlsAllowInvalidHostnames", ""), "tlsCAFile", "");
return { urlParams: next, caCertPath: "" };
}
export function mongodbAuthFailureHint(message: string): string {
const normalizedMessage = message.toLowerCase();
if (normalizedMessage.includes("tlsallowinvalidhostnames is an invalid option")) {
return `${message}\n\nDBX uses the Rust MongoDB driver (rustls), which does not accept tlsAllowInvalidHostnames in the connection string. Use tlsAllowInvalidCertificates=true instead. This is commonly required for AWS DocumentDB over an SSH tunnel because the local endpoint is 127.0.0.1 while the server certificate is issued for the DocumentDB hostname.`;
}
if (message.includes('certificate not valid for name "127.0.0.1"') || (message.includes("invalid peer certificate") && message.includes("127.0.0.1") && message.includes("docdb"))) {
return `${message}\n\nTLS hostname verification failed because the SSH tunnel connects to 127.0.0.1, but the DocumentDB certificate is issued for the cluster hostname. Add tlsAllowInvalidCertificates=true to URL params. Keep tlsCAFile configured so the certificate chain is still trusted when possible.`;
}
if (message.includes("cannot specify multiple seeds with directConnection=true")) {
return `${message}\n\nMongoDB directConnection=true can only be used with a single host. Remove directConnection=true when using a multi-host replica set URL, or keep only one reachable host if you need a direct connection.`;
}
+100 -2
View File
@@ -1110,7 +1110,9 @@ impl ConnectionConfig {
}
DatabaseType::Databend => normalize_bare_mysql_url_params(value),
DatabaseType::Postgres | DatabaseType::Redshift => normalize_postgres_url_params(value, self.ssl),
DatabaseType::MongoDb => normalize_mongo_url_params(value, self.ssl, !self.username.trim().is_empty()),
DatabaseType::MongoDb => {
normalize_mongo_url_params(value, self.ssl, !self.username.trim().is_empty(), self.ca_cert_path.trim())
}
_ => value.trim_start_matches('?').to_string(),
}
}
@@ -1270,7 +1272,7 @@ fn normalize_mysql_url_params(value: &str, force_tls: bool, accept_invalid_certs
parts.join("&")
}
fn normalize_mongo_url_params(value: &str, force_tls: bool, default_auth_source: bool) -> String {
fn normalize_mongo_url_params(value: &str, force_tls: bool, default_auth_source: bool, ca_cert_path: &str) -> String {
let value = value.trim_start_matches('?');
let mut parts: Vec<String> = value.split('&').filter(|part| !part.is_empty()).map(str::to_string).collect();
@@ -1279,6 +1281,29 @@ fn normalize_mongo_url_params(value: &str, force_tls: bool, default_auth_source:
parts.insert(0, "tls=true".to_string());
}
normalize_mongo_tls_compat_params(&mut parts);
if !force_tls {
parts.retain(|part| {
!url_param_key_is(part, "tlsAllowInvalidCertificates") && !url_param_key_is(part, "tlsCAFile")
});
}
let existing_tls_ca_file = if ca_cert_path.is_empty() {
parts.iter().find(|part| url_param_key_is(part, "tlsCAFile")).cloned()
} else {
None
};
parts.retain(|part| !url_param_key_is(part, "tlsCAFile"));
if force_tls {
if !ca_cert_path.is_empty() {
parts.push(format!("tlsCAFile={}", encode_mongo_tls_file_path(ca_cert_path)));
} else if let Some(part) = existing_tls_ca_file {
parts.push(part);
}
}
if default_auth_source && !parts.iter().any(|part| url_param_key_is(part, "authSource")) {
parts.push("authSource=admin".to_string());
}
@@ -1286,6 +1311,29 @@ fn normalize_mongo_url_params(value: &str, force_tls: bool, default_auth_source:
parts.join("&")
}
/// The Rust MongoDB driver uses rustls by default, which does not accept
/// `tlsAllowInvalidHostnames` in the connection string. Map it to the
/// supported `tlsAllowInvalidCertificates` option instead.
fn normalize_mongo_tls_compat_params(parts: &mut Vec<String>) {
let allow_invalid_hostnames =
parts.iter().any(|part| url_param_key_is(part, "tlsAllowInvalidHostnames") && mongo_url_param_is_truthy(part));
parts.retain(|part| !url_param_key_is(part, "tlsAllowInvalidHostnames"));
if allow_invalid_hostnames && !parts.iter().any(|part| url_param_key_is(part, "tlsAllowInvalidCertificates")) {
parts.push("tlsAllowInvalidCertificates=true".to_string());
}
}
fn mongo_url_param_is_truthy(part: &str) -> bool {
let Some((_, value)) = part.split_once('=') else {
return true;
};
matches!(percent_decode_str(value).decode_utf8_lossy().trim().to_ascii_lowercase().as_str(), "true" | "1" | "yes")
}
fn encode_mongo_tls_file_path(path: &str) -> String {
utf8_percent_encode(path, NON_ALPHANUMERIC).to_string()
}
fn mongo_uri_db_part_for_suffix<'a>(db_part: &'a str, suffix: &str) -> &'a str {
if db_part.is_empty() && !suffix.is_empty() {
"/"
@@ -2495,6 +2543,56 @@ mod tests {
assert_eq!(config.connection_url(), "mongodb://root:secret@10.1.2.3:17000/admin?tls=true&authSource=admin");
}
#[test]
fn mongodb_form_tls_ca_cert_adds_tls_ca_file_param() {
let mut config = mongodb_config("root", "secret", Some("admin"));
config.ssl = true;
config.ca_cert_path = "/tmp/mongo-ca.pem".to_string();
assert_eq!(
config.connection_url(),
"mongodb://root:secret@10.1.2.3:17000/admin?tls=true&tlsCAFile=%2Ftmp%2Fmongo%2Dca%2Epem&authSource=admin"
);
}
#[test]
fn mongodb_form_tls_cert_params_replace_existing_url_values() {
let mut config = mongodb_config("root", "secret", Some("admin"));
config.ssl = true;
config.ca_cert_path = "/tmp/new-ca.pem".to_string();
config.url_params = Some("tlsCAFile=%2Fold-ca.pem&authSource=admin".to_string());
assert_eq!(
config.connection_url(),
"mongodb://root:secret@10.1.2.3:17000/admin?tls=true&authSource=admin&tlsCAFile=%2Ftmp%2Fnew%2Dca%2Epem"
);
}
#[test]
fn mongodb_form_tls_preserves_legacy_tls_ca_file_in_url_params() {
let mut config = mongodb_config("root", "secret", Some("admin"));
config.ssl = true;
config.url_params = Some("tlsCAFile=%2Ftmp%2Flegacy-ca.pem&authSource=admin".to_string());
assert_eq!(
config.connection_url(),
"mongodb://root:secret@10.1.2.3:17000/admin?tls=true&authSource=admin&tlsCAFile=%2Ftmp%2Flegacy-ca.pem"
);
}
#[test]
fn mongodb_form_tls_allow_invalid_hostnames_maps_to_allow_invalid_certificates() {
let mut config = mongodb_config("root", "secret", Some("admin"));
config.ssl = true;
config.url_params = Some("replicaSet=rs0&tlsAllowInvalidHostnames=true".to_string());
assert_eq!(
config.connection_url(),
"mongodb://root:secret@10.1.2.3:17000/admin?tls=true&replicaSet=rs0&tlsAllowInvalidCertificates=true&authSource=admin"
);
assert!(!config.connection_url().contains("tlsAllowInvalidHostnames"));
}
#[test]
fn parse_mongo_first_host_replica_set() {
let uri = "mongodb://user:pass@host1:27017,host2:27017,host3:27017/admin?replicaSet=rs0";
@@ -1,6 +1,6 @@
import { strict as assert } from "node:assert";
import { test } from "vitest";
import { mongoUrlParam, setMongoUrlParam, mongodbAuthFailureHint } from "../../apps/desktop/src/lib/mongoConnectionOptions.ts";
import { mongoUrlParam, mongoUrlParamIsTrue, normalizeMongoTlsFormState, setMongoUrlParam, setMongoUrlParamBoolean, mongodbAuthFailureHint } from "../../apps/desktop/src/lib/mongoConnectionOptions.ts";
test("reads MongoDB authSource from URL params", () => {
assert.equal(mongoUrlParam("?replicaSet=rs0&authSource=admin", "authSource"), "admin");
@@ -14,6 +14,43 @@ test("removes empty MongoDB authSource from URL params", () => {
assert.equal(setMongoUrlParam("replicaSet=rs0&authSource=admin", "authSource", ""), "replicaSet=rs0");
});
test("reads MongoDB boolean URL params", () => {
assert.equal(mongoUrlParamIsTrue("tlsAllowInvalidCertificates=true", "tlsAllowInvalidCertificates"), true);
assert.equal(mongoUrlParamIsTrue("retryWrites=false", "retryWrites", true), false);
assert.equal(mongoUrlParamIsTrue("", "retryWrites", true), true);
});
test("sets MongoDB boolean URL params", () => {
assert.equal(setMongoUrlParamBoolean("replicaSet=rs0", "retryWrites", false, true), "replicaSet=rs0&retryWrites=false");
assert.equal(setMongoUrlParamBoolean("replicaSet=rs0&retryWrites=false", "retryWrites", true, true), "replicaSet=rs0");
assert.equal(setMongoUrlParamBoolean("", "tlsAllowInvalidCertificates", true), "tlsAllowInvalidCertificates=true");
});
test("clears MongoDB TLS form state when SSL is disabled", () => {
assert.deepEqual(
normalizeMongoTlsFormState(false, "replicaSet=rs0&tlsAllowInvalidCertificates=true&tlsAllowInvalidHostnames=true&tlsCAFile=%2Ftmp%2Fca.pem", "/tmp/ca.pem"),
{ urlParams: "replicaSet=rs0", caCertPath: "" },
);
assert.deepEqual(normalizeMongoTlsFormState(true, "replicaSet=rs0", " /tmp/ca.pem "), {
urlParams: "replicaSet=rs0",
caCertPath: "/tmp/ca.pem",
});
});
test("preserves MongoDB tlsCAFile in URL params when caCertPath is empty", () => {
assert.deepEqual(normalizeMongoTlsFormState(true, "replicaSet=rs0&tlsCAFile=%2Ftmp%2Flegacy-ca.pem", ""), {
urlParams: "replicaSet=rs0&tlsCAFile=%2Ftmp%2Flegacy-ca.pem",
caCertPath: "",
});
});
test("form caCertPath overrides tlsCAFile in URL params when set", () => {
assert.deepEqual(normalizeMongoTlsFormState(true, "replicaSet=rs0&tlsCAFile=%2Ftmp%2Flegacy-ca.pem", "/tmp/explicit-ca.pem"), {
urlParams: "replicaSet=rs0",
caCertPath: "/tmp/explicit-ca.pem",
});
});
test("adds a MongoDB authSource hint for legacy authentication failures", () => {
const message = "Agent RPC error: Exception authenticating MongoCredential{mechanism=SCRAM-SHA-1, userName='rwuser', source='gray_lite_twin_fat'}";
@@ -52,3 +89,16 @@ test("adds a MongoDB listDatabases permission hint", () => {
assert.match(mongodbAuthFailureHint(message), /does not have permission to run listDatabases/);
});
test("adds a MongoDB rustls hint for tlsAllowInvalidHostnames", () => {
const message = "MongoDB connection failed: Kind: An invalid argument was provided: tlsAllowInvalidHostnames is an invalid option";
assert.match(mongodbAuthFailureHint(message), /tlsAllowInvalidCertificates=true/);
});
test("adds a MongoDB DocumentDB SSH tunnel hostname hint", () => {
const message =
'MongoDB connection failed: Kind: Server selection timeout: invalid peer certificate: certificate not valid for name "127.0.0.1"; certificate is only valid for DnsName("docdb-2023-06-13-09-23-15.ctofbpmjm2lc.ap-northeast-1.docdb.amazonaws.com")';
assert.match(mongodbAuthFailureHint(message), /tlsAllowInvalidCertificates=true/);
});