MLE-30259: [Node Client] Replace rejectUnauthorized:false With Proper CA Certificates in Test Configs#1100
MLE-30259: [Node Client] Replace rejectUnauthorized:false With Proper CA Certificates in Test Configs#1100ngodugu-marklogic wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens Node client SSL test configuration by removing rejectUnauthorized: false from test connection configs and switching to CA-based verification, with a Gradle task that extracts the generated MarkLogic certificate PEM to a well-known path used by the Node test configs. It also adds a regression test to prevent insecure TLS bypasses from reappearing and to validate the deploy-time certificate provisioning wiring.
Changes:
- Added a regression/security test that asserts test configs do not set
rejectUnauthorized: falseand (when deployed) provide a CABuffer. - Updated Node test config modules to load a deploy-generated
self-signed-ca.pemand pass it via thecaoption for SSL connections. - Added a Gradle
extractSslCertificatetask finalized bymlDeployto fetch the generated PEM from MarkLogic and write it tosrc/main/ml-config/self-signed-ca.pem, and ignored generated cert artifacts in.gitignore.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
test-basic/ssl-config-security-test.js |
Adds regression coverage for TLS config security and deploy wiring validation. |
test-app/build.gradle |
Extracts the generated MarkLogic certificate PEM after mlDeploy for Node tests to trust via ca. |
etc/test-config.js |
Replaces TLS bypass with CA-based verification for SSL test connections. |
etc/test-config-qa.js |
Replaces TLS bypass with CA-based verification for QA SSL connection config. |
etc/test-config-qa-ssl.js |
Replaces TLS bypass with CA-based verification for QA SSL-only config. |
.gitignore |
Prevents deploy-generated cert/key material from being committed. |
cc06169 to
6103d49
Compare
a122ea0 to
7307496
Compare
7307496 to
3e78a1a
Compare
| tasks.register("extractSslCertificate") { | ||
| description = "Extracts the generated SSL CA certificate from MarkLogic and writes it for Node.js test clients." | ||
| doLast { | ||
| def manageHost = project.findProperty("mlManageHost") ?: project.findProperty("mlHost") ?: "localhost" |
There was a problem hiding this comment.
This logic can be simplified by using the ManageClient like the following:
getManageClient().getJson(
"/manage/v2/certificate-templates/node-client-ssl-template?format=json")That has the added benefit of handling the different authentication types for us.
Also, we probably want a task to generate the SSL Certificate and insert it, instead of extracting it from MarkLogic something like below:
/*
* Generates a CA + server certificate pair using openssl and inserts the server
* cert into the MarkLogic node-client-ssl-template. The self-signed CA cert is
* written to src/main/ml-config/self-signed-ca.pem so Node.js test clients can
* trust the MarkLogic SSL server via the `ca` option.
*
* Why openssl instead of MarkLogic's generateTemporaryCertificate:
* MarkLogic's internal CA (used to sign the temporary cert) is not exposed by
* the Management API, so there is no way to retrieve it for use in the Node.js
* `ca` option. Generating our own CA + server cert with openssl solves this
* while keeping rejectUnauthorized: false out of production code.
* Do NOT use rejectUnauthorized: false in production.
*
* The generated files are listed in .gitignore and recreated on every run.
*/
tasks.register("generateAndInstallSslCertificate", com.marklogic.gradle.task.MarkLogicTask) {
description = "Generates CA + server certs with openssl and inserts the server cert into MarkLogic."
doLast {
def certDir = file("src/main/ml-config")
certDir.mkdirs()
def caKeyFile = new File(certDir, "self-signed-ca.key")
def caCertFile = new File(certDir, "self-signed-ca.pem")
def srvKeyFile = new File(certDir, "ssl-server.key")
def srvCsrFile = new File(certDir, "ssl-server.csr")
def srvCertFile = new File(certDir, "ssl-server.pem")
def extFile = new File(certDir, "ssl-server-ext.cnf")
// Step 1 — self-signed CA cert (subject == issuer so Node.js can use it as a trust anchor)
exec {
commandLine "openssl", "req", "-x509", "-newkey", "rsa:2048",
"-keyout", caKeyFile.absolutePath, "-out", caCertFile.absolutePath,
"-days", "365", "-nodes",
"-subj", "/C=US/ST=VA/L=McLean/O=MarkLogic/OU=Sales/CN=localhost"
errorOutput = System.err
}
// Step 2 — server CSR
exec {
commandLine "openssl", "req", "-newkey", "rsa:2048",
"-keyout", srvKeyFile.absolutePath, "-out", srvCsrFile.absolutePath,
"-nodes", "-subj", "/CN=localhost"
errorOutput = System.err
}
// Step 3 — server cert signed by our CA (SAN required by modern TLS clients)
extFile.text = "subjectAltName=IP:127.0.0.1,DNS:localhost\nextendedKeyUsage=serverAuth"
exec {
commandLine "openssl", "x509", "-req",
"-in", srvCsrFile.absolutePath,
"-CA", caCertFile.absolutePath, "-CAkey", caKeyFile.absolutePath,
"-CAcreateserial", "-out", srvCertFile.absolutePath,
"-days", "365", "-extfile", extFile.absolutePath
errorOutput = System.err
}
// Step 4 — insert server cert + key into the MarkLogic certificate template.
// ManageClient handles Digest auth automatically.
def payload = new groovy.json.JsonBuilder([
operation : "insert-host-certificates",
certificates : [[certificate: [cert: srvCertFile.text, pkey: srvKeyFile.text]]]
]).toString()
getManageClient().postJson(
"/manage/v2/certificate-templates/node-client-ssl-template", payload)
println "SSL CA cert → ${caCertFile.absolutePath}"
println "SSL server cert inserted into MarkLogic template: node-client-ssl-template"
}
}
mlDeploy.finalizedBy generateAndInstallSslCertificate
| describe("SSL connection config security guard", function () { | ||
|
|
||
| // Smoke-test the deploy wiring that provisions and exports the cert used by `ca`. | ||
| describe("build.gradle certificate provisioning wiring", function () { |
There was a problem hiding this comment.
I think we can leave out the testing of the test-app build.gradle file contents.
Summary
Why
Testing
No-server regression/security checks:
npx mocha test-basic/ssl-config-security-test.js
Live-server validation (same file, integration section):
ensures TLS handshake fails when CA is intentionally omitted.