mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "色",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "颜色",
|
||||
|
||||
@@ -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.`;
|
||||
}
|
||||
|
||||
@@ -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/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user