From b2c9703d6679426524fc874f7e0d0524d38153f2 Mon Sep 17 00:00:00 2001 From: asinha Date: Thu, 24 Apr 2025 13:36:06 -0700 Subject: [PATCH] MLE-16819 : Support TLSv1.3 via Node Client --- etc/test-config.js | 10 ++ lib/marklogic.js | 2 +- test-basic/ssl-min-allow-tls-test.js | 169 +++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 test-basic/ssl-min-allow-tls-test.js diff --git a/etc/test-config.js b/etc/test-config.js index 68886522..12dd21bc 100644 --- a/etc/test-config.js +++ b/etc/test-config.js @@ -16,6 +16,7 @@ let testHost = 'localhost'; let restPort = '8015'; +let restSslPort = '8016'; let restAuthType = 'DIGEST'; let managePort = '8002'; @@ -148,5 +149,14 @@ module.exports = { host: testHost, port: restPort, authType: 'oauth' + }, + restConnectionForTls: { + host: testHost, + port: restSslPort, + user: restWriterUser, + password: restWriterPassword, + authType: restAuthType, + ssl: true, + rejectUnauthorized: false } }; diff --git a/lib/marklogic.js b/lib/marklogic.js index 1e29f8cf..33f994af 100644 --- a/lib/marklogic.js +++ b/lib/marklogic.js @@ -791,7 +791,7 @@ function initClient(client, inputParams) { client.request = https.request; if (noAgent) { mlutil.copyProperties(inputParams, agentOptions, [ - 'keepAliveMsecs', 'maxCachedSessions', 'maxFreeSockets', 'maxSockets', 'maxTotalSockets', 'scheduling', 'timeout' + 'keepAliveMsecs', 'maxCachedSessions', 'maxFreeSockets', 'maxSockets', 'maxTotalSockets', 'scheduling', 'timeout', 'minVersion', 'maxVersion' ]); connectionParams.agent = new https.Agent(agentOptions); } else { diff --git a/test-basic/ssl-min-allow-tls-test.js b/test-basic/ssl-min-allow-tls-test.js new file mode 100644 index 00000000..fb5b07b7 --- /dev/null +++ b/test-basic/ssl-min-allow-tls-test.js @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2025 MarkLogic Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +let testconfig = require('../etc/test-config.js'); +let should = require('should'); +let marklogic = require('../'); +const { exec } = require('child_process'); +const testlib = require("../etc/test-lib"); +let db = marklogic.createDatabaseClient(testconfig.restConnectionForTls); +let serverConfiguration = {}; +let host = testconfig.testHost; + +describe('document write and read using min tls', function () { + this.timeout(10000); + before(function (done) { + testlib.findServerConfiguration(serverConfiguration); + setTimeout(() => { + if (serverConfiguration.serverVersion < 12) { + this.skip(); + } + done(); + }, 3000); + }); + + it('should write document with minimum TLS versions 1.3', function (done) { + updateTlsVersion('TLSv1.3').then((result) => { + db.documents.write({ + uri: '/test/write_tlsV1.3.json', + contentType: 'application/json', + content: '{"key1":"With TLS 1.3"}' + }).result(function (response) { + db.documents.read('/test/write_tlsV1.3.json') + .result(function (documents) { + documents[0].content.should.have.property('key1'); + documents[0].content.key1.should.equal('With TLS 1.3'); + + }).then(() => done()) + .catch(error => done(error)); + }).catch(error=> done(error)); + }).catch(error=> done(error)); + }); + + it('should write document with minimum TLS versions 1.2', function (done) { + updateTlsVersion('TLSv1.2').then((result) => { + db.documents.write({ + uri: '/test/write_tlsV1.2.json', + contentType: 'application/json', + content: '{"key1":"With TLS 1.2"}' + }).result(function (response) { + db.documents.read('/test/write_tlsV1.2.json') + .result(function (documents) { + documents[0].content.should.have.property('key1'); + documents[0].content.key1.should.equal('With TLS 1.2'); + + }).then(() => done()) + .catch(error => done(error)); + }).catch(error=> done(error)); + }).catch(error=> done(error)); + }); + + it('should throw error when user strictly sets 1.2 and server needs min TLS version as 1.3', function (done) { + testconfig.restConnectionForTls.minVersion = 'TLSv1.2'; + testconfig.restConnectionForTls.maxVersion = 'TLSv1.2'; + db = marklogic.createDatabaseClient(testconfig.restConnectionForTls); + updateTlsVersion('TLSv1.3').then(() => { + db.documents.write({ + uri: '/test/write_tlsV1.2.json', + contentType: 'application/json', + content: '{"key1":"Test"}' + }).result(()=> done(new Error('Document write should fail when user uses 1.2 and server needs min TLS version as 1.3')) + ).catch(error=> { + // TLS handshake error. + error.message.should.containEql("SSL routines") + done(); + }) + }).catch(error=> done(error)); + }); + + it('should write document with minVersion and maxVersion', function (done) { + testconfig.restConnectionForTls.minVersion = 'TLSv1.2'; + testconfig.restConnectionForTls.maxVersion = 'TLSv1.3'; + db = marklogic.createDatabaseClient(testconfig.restConnectionForTls); + updateTlsVersion('TLSv1.3').then(() => { + db.documents.write({ + uri: '/test/write_with_min_and_max_versions.json', + contentType: 'application/json', + content: '{"key1":"With min and max TLS versions."}' + }).result(() => done()).catch(error => { + db.documents.read('/test/write_with_min_and_max_versions.json') + .result(function (documents) { + documents[0].content.should.have.property('key1'); + documents[0].content.key1.should.equal('With min and max TLS versions.'); + + }).then(() => done()) + .catch(error => done(error)); + }).catch(error=> done(error)); + }).catch(error=> done(error)); + }); + + it('should write document with only minVersion', function (done) { + testconfig.restConnectionForTls.minVersion = 'TLSv1.2'; + db = marklogic.createDatabaseClient(testconfig.restConnectionForTls); + updateTlsVersion('TLSv1.3').then(() => { + db.documents.write({ + uri: '/test/write_with_only_min_version.json', + contentType: 'application/json', + content: '{"key1":"With only min TLS version."}' + }).result(() => done()).catch(error => { + db.documents.read('/test/write_with_only_min_version.json') + .result(function (documents) { + documents[0].content.should.have.property('key1'); + documents[0].content.key1.should.equal('With only min TLS version.'); + + }).then(() => done()) + .catch(error => done(error)); + }).catch(error=> done(error)); + }).catch(error=> done(error)); + }); + + it('should write document with only maxVersion', function (done) { + testconfig.restConnectionForTls.maxVersion = 'TLSv1.3'; + db = marklogic.createDatabaseClient(testconfig.restConnectionForTls); + updateTlsVersion('TLSv1.3').then(() => { + db.documents.write({ + uri: '/test/write_with_only_max_version.json', + contentType: 'application/json', + content: '{"key1":"With only max TLS version."}' + }).result(() => done()).catch(error => { + db.documents.read('/test/write_with_only_max_version.json') + .result(function (documents) { + documents[0].content.should.have.property('key1'); + documents[0].content.key1.should.equal('With only max TLS version.'); + + }).then(() => done()) + .catch(error => done(error)); + }).catch(error=> done(error)); + }).catch(error=> done(error)); + }); +}) + +function updateTlsVersion(tlsVersion) { + return new Promise((resolve, reject) => { + const curlCommand = ` + curl --anyauth --user admin:admin -X PUT -H "Content-Type: application/json" \ +-d '{"ssl-min-allow-tls": "${tlsVersion}"}' \ +'http://${host}:8002/manage/v2/servers/unittest-nodeapi-ssl/properties?group-id=Default' + `; + exec(curlCommand, (error, stdout, stderr) => { + if (error) { + throw new Error(`Error executing curl: ${stderr}`); + } + resolve(); + }); + }); +} +