fix(mysql): preserve JDBC TLS parameter semantics
This commit is contained in:
17 files changed
+428
-23
No files matched your search
@@ -4529,6 +4529,15 @@ function mysqlTlsModeFromParams(params: string | undefined, ssl: boolean | undef
|
||||
return "verify_identity";
|
||||
}
|
||||
|
||||
const jdbcUseSsl = getUrlParam(params, "useSSL").trim().toLowerCase();
|
||||
const jdbcRequireSsl = getUrlParam(params, "requireSSL").trim().toLowerCase();
|
||||
const jdbcVerifyServerCertificate = getUrlParam(params, "verifyServerCertificate").trim().toLowerCase();
|
||||
const isTrue = (value: string) => ["true", "1", "yes", "on"].includes(value);
|
||||
if (isTrue(jdbcVerifyServerCertificate) && (isTrue(jdbcUseSsl) || isTrue(jdbcRequireSsl))) return "verify_ca";
|
||||
if (isTrue(jdbcRequireSsl)) return "required";
|
||||
if (["false", "0", "no", "off"].includes(jdbcUseSsl)) return "disabled";
|
||||
if (isTrue(jdbcUseSsl)) return "preferred";
|
||||
|
||||
if (!ssl && getUrlParam(params, "require_ssl").toLowerCase() !== "true") return "disabled";
|
||||
if (getUrlParam(params, "verify_identity").toLowerCase() === "true") return "verify_identity";
|
||||
if (getUrlParam(params, "verify_ca").toLowerCase() === "true") return "verify_ca";
|
||||
@@ -4536,7 +4545,7 @@ function mysqlTlsModeFromParams(params: string | undefined, ssl: boolean | undef
|
||||
}
|
||||
|
||||
function applyMysqlTlsMode(params: string | undefined, mode: string): string {
|
||||
let next = deleteUrlParams(params, ["ssl-mode", "sslmode", "require_ssl", "verify_ca", "verify_identity"]);
|
||||
let next = deleteUrlParams(params, ["ssl-mode", "sslmode", "sslMode", "require_ssl", "verify_ca", "verify_identity", "useSSL", "requireSSL", "verifyServerCertificate"]);
|
||||
if (mode === "disabled") {
|
||||
return setUrlParam(next, "ssl-mode", "disabled");
|
||||
}
|
||||
|
||||
@@ -574,6 +574,7 @@ export default {
|
||||
mysqlClientKeyPlaceholder: "/path/to/client.key",
|
||||
mysqlClientCertHint: "Client certificate and private key must be provided together when MySQL requires mTLS.",
|
||||
mysqlTlsConnectionFailureHint: "If the MySQL server does not require TLS, edit the connection, set TLS Mode to Disabled, and try again.",
|
||||
mysqlUnsupportedCertVersionHint: "Strict verification does not support this server certificate version. Replace it with an X.509 v3 certificate, or use Required mode only on a trusted network.",
|
||||
mysqlClientCertBrowse: "Choose client certificate",
|
||||
mysqlClientKeyBrowse: "Choose client private key",
|
||||
postgresSslMode: "TLS Mode",
|
||||
|
||||
@@ -549,6 +549,7 @@ export default withEnglishFallback({
|
||||
mysqlClientKeyPlaceholder: "/ruta/a/client.key",
|
||||
mysqlClientCertHint: "El certificado y la clave privada deben indicarse juntos si MySQL requiere mTLS.",
|
||||
mysqlTlsConnectionFailureHint: "Si el servidor MySQL no requiere TLS, edita la conexión, establece el Modo TLS en Deshabilitado e inténtalo de nuevo.",
|
||||
mysqlUnsupportedCertVersionHint: "La verificación estricta no admite esta versión del certificado del servidor. Sustitúyelo por un certificado X.509 v3 o usa el modo Requerido solo en una red de confianza.",
|
||||
mysqlClientCertBrowse: "Elegir certificado cliente",
|
||||
mysqlClientKeyBrowse: "Elegir clave privada cliente",
|
||||
postgresSslMode: "Modo TLS",
|
||||
|
||||
@@ -548,6 +548,7 @@ export default withEnglishFallback({
|
||||
mysqlClientKeyPlaceholder: "/percorso/per/client.key",
|
||||
mysqlClientCertHint: "Il certificato client e la chiave privata devono essere forniti insieme quando MySQL richiede mTLS.",
|
||||
mysqlTlsConnectionFailureHint: "Se il server MySQL non richiede TLS, modifica la connessione, imposta la modalità TLS su Disabilitata e riprova.",
|
||||
mysqlUnsupportedCertVersionHint: "La verifica rigorosa non supporta questa versione del certificato del server. Sostituiscilo con un certificato X.509 v3 oppure usa la modalità Obbligatoria solo su una rete attendibile.",
|
||||
mysqlClientCertBrowse: "Scegli certificato client",
|
||||
mysqlClientKeyBrowse: "Scegli chiave privata client",
|
||||
postgresSslMode: "Modalità TLS",
|
||||
|
||||
@@ -548,6 +548,7 @@ export default withEnglishFallback({
|
||||
mysqlClientKeyPlaceholder: "/path/to/client.key",
|
||||
mysqlClientCertHint: "MySQLがmTLSを要求する場合、クライアント証明書と秘密鍵の両方を指定する必要があります。",
|
||||
mysqlTlsConnectionFailureHint: "MySQLサーバーがTLSを要求しない場合は、接続を編集し、TLSモードを無効にしてから再試行してください。",
|
||||
mysqlUnsupportedCertVersionHint: "厳密な検証では、このサーバー証明書のバージョンはサポートされていません。X.509 v3証明書に置き換えるか、信頼できるネットワークでのみ「必須」モードを使用してください。",
|
||||
mysqlClientCertBrowse: "クライアント証明書を選択",
|
||||
mysqlClientKeyBrowse: "クライアント秘密鍵を選択",
|
||||
postgresSslMode: "TLSモード",
|
||||
|
||||
@@ -568,6 +568,7 @@ export default withEnglishFallback({
|
||||
mysqlClientKeyPlaceholder: "/path/to/client.key",
|
||||
mysqlClientCertHint: "MySQL이 mTLS를 요구할 때 클라이언트 인증서와 개인 키를 함께 제공해야 합니다.",
|
||||
mysqlTlsConnectionFailureHint: "MySQL 서버가 TLS를 요구하지 않으면 연결을 편집하여 TLS 모드를 비활성화로 설정하고 다시 시도하세요.",
|
||||
mysqlUnsupportedCertVersionHint: "엄격한 검증은 이 서버 인증서 버전을 지원하지 않습니다. X.509 v3 인증서로 교체하거나 신뢰할 수 있는 네트워크에서만 필수 모드를 사용하세요.",
|
||||
mysqlClientCertBrowse: "클라이언트 인증서 선택",
|
||||
mysqlClientKeyBrowse: "클라이언트 개인 키 선택",
|
||||
postgresSslMode: "TLS 모드",
|
||||
|
||||
@@ -549,6 +549,7 @@ export default withEnglishFallback({
|
||||
mysqlClientKeyPlaceholder: "/caminho/para/client.key",
|
||||
mysqlClientCertHint: "O certificado e a chave privada do cliente devem ser fornecidos juntos quando o MySQL exige mTLS.",
|
||||
mysqlTlsConnectionFailureHint: "Se o servidor MySQL não exigir TLS, edite a conexão, defina o Modo TLS como Desabilitado e tente novamente.",
|
||||
mysqlUnsupportedCertVersionHint: "A verificação estrita não oferece suporte a esta versão do certificado do servidor. Substitua-o por um certificado X.509 v3 ou use o modo Obrigatório apenas em uma rede confiável.",
|
||||
mysqlClientCertBrowse: "Escolher certificado do cliente",
|
||||
mysqlClientKeyBrowse: "Escolher chave privada do cliente",
|
||||
postgresSslMode: "Modo TLS",
|
||||
|
||||
@@ -499,6 +499,7 @@ export default withEnglishFallback({
|
||||
mysqlClientKeyPlaceholder: "/path/to/client.key",
|
||||
mysqlClientCertHint: "MySQL 要求 mTLS 时,客户端证书和私钥必须一起填写。",
|
||||
mysqlTlsConnectionFailureHint: "如果 MySQL 服务端不要求 TLS,请编辑连接,在 TLS 模式中选择“禁用”后重试。",
|
||||
mysqlUnsupportedCertVersionHint: "服务端证书版本不受严格校验支持。请更换为 X.509 v3 证书,或仅在可信网络中改用“必需”模式。",
|
||||
mysqlClientCertBrowse: "选择客户端证书",
|
||||
mysqlClientKeyBrowse: "选择客户端私钥",
|
||||
postgresSslMode: "TLS 模式",
|
||||
|
||||
@@ -549,6 +549,7 @@ export default withEnglishFallback({
|
||||
mysqlClientKeyPlaceholder: "/path/to/client.key",
|
||||
mysqlClientCertHint: "MySQL 要求 mTLS 時,用戶端憑證和私鑰必須一起填寫。",
|
||||
mysqlTlsConnectionFailureHint: "如果 MySQL 伺服器不要求 TLS,請編輯連線,在 TLS 模式中選擇「停用」後重試。",
|
||||
mysqlUnsupportedCertVersionHint: "伺服器憑證版本不受嚴格驗證支援。請更換為 X.509 v3 憑證,或僅在可信任網路中改用「必要」模式。",
|
||||
mysqlClientCertBrowse: "選擇用戶端憑證",
|
||||
mysqlClientKeyBrowse: "選擇用戶端私鑰",
|
||||
postgresSslMode: "TLS 模式",
|
||||
|
||||
@@ -33,6 +33,7 @@ function jdbcConfig(): ConnectionConfig {
|
||||
|
||||
const t = (key: string) => {
|
||||
if (key === "connection.mysqlTlsConnectionFailureHint") return "Set TLS Mode to Disabled.";
|
||||
if (key === "connection.mysqlUnsupportedCertVersionHint") return "Replace the certificate or use Required mode.";
|
||||
if (key === "connection.mysqlMissingPasswordHint") return "No database password was sent.";
|
||||
if (key === "connection.jdbcMissingRuntimeDependencyHint") return "Install from Maven or import every dependency JAR.";
|
||||
return key;
|
||||
@@ -53,6 +54,52 @@ describe("appendConnectionErrorHints", () => {
|
||||
expect(message).toContain("Set TLS Mode to Disabled.");
|
||||
});
|
||||
|
||||
it("recognizes Connector/J TLS aliases", () => {
|
||||
const message = appendConnectionErrorHints(mysqlConfig("useSSL=true&requireSSL=true&verifyServerCertificate=true"), "MySQL connection failed: TLS handshake failed", t);
|
||||
|
||||
expect(message).toContain("Set TLS Mode to Disabled.");
|
||||
});
|
||||
|
||||
it("treats verifyServerCertificate as verified TLS unless useSSL is disabled", () => {
|
||||
const verified = appendConnectionErrorHints(mysqlConfig("verifyServerCertificate=true"), "MySQL connection failed: TLS handshake failed", t);
|
||||
const disabled = appendConnectionErrorHints(mysqlConfig("useSSL=false&requireSSL=true&verifyServerCertificate=true"), "MySQL connection failed: TLS handshake failed", t);
|
||||
|
||||
expect(verified).toContain("Set TLS Mode to Disabled.");
|
||||
expect(disabled).toBe("MySQL connection failed: TLS handshake failed");
|
||||
});
|
||||
|
||||
it("uses the last duplicate Connector/J TLS parameter", () => {
|
||||
const enabled = appendConnectionErrorHints(mysqlConfig("useSSL=false&useSSL=true"), "MySQL connection failed: TLS handshake failed", t);
|
||||
const disabled = appendConnectionErrorHints(mysqlConfig("useSSL=true&useSSL=false"), "MySQL connection failed: TLS handshake failed", t);
|
||||
|
||||
expect(enabled).toContain("Set TLS Mode to Disabled.");
|
||||
expect(disabled).toBe("MySQL connection failed: TLS handshake failed");
|
||||
});
|
||||
|
||||
it("prefers native require_ssl over Connector/J TLS aliases", () => {
|
||||
const disabled = appendConnectionErrorHints(mysqlConfig("require_ssl=false&verifyServerCertificate=true"), "MySQL connection failed: TLS handshake failed", t);
|
||||
const required = appendConnectionErrorHints(mysqlConfig("require_ssl=true&useSSL=false"), "MySQL connection failed: TLS handshake failed", t);
|
||||
|
||||
expect(disabled).toBe("MySQL connection failed: TLS handshake failed");
|
||||
expect(required).toContain("Set TLS Mode to Disabled.");
|
||||
});
|
||||
|
||||
it("uses the last duplicate native require_ssl value", () => {
|
||||
const required = appendConnectionErrorHints(mysqlConfig("require_ssl=false&require_ssl=true"), "MySQL connection failed: TLS handshake failed", t);
|
||||
const disabled = appendConnectionErrorHints(mysqlConfig("require_ssl=true&require_ssl=false"), "MySQL connection failed: TLS handshake failed", t);
|
||||
|
||||
expect(required).toContain("Set TLS Mode to Disabled.");
|
||||
expect(disabled).toBe("MySQL connection failed: TLS handshake failed");
|
||||
});
|
||||
|
||||
it("uses a certificate-specific hint for unsupported certificate versions", () => {
|
||||
const error = "MySQL connection failed: invalid peer certificate: Other(OtherError(UnsupportedCertVersion))";
|
||||
const message = appendConnectionErrorHints(mysqlConfig("sslMode=VERIFY_CA"), error, t);
|
||||
|
||||
expect(message).toContain("Replace the certificate or use Required mode.");
|
||||
expect(message).not.toContain("Set TLS Mode to Disabled.");
|
||||
});
|
||||
|
||||
it("does not add the TLS hint when MySQL TLS is disabled", () => {
|
||||
const message = appendConnectionErrorHints(mysqlConfig("ssl-mode=disabled"), "MySQL connection failed: TLS handshake failed", t);
|
||||
|
||||
|
||||
@@ -12,10 +12,28 @@ function normalizeUrlParamKey(key: string): string {
|
||||
|
||||
function urlParamValue(params: URLSearchParams, key: string): string {
|
||||
const normalizedKey = normalizeUrlParamKey(key);
|
||||
let result = "";
|
||||
for (const [paramKey, value] of params.entries()) {
|
||||
if (normalizeUrlParamKey(paramKey) === normalizedKey) return value;
|
||||
if (normalizeUrlParamKey(paramKey) === normalizedKey) result = value;
|
||||
}
|
||||
return "";
|
||||
return result;
|
||||
}
|
||||
|
||||
function jdbcUrlParamValue(params: URLSearchParams, key: string): string {
|
||||
const normalizedKey = key.trim().toLowerCase();
|
||||
let result = "";
|
||||
for (const [paramKey, value] of params.entries()) {
|
||||
if (paramKey.trim().toLowerCase() === normalizedKey) result = value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function hasJdbcUrlParam(params: URLSearchParams, key: string): boolean {
|
||||
const normalizedKey = key.trim().toLowerCase();
|
||||
for (const paramKey of params.keys()) {
|
||||
if (paramKey.trim().toLowerCase() === normalizedKey) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function mysqlTlsMode(config: ConnectionConfig): string {
|
||||
@@ -25,7 +43,19 @@ function mysqlTlsMode(config: ConnectionConfig): string {
|
||||
if (["disabled", "disable"].includes(mode)) return "disabled";
|
||||
if (["preferred", "prefer"].includes(mode)) return "preferred";
|
||||
if (["required", "require", "verify_ca", "verify_identity"].includes(mode)) return mode;
|
||||
if (config.ssl || ["true", "1", "yes", "on"].includes(urlParamValue(parsed, "require_ssl").trim().toLowerCase())) return "required";
|
||||
const isTrue = (value: string) => ["true", "1", "yes", "on"].includes(value);
|
||||
const isFalse = (value: string) => ["false", "0", "no", "off"].includes(value);
|
||||
const nativeRequireSsl = jdbcUrlParamValue(parsed, "require_ssl").trim().toLowerCase();
|
||||
if (isFalse(nativeRequireSsl)) return "disabled";
|
||||
if (isTrue(nativeRequireSsl)) return "required";
|
||||
const jdbcUseSsl = jdbcUrlParamValue(parsed, "useSSL").trim().toLowerCase();
|
||||
const jdbcRequireSsl = jdbcUrlParamValue(parsed, "requireSSL").trim().toLowerCase();
|
||||
const jdbcVerifyServerCertificate = jdbcUrlParamValue(parsed, "verifyServerCertificate").trim().toLowerCase();
|
||||
if (isFalse(jdbcUseSsl)) return "disabled";
|
||||
if (isTrue(jdbcVerifyServerCertificate)) return "verify_ca";
|
||||
if (isTrue(jdbcRequireSsl)) return "required";
|
||||
if (hasJdbcUrlParam(parsed, "useSSL") || hasJdbcUrlParam(parsed, "requireSSL") || hasJdbcUrlParam(parsed, "verifyServerCertificate")) return "preferred";
|
||||
if (config.ssl) return "required";
|
||||
return "disabled";
|
||||
}
|
||||
|
||||
@@ -66,6 +96,9 @@ export function appendConnectionErrorHints(config: ConnectionConfig | undefined,
|
||||
}
|
||||
if (mysqlTlsMode(config) === "disabled") return message;
|
||||
if (!isMysqlTlsLikeFailure(message)) return message;
|
||||
if (/UnsupportedCertVersion/i.test(message)) {
|
||||
return appendHint(message, t("connection.mysqlUnsupportedCertVersionHint"));
|
||||
}
|
||||
const hint = t("connection.mysqlTlsConnectionFailureHint");
|
||||
return appendHint(message, hint);
|
||||
}
|
||||
@@ -257,6 +257,18 @@ function queryParamValue(params: string, key: string): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function queryParamLastValue(params: string, key: string): string | undefined {
|
||||
let result: string | undefined;
|
||||
for (const part of params.split(/[&;]/)) {
|
||||
if (!part) continue;
|
||||
const [rawKey, ...rest] = part.split("=");
|
||||
if (decodeUrlPart(rawKey).toLowerCase() === key.toLowerCase()) {
|
||||
result = decodeUrlPart(rest.join("=")).trim();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function extractHiveStructuredParams(params: string): { username?: string; password?: string; ssl: boolean; urlParams: string } {
|
||||
let username: string | undefined;
|
||||
let password: string | undefined;
|
||||
@@ -334,10 +346,16 @@ function urlParamsRequireTls(dbType: DatabaseType, params: string): boolean {
|
||||
}
|
||||
|
||||
if (dbType === "mysql") {
|
||||
const requireSsl = queryParamValue(params, "require_ssl")?.toLowerCase();
|
||||
const requireSsl = queryParamLastValue(params, "require_ssl")?.toLowerCase();
|
||||
if (requireSsl === "true" || requireSsl === "1" || requireSsl === "yes") return true;
|
||||
const sslMode = (queryParamValue(params, "ssl-mode") || queryParamValue(params, "sslmode") || "").toLowerCase().replace("-", "_");
|
||||
return sslMode === "required" || sslMode === "require" || sslMode === "verify_ca" || sslMode === "verify_identity";
|
||||
const sslMode = (queryParamLastValue(params, "ssl-mode") || queryParamLastValue(params, "sslmode") || "").toLowerCase().replace("-", "_");
|
||||
if (sslMode === "required" || sslMode === "require" || sslMode === "verify_ca" || sslMode === "verify_identity") return true;
|
||||
if (requireSsl !== undefined || sslMode) return false;
|
||||
const jdbcUseSsl = (queryParamLastValue(params, "useSSL") || "").toLowerCase();
|
||||
const jdbcRequireSsl = (queryParamLastValue(params, "requireSSL") || "").toLowerCase();
|
||||
const jdbcVerifyServerCertificate = (queryParamLastValue(params, "verifyServerCertificate") || "").toLowerCase();
|
||||
if (["false", "0", "no", "off"].includes(jdbcUseSsl)) return false;
|
||||
return ["true", "1", "yes", "on"].includes(jdbcRequireSsl) || ["true", "1", "yes", "on"].includes(jdbcVerifyServerCertificate);
|
||||
}
|
||||
|
||||
if (dbType === "postgres" || dbType === "redshift" || dbType === "kwdb") {
|
||||
|
||||
Reference in new issue
Block a user