Add ssl-checker, refactor fetchMonitorCertificate

This commit is contained in:
Alex Holliday committed 2024-10-17 11:57:37 +08:00
1 parent d8e2b06eca
commit 79cf169ede
5 files changed
+42 -114

No files matched your search

+7 -24
View File
@@ -12,32 +12,15 @@ const handleError = (error, serviceName, method, status = 500) => {
return error;
};
const fetchMonitorCertificate = async (tls, monitor) => {
return new Promise((resolve, reject) => {
const fetchMonitorCertificate = async (sslChecker, monitor) => {
try {
const monitorUrl = new URL(monitor.url);
const hostname = monitorUrl.hostname;
try {
let socket = tls.connect(
{
port: 443,
host: hostname,
servername: hostname, // this is required in case the server enabled SNI
},
() => {
try {
let x509Certificate = socket.getPeerX509Certificate();
resolve(x509Certificate);
} catch (error) {
reject(error);
} finally {
socket.end();
}
}
);
} catch (error) {
reject(error);
}
});
const cert = await sslChecker(hostname);
return cert;
} catch (error) {
throw error;
}
};
export { handleValidationError, handleError, fetchMonitorCertificate };
+3 -7
View File
@@ -12,18 +12,14 @@ import {
getMonitorStatsByIdQueryValidation,
getCertificateParamValidation,
} from "../validation/joi.js";
import * as tls from "tls";
import sslChecker from "ssl-checker";
const SERVICE_NAME = "monitorController";
import { errorMessages, successMessages } from "../utils/messages.js";
import jwt from "jsonwebtoken";
import { getTokenFromHeaders } from "../utils/utils.js";
import logger from "../utils/logger.js";
import {
handleError,
handleValidationError,
fetchMonitorCertificate,
} from "./controllerUtils.js";
import { handleError, handleValidationError } from "./controllerUtils.js";
/**
* Returns all monitors
@@ -87,7 +83,7 @@ const getMonitorCertificate = async (req, res, next, fetchMonitorCertificate) =>
try {
const { monitorId } = req.params;
const monitor = await req.db.getMonitorById(monitorId);
const certificate = await fetchMonitorCertificate(tls, monitor);
const certificate = await fetchMonitorCertificate(sslChecker, monitor);
if (certificate && certificate.validTo) {
return res.status(200).json({
success: true,
+7
View File
@@ -27,6 +27,7 @@
"nodemailer": "^6.9.14",
"ping": "0.4.4",
"sharp": "0.33.4",
"ssl-checker": "2.0.10",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
@@ -6002,6 +6003,12 @@
"memory-pager": "^1.0.2"
}
},
"node_modules/ssl-checker": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/ssl-checker/-/ssl-checker-2.0.10.tgz",
"integrity": "sha512-SS6rrZocToJWHM1p6iVNb583ybB3UqT1fymCHSWuEdXDUqKA6O1D5Fb8KJVmhj3XKXE82IEWcr+idJrc4jUzFQ==",
"license": "MIT"
},
"node_modules/stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
+4 -3
View File
@@ -30,16 +30,17 @@
"nodemailer": "^6.9.14",
"ping": "0.4.4",
"sharp": "0.33.4",
"ssl-checker": "2.0.10",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
"devDependencies": {
"prettier": "^3.3.3",
"chai": "5.1.1",
"mocha": "10.7.3",
"c8": "10.1.2",
"chai": "5.1.1",
"esm": "3.2.25",
"mocha": "10.7.3",
"nodemon": "3.1.0",
"prettier": "^3.3.3",
"sinon": "19.0.2"
}
}
@@ -6,6 +6,9 @@ import {
fetchMonitorCertificate,
} from "../../controllers/controllerUtils.js";
import { expect } from "chai";
import sslChecker from "ssl-checker";
import { afterEach } from "node:test";
import exp from "constants";
describe("controllerUtils - handleValidationError", () => {
it("should set status to 422", () => {
@@ -109,102 +112,40 @@ describe("controllerUtils - handleError", () => {
});
describe("controllerUtils - fetchMonitorCertificate", () => {
const originalTls = {
connect: sinon.stub().callsFake((options, callback) => {
// Create socket stub with sinon stubs for all methods
socket = {
getPeerX509Certificate: sinon.stub().returns({
subject: "CN=fake-cert",
validTo: "Dec 31 23:59:59 2023 GMT",
}),
end: sinon.stub(),
on: sinon.stub(),
};
// Use process.nextTick to ensure async behavior
process.nextTick(() => {
callback.call(socket); // Ensure correct 'this' binding
});
return socket;
}),
};
let tls, monitor, socket;
let sslChecker, monitor;
beforeEach(() => {
monitor = { url: "https://www.google.com" };
tls = {
connect: sinon.stub().callsFake((options, callback) => {
// Create socket stub with sinon stubs for all methods
socket = {
getPeerX509Certificate: sinon.stub().returns({
subject: "CN=fake-cert",
validTo: "Dec 31 23:59:59 2023 GMT",
}),
end: sinon.stub(),
on: sinon.stub(),
};
// Use process.nextTick to ensure async behavior
process.nextTick(() => {
callback.call(socket); // Ensure correct 'this' binding
});
return socket;
}),
monitor = {
url: "https://www.google.com",
};
sslChecker = sinon.stub();
});
afterEach(() => {
tls = { ...originalTls };
sinon.restore();
});
it("should resolve with the certificate when the connection is successful", async () => {
const certificate = await fetchMonitorCertificate(tls, monitor);
expect(certificate.validTo).to.equal("Dec 31 23:59:59 2023 GMT");
expect(socket.end.calledOnce).to.be.true;
});
it("should reject with an error when the connection fails", async () => {
tls.connect = sinon.stub().throws(new Error("Connection error"));
it("should reject with an error if a URL does not parse", async () => {
monitor.url = "invalidurl";
try {
await fetchMonitorCertificate(tls, monitor);
} catch (error) {
expect(error.message).to.equal("Connection error");
}
});
it("should reject with an error if monitorURL is invalid", async () => {
monitor.url = "invalid-url";
try {
await fetchMonitorCertificate(tls, monitor);
await fetchMonitorCertificate(sslChecker, monitor);
} catch (error) {
expect(error).to.be.an("error");
expect(error.message).to.equal("Invalid URL");
}
});
it("should do a thing", async () => {
tls = {
connect: sinon.stub().callsFake((options, callback) => {
// Create socket stub with sinon stubs for all methods
socket = {
getPeerX509Certificate: sinon.stub().throws(new Error("Certificate error")),
end: sinon.stub(),
on: sinon.stub(),
};
// Use process.nextTick to ensure async behavior
process.nextTick(() => {
callback.call(socket); // Ensure correct 'this' binding
});
return socket;
}),
};
it("should reject with an error if sslChecker throws an error", async () => {
sslChecker.rejects(new Error("Test error"));
try {
await fetchMonitorCertificate(tls, monitor);
await fetchMonitorCertificate(sslChecker, monitor);
} catch (error) {
expect(error.message).to.equal("Certificate error");
expect(error).to.be.an("error");
expect(error.message).to.equal("Test error");
}
});
it("should return a certificate if sslChecker resolves", async () => {
sslChecker.resolves({ validTo: "2022-01-01" });
const result = await fetchMonitorCertificate(sslChecker, monitor);
expect(result).to.deep.equal({ validTo: "2022-01-01" });
});
});