From 1490443618d6c389057b4877afe7cd0b9dde8352 Mon Sep 17 00:00:00 2001
From: Nelson Chan <3271800+chakflying@users.noreply.github.com>
Date: Wed, 24 Apr 2024 14:37:17 +0800
Subject: [PATCH] Fix: Getting TLS certificate through proxy & prometheus
 update (#4700)

---
 server/model/monitor.js | 48 ++++++++++++++++++++++++++++-------------
 server/prometheus.js    | 34 +++++++++++++++--------------
 2 files changed, 51 insertions(+), 31 deletions(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index cdc9f5324..2848a0cb9 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -512,10 +512,16 @@ class Monitor extends BeanModel {
                         }
                     }
 
-                    let tlsInfo;
-                    // Store tlsInfo when key material is received
-                    options.httpsAgent.on("keylog", (line, tlsSocket) => {
-                        tlsInfo = checkCertificate(tlsSocket);
+                    let tlsInfo = {};
+                    // Store tlsInfo when secureConnect event is emitted
+                    // The keylog event listener is a workaround to access the tlsSocket
+                    options.httpsAgent.once("keylog", async (line, tlsSocket) => {
+                        tlsSocket.once("secureConnect", async () => {
+                            tlsInfo = checkCertificate(tlsSocket);
+                            tlsInfo.valid = tlsSocket.authorized || false;
+
+                            await this.handleTlsInfo(tlsInfo);
+                        });
                     });
 
                     log.debug("monitor", `[${this.name}] Axios Options: ${JSON.stringify(options)}`);
@@ -527,19 +533,16 @@ class Monitor extends BeanModel {
                     bean.msg = `${res.status} - ${res.statusText}`;
                     bean.ping = dayjs().valueOf() - startTime;
 
-                    // Store certificate and check for expiry if https is used
-                    if (this.getUrl()?.protocol === "https:") {
-                        // No way to listen for the `secureConnection` event, so we do it here
-                        const tlssocket = res.request.res.socket;
+                    // fallback for if kelog event is not emitted, but we may still have tlsInfo,
+                    // e.g. if the connection is made through a proxy
+                    if (this.getUrl()?.protocol === "https:" && tlsInfo.valid === undefined) {
+                        const tlsSocket = res.request.res.socket;
 
-                        if (tlssocket) {
-                            tlsInfo.valid = tlssocket.authorized || false;
-                        }
+                        if (tlsSocket) {
+                            tlsInfo = checkCertificate(tlsSocket);
+                            tlsInfo.valid = tlsSocket.authorized || false;
 
-                        await this.updateTlsInfo(tlsInfo);
-                        if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) {
-                            log.debug("monitor", `[${this.name}] call checkCertExpiryNotifications`);
-                            await this.checkCertExpiryNotifications(tlsInfo);
+                            await this.handleTlsInfo(tlsInfo);
                         }
                     }
 
@@ -1679,6 +1682,21 @@ class Monitor extends BeanModel {
         const parentActive = await Monitor.isParentActive(parent.id);
         return parent.active && parentActive;
     }
+
+    /**
+     * Store TLS certificate information and check for expiry
+     * @param {Object} tlsInfo Information about the TLS connection
+     * @returns {Promise<void>}
+     */
+    async handleTlsInfo(tlsInfo) {
+        await this.updateTlsInfo(tlsInfo);
+        this.prometheus?.update(null, tlsInfo);
+
+        if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) {
+            log.debug("monitor", `[${this.name}] call checkCertExpiryNotifications`);
+            await this.checkCertExpiryNotifications(tlsInfo);
+        }
+    }
 }
 
 module.exports = Monitor;
diff --git a/server/prometheus.js b/server/prometheus.js
index dd04394ae..fa7f60855 100644
--- a/server/prometheus.js
+++ b/server/prometheus.js
@@ -79,23 +79,25 @@ class Prometheus {
             }
         }
 
-        try {
-            monitorStatus.set(this.monitorLabelValues, heartbeat.status);
-        } catch (e) {
-            log.error("prometheus", "Caught error");
-            log.error("prometheus", e);
-        }
-
-        try {
-            if (typeof heartbeat.ping === "number") {
-                monitorResponseTime.set(this.monitorLabelValues, heartbeat.ping);
-            } else {
-                // Is it good?
-                monitorResponseTime.set(this.monitorLabelValues, -1);
+        if (heartbeat) {
+            try {
+                monitorStatus.set(this.monitorLabelValues, heartbeat.status);
+            } catch (e) {
+                log.error("prometheus", "Caught error");
+                log.error("prometheus", e);
+            }
+
+            try {
+                if (typeof heartbeat.ping === "number") {
+                    monitorResponseTime.set(this.monitorLabelValues, heartbeat.ping);
+                } else {
+                    // Is it good?
+                    monitorResponseTime.set(this.monitorLabelValues, -1);
+                }
+            } catch (e) {
+                log.error("prometheus", "Caught error");
+                log.error("prometheus", e);
             }
-        } catch (e) {
-            log.error("prometheus", "Caught error");
-            log.error("prometheus", e);
         }
     }