From 80d13d924b67693e0d3512cc1f19078a82ff8d1d Mon Sep 17 00:00:00 2001 From: JFronny Date: Sun, 5 Jan 2025 13:26:10 +0100 Subject: [PATCH 1/2] Implement check-tls as alternative to ping (cherry picked from commit 701a50b279c444354c159b7a448601720be778b0) --- src/helpers/check-tls.spec.ts | 44 ++++++++++++++++++++++++ src/helpers/check-tls.ts | 65 +++++++++++++++++++++++++++++++++++ src/interfaces.ts | 2 +- src/update.ts | 36 +++++++++++++++++++ 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/helpers/check-tls.spec.ts create mode 100644 src/helpers/check-tls.ts diff --git a/src/helpers/check-tls.spec.ts b/src/helpers/check-tls.spec.ts new file mode 100644 index 00000000..3c3e5c6e --- /dev/null +++ b/src/helpers/check-tls.spec.ts @@ -0,0 +1,44 @@ +import { checkTls } from "./check-tls" + +test("checkTls", async () => { + const tcpResult = await checkTls({ + address: "smtp.gmail.com", + port: 465 + }) + expect(tcpResult.results.every( + (result) => Object.prototype.toString.call((result as any).err) === "[object Error]" + )).toBe(false) + expect(tcpResult.avg || -1).toBeGreaterThan(0) + expect(tcpResult.avg || 0).toBeLessThan(60000) +}) + +test("checkTls2", async () => { + const tcpResult = await checkTls({ + address: "wrong.host.badssl.com", + }) + console.log(tcpResult) + expect(tcpResult.results.every( + (result) => Object.prototype.toString.call((result as any).err) === "[object Error]" + )).toBe(true) + expect(tcpResult.avg).toBe(0) +}) + +test("checkTls3", async () => { + const tcpResult = await checkTls({ + address: "expired.host.badssl.com", + }) + expect(tcpResult.results.every( + (result) => Object.prototype.toString.call((result as any).err) === "[object Error]" + )).toBe(true) + expect(tcpResult.avg).toBe(0) +}) + +test("checkTls4", async () => { + const tcpResult = await checkTls({ + address: "self-signed.badssl.com", + }) + expect(tcpResult.results.every( + (result) => Object.prototype.toString.call((result as any).err) === "[object Error]" + )).toBe(true) + expect(tcpResult.avg).toBe(0) +}) \ No newline at end of file diff --git a/src/helpers/check-tls.ts b/src/helpers/check-tls.ts new file mode 100644 index 00000000..748bc593 --- /dev/null +++ b/src/helpers/check-tls.ts @@ -0,0 +1,65 @@ +import { connect } from "tls"; +import { Options, Results, Result } from "tcp-ping"; + +export const checkTls = (options: Options) => new Promise((resolve, reject) => { + let i = 0; + const results: Results[] = []; + const check = () => { + if (i < (options.attempts || 10)) { + doConnect(); + } else { + resolve({ + address: options.address || "localhost", + port: options.port || 443, + attempts: options.attempts || 10, + avg: results.reduce((acc, curr) => acc + (curr.time || 0), 0) / results.length, + max: results.reduce((acc, curr) => Math.max(acc, curr.time || 0), 0), + min: results.reduce((acc, curr) => Math.min(acc, curr.time || 0), Infinity), + results: results, + }) + } + } + const doConnect = () => { + const start = process.hrtime(); + const socket = connect(options.port || 443, options.address, { + servername: options.address, + rejectUnauthorized: true, + //checkServerIdentity: () => undefined, + }, () => { + const timeArr = process.hrtime(start); + results.push({ + time: (timeArr[0] * 1e9 + timeArr[1]) / 1e6, + seq: i, + }); + socket.end(); + socket.destroy(); + i++; + check(); + }); + + socket.setTimeout(options.timeout || 5000, () => { + results.push({ + time: undefined, + seq: i, + err: Error("Request timeout"), + }) + socket.end(); + socket.destroy(); + i++; + check(); + }) + + socket.on("error", (error) => { + results.push({ + time: undefined, + seq: i, + err: error, + }); + socket.end(); + socket.destroy(); + i++; + check(); + }); + } + doConnect(); +}) \ No newline at end of file diff --git a/src/interfaces.ts b/src/interfaces.ts index 727a0068..7c05179b 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -3,7 +3,7 @@ export interface UpptimeConfig { repo: string; "user-agent"?: string; sites: { - check?: "http" | "tcp-ping" | "ws"; + check?: "http" | "tcp-ping" | "ws" | "ssl"; method?: string; name: string; url: string; diff --git a/src/update.ts b/src/update.ts index a72dc7f7..5e25c45b 100644 --- a/src/update.ts +++ b/src/update.ts @@ -13,6 +13,7 @@ import { getOctokit } from "./helpers/github"; import { shouldContinue } from "./helpers/init-check"; import { sendNotification } from "./helpers/notifme"; import { ping } from "./helpers/ping"; +import { checkTls } from "./helpers/check-tls"; import { curl } from "./helpers/request"; import { getOwnerRepo, getSecret } from "./helpers/secrets"; import { SiteHistory } from "./interfaces"; @@ -194,6 +195,41 @@ export const update = async (shouldCommit = false) => { console.log("ERROR Got pinging error", error); return { result: { httpCode: 0 }, responseTime: (0).toFixed(0), status: "down" }; } + } else if (site.check === "ssl") { + console.log("Using check-tls instead of curl"); + try { + let status: "up" | "down" | "degraded" = "up"; + // https://github.com/upptime/upptime/discussions/888 + const url = replaceEnvironmentVariables(site.url); + let address = url; + if (isIP(url)) { + if (site.ipv6 && !isIPv6(url)) + throw new Error("Site URL must be IPv6 for ipv6 check"); + } + + const tcpResult = await checkTls({ + address, + attempts: 5, + port: Number(replaceEnvironmentVariables(site.port ? String(site.port) : "")), + }); + if ( + tcpResult.results.every( + (result) => Object.prototype.toString.call((result as any).err) === "[object Error]" + ) + ) + throw Error("all attempts failed"); + console.log("Got result", tcpResult); + let responseTime = (tcpResult.avg || 0).toFixed(0); + if (parseInt(responseTime) > (site.maxResponseTime || 60000)) status = "degraded"; + return { + result: { httpCode: 200 }, + responseTime, + status, + }; + } catch (error) { + console.log("ERROR Got pinging error", error); + return { result: { httpCode: 0 }, responseTime: (0).toFixed(0), status: "down" }; + } } else if (site.check === "ws") { console.log("Using websocket check instead of curl"); let success = false; From e44b6a32aa185abf8c495a241b53f2f5c579daa2 Mon Sep 17 00:00:00 2001 From: JFronny Date: Sun, 5 Jan 2025 14:30:26 +0100 Subject: [PATCH 2/2] Reformat tests --- src/helpers/check-tls.spec.ts | 71 +++++++++++++++++------------------ 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/src/helpers/check-tls.spec.ts b/src/helpers/check-tls.spec.ts index 3c3e5c6e..b590eefa 100644 --- a/src/helpers/check-tls.spec.ts +++ b/src/helpers/check-tls.spec.ts @@ -1,44 +1,43 @@ -import { checkTls } from "./check-tls" +import { checkTls } from "./check-tls"; test("checkTls", async () => { - const tcpResult = await checkTls({ - address: "smtp.gmail.com", - port: 465 - }) - expect(tcpResult.results.every( - (result) => Object.prototype.toString.call((result as any).err) === "[object Error]" - )).toBe(false) - expect(tcpResult.avg || -1).toBeGreaterThan(0) - expect(tcpResult.avg || 0).toBeLessThan(60000) -}) + const tcpResult = await checkTls({ + address: "smtp.gmail.com", + port: 465, + }); + expect(tcpResult.results.every( + (result) => Object.prototype.toString.call((result as any).err) === "[object Error]" + )).toBe(false); + expect(tcpResult.avg || -1).toBeGreaterThan(0); + expect(tcpResult.avg || 0).toBeLessThan(60000); +}); test("checkTls2", async () => { - const tcpResult = await checkTls({ - address: "wrong.host.badssl.com", - }) - console.log(tcpResult) - expect(tcpResult.results.every( - (result) => Object.prototype.toString.call((result as any).err) === "[object Error]" - )).toBe(true) - expect(tcpResult.avg).toBe(0) -}) + const tcpResult = await checkTls({ + address: "wrong.host.badssl.com", + }); + expect(tcpResult.results.every( + (result) => Object.prototype.toString.call((result as any).err) === "[object Error]" + )).toBe(true); + expect(tcpResult.avg).toBe(0); +}); test("checkTls3", async () => { - const tcpResult = await checkTls({ - address: "expired.host.badssl.com", - }) - expect(tcpResult.results.every( - (result) => Object.prototype.toString.call((result as any).err) === "[object Error]" - )).toBe(true) - expect(tcpResult.avg).toBe(0) -}) + const tcpResult = await checkTls({ + address: "expired.host.badssl.com", + }); + expect(tcpResult.results.every( + (result) => Object.prototype.toString.call((result as any).err) === "[object Error]" + )).toBe(true); + expect(tcpResult.avg).toBe(0); +}); test("checkTls4", async () => { - const tcpResult = await checkTls({ - address: "self-signed.badssl.com", - }) - expect(tcpResult.results.every( - (result) => Object.prototype.toString.call((result as any).err) === "[object Error]" - )).toBe(true) - expect(tcpResult.avg).toBe(0) -}) \ No newline at end of file + const tcpResult = await checkTls({ + address: "self-signed.badssl.com", + }); + expect(tcpResult.results.every( + (result) => Object.prototype.toString.call((result as any).err) === "[object Error]" + )).toBe(true); + expect(tcpResult.avg).toBe(0); +});