Skip to content

MLE-30259: [Node Client] Replace rejectUnauthorized:false With Proper CA Certificates in Test Configs#1100

Open
ngodugu-marklogic wants to merge 1 commit into
developfrom
MLE-30259-1
Open

MLE-30259: [Node Client] Replace rejectUnauthorized:false With Proper CA Certificates in Test Configs#1100
ngodugu-marklogic wants to merge 1 commit into
developfrom
MLE-30259-1

Conversation

@ngodugu-marklogic

Copy link
Copy Markdown

Summary

  1. Removed insecure TLS bypasses from test configs by eliminating active uses of [rejectUnauthorized: false] and switching SSL connections to CA-based verification [ca].
  2. Implemented deploy-time certificate trust flow: MarkLogic generates the SSL cert during deploy, and Gradle extracts the PEM to a well-known path for Node.js test clients.
  3. Added regression coverage to prevent insecure config drift and validate certificate-provisioning wiring.

Why

  1. [rejectUnauthorized: false] disables certificate validation and is vulnerable to MITM (CWE-295).
  2. This change enforces proper certificate verification while keeping test setup reproducible.

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: false and (when deployed) provide a CA Buffer.
  • Updated Node test config modules to load a deploy-generated self-signed-ca.pem and pass it via the ca option for SSL connections.
  • Added a Gradle extractSslCertificate task finalized by mlDeploy to fetch the generated PEM from MarkLogic and write it to src/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.

Comment thread test-app/build.gradle Outdated
Comment thread test-app/build.gradle
Comment thread test-basic/ssl-config-security-test.js
Comment thread test-app/build.gradle
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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 () {

@rjdew-progress rjdew-progress Jul 17, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can leave out the testing of the test-app build.gradle file contents.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants