-
Notifications
You must be signed in to change notification settings - Fork 53
Expand file tree
/
Copy pathssl-config-security-test.js
More file actions
164 lines (141 loc) · 7.24 KB
/
Copy pathssl-config-security-test.js
File metadata and controls
164 lines (141 loc) · 7.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/*
* Copyright (c) 2015-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
*
* Regression guard: verify that no SSL-capable connection config disables TLS certificate
* verification via rejectUnauthorized: false, and that each uses the CA extracted from
* MarkLogic by the Gradle generateAndInstallSslCertificate task.
*
* Two tiers of checks:
* 1. rejectUnauthorized: false guard -- always runs, no MarkLogic required.
* 2. CA Buffer presence check -- runs only after mlDeploy has been executed
* (self-signed-ca.pem exists). Tests are skipped with a clear message otherwise.
*/
"use strict";
var fs = require("fs");
var path = require("path");
var should = require("should");
var marklogic = require("../");
var testlib = require("../etc/test-lib");
// Path where generateAndInstallSslCertificate writes the CA PEM after mlDeploy
var CA_PATH = path.join(__dirname, "../test-app/src/main/ml-config/self-signed-ca.pem");
var caDeployed = fs.existsSync(CA_PATH);
// All three test-config files that previously contained rejectUnauthorized: false
var configs = [
{ label: "test-config.js", module: require("../etc/test-config.js") },
{ label: "test-config-qa-ssl.js", module: require("../etc/test-config-qa-ssl.js") },
{ label: "test-config-qa.js", module: require("../etc/test-config-qa.js") }
];
var mainTestConfig = configs[0].module;
// SSL-enabled connection objects expected in each config
var sslConnectionKeys = {
"test-config.js": ["restSslConnection", "restConnectionForTls"],
"test-config-qa-ssl.js": ["restSslConnection"],
"test-config-qa.js": ["restSslConnection"]
};
// Non-SSL connections that previously carried the stray property
var nonSslConnectionKeys = {
"test-config.js": ["testConnection", "tdeConnection"]
};
function looksLikeTlsVerifyError(message) {
if (!message) {
return false;
}
return /self signed|unable to verify|certificate verify failed|DEPTH_ZERO_SELF_SIGNED_CERT|SELF_SIGNED_CERT_IN_CHAIN|UNABLE_TO_VERIFY_LEAF_SIGNATURE|SSL routines/i.test(message);
}
describe("SSL connection config security guard", function () {
configs.forEach(function(cfgEntry) {
var label = cfgEntry.label;
var cfg = cfgEntry.module;
describe(label, function () {
var sslKeys = sslConnectionKeys[label] || [];
sslKeys.forEach(function (key) {
// Tier 1: always enforce -- no MarkLogic needed
it(key + " must not set rejectUnauthorized: false", function () {
var conn = cfg[key];
should.exist(conn, key + " should exist in " + label);
should(conn.rejectUnauthorized).not.equal(false,
key + ".rejectUnauthorized must not be false -- use a ca bundle instead");
});
// Tier 2: CA presence -- only meaningful after mlDeploy has run
it(key + " must provide a ca certificate Buffer (requires mlDeploy)", function () {
if (!caDeployed) {
return this.skip(
"self-signed-ca.pem not found -- run gradle mlDeploy first to " +
"extract the CA certificate from MarkLogic");
}
var conn = cfg[key];
should.exist(conn, key + " should exist in " + label);
should.exist(conn.ca,
key + ".ca must be set -- self-signed-ca.pem exists but ca is undefined");
Buffer.isBuffer(conn.ca).should.be.true(
key + ".ca should be a Buffer loaded from the PEM file");
conn.ca.length.should.be.greaterThan(0, key + ".ca buffer must not be empty");
conn.ca.toString("utf8").should.containEql("-----BEGIN CERTIFICATE-----");
});
});
// Non-SSL connections must not carry the stray property
var nonSslKeys = nonSslConnectionKeys[label] || [];
nonSslKeys.forEach(function (key) {
it(key + " (non-SSL) must not set rejectUnauthorized: false", function () {
var conn = cfg[key];
should.exist(conn, key + " should exist in " + label);
should(conn.rejectUnauthorized).not.equal(false,
key + ".rejectUnauthorized: false has no effect on non-SSL connections -- remove it");
});
});
});
});
// CA file sanity check -- skipped pre-deploy, validates post-deploy
describe("self-signed-ca.pem (generated by generateAndInstallSslCertificate task)", function () {
it("CA file exists at the well-known path after mlDeploy", function () {
if (!caDeployed) {
return this.skip("Run gradle mlDeploy to generate self-signed-ca.pem");
}
fs.existsSync(CA_PATH).should.be.true("CA file not found at " + CA_PATH);
});
it("CA file contains a valid PEM certificate", function () {
if (!caDeployed) {
return this.skip("Run gradle mlDeploy to generate self-signed-ca.pem");
}
var content = fs.readFileSync(CA_PATH, "utf8");
content.should.containEql("-----BEGIN CERTIFICATE-----");
content.should.containEql("-----END CERTIFICATE-----");
});
});
// Live-server integration check: verification must fail when CA is not provided.
describe("SSL certificate enforcement (negative)", function () {
this.timeout(15000);
var serverConfiguration = {};
before(function (done) {
testlib.findServerConfiguration(serverConfiguration);
setTimeout(function () {
done();
}, 3000);
});
it("fails TLS handshake when CA is intentionally omitted", function (done) {
if (serverConfiguration.serverVersion < 12) {
return this.skip();
}
if (!caDeployed || !mainTestConfig.restConnectionForTls || !Buffer.isBuffer(mainTestConfig.restConnectionForTls.ca)) {
return this.skip(
"CA not deployed/loaded -- run gradle mlDeploy first so restConnectionForTls includes a CA before running this integration check");
}
// Clone the known-good TLS connection and intentionally remove CA trust.
var insecureConn = Object.assign({}, mainTestConfig.restConnectionForTls);
delete insecureConn.ca;
var dbNoCa = marklogic.createDatabaseClient(insecureConn);
dbNoCa.documents.read({ uris: "/does-not-matter-for-tls-check.json" })
.result(function () {
done(new Error("Expected TLS handshake failure when CA is omitted, but request succeeded"));
})
.catch(function (error) {
should.exist(error);
should.exist(error.message);
looksLikeTlsVerifyError(error.message).should.be.true(
"Expected TLS/certificate verification failure, got: " + error.message
);
done();
});
});
});
});