From 957c2923074b29d1fedb6f02d147c86c97254937 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 23 Oct 2021 21:44:21 +0800 Subject: [PATCH 1/4] add certificate-notification job --- server/jobs.js | 6 +++++- server/jobs/certificate-notification.js | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 server/jobs/certificate-notification.js diff --git a/server/jobs.js b/server/jobs.js index 8a768b91b..5a92893ba 100644 --- a/server/jobs.js +++ b/server/jobs.js @@ -6,7 +6,11 @@ const jobs = [ { name: "clear-old-data", interval: "at 03:14", - } + }, + { + name: "certificate-notification", + interval: "60 seconds", // TODO: Production should be larger + }, ]; const initBackgroundJobs = function (args) { diff --git a/server/jobs/certificate-notification.js b/server/jobs/certificate-notification.js new file mode 100644 index 000000000..2bada3466 --- /dev/null +++ b/server/jobs/certificate-notification.js @@ -0,0 +1,13 @@ +const { log, exit, connectDb } = require("./util-worker"); +const { R } = require("redbean-node"); +const { setSetting, setting } = require("../util-server"); + +(async () => { + await connectDb(); + + console.log("Checking Certificate Expiry Date"); + + // TODO: Query monitor_tls_info + + exit(); +})(); From 44c1b336dc82885cab2c1703e61070d1808c18a0 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 27 Oct 2021 15:33:15 +0800 Subject: [PATCH 2/4] send certificate notifications in 21, 14, 7 days --- db/patch-notification_sent_history.sql | 18 ++++++ server/database.js | 1 + server/jobs.js | 4 -- server/jobs/certificate-notification.js | 13 ----- server/model/monitor.js | 77 +++++++++++++++++++++++-- 5 files changed, 92 insertions(+), 21 deletions(-) create mode 100644 db/patch-notification_sent_history.sql delete mode 100644 server/jobs/certificate-notification.js diff --git a/db/patch-notification_sent_history.sql b/db/patch-notification_sent_history.sql new file mode 100644 index 000000000..759eb3821 --- /dev/null +++ b/db/patch-notification_sent_history.sql @@ -0,0 +1,18 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +CREATE TABLE [notification_sent_history] ( + [id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + [type] VARCHAR(50) NOT NULL, + [monitor_id] INTEGER NOT NULL, + [days] INTEGER NOT NULL, + UNIQUE([type], [monitor_id], [days]) +); + +CREATE INDEX [good_index] ON [notification_sent_history] ( + [type], + [monitor_id], + [days] +); + +COMMIT; diff --git a/server/database.js b/server/database.js index 98494251d..41d91e858 100644 --- a/server/database.js +++ b/server/database.js @@ -51,6 +51,7 @@ class Database { "patch-monitor-push_token.sql": true, "patch-http-monitor-method-body-and-headers.sql": true, "patch-2fa-invalidate-used-token.sql": true, + "patch-notification_sent_history.sql": true, } /** diff --git a/server/jobs.js b/server/jobs.js index 5a92893ba..0469d5cab 100644 --- a/server/jobs.js +++ b/server/jobs.js @@ -7,10 +7,6 @@ const jobs = [ name: "clear-old-data", interval: "at 03:14", }, - { - name: "certificate-notification", - interval: "60 seconds", // TODO: Production should be larger - }, ]; const initBackgroundJobs = function (args) { diff --git a/server/jobs/certificate-notification.js b/server/jobs/certificate-notification.js deleted file mode 100644 index 2bada3466..000000000 --- a/server/jobs/certificate-notification.js +++ /dev/null @@ -1,13 +0,0 @@ -const { log, exit, connectDb } = require("./util-worker"); -const { R } = require("redbean-node"); -const { setSetting, setting } = require("../util-server"); - -(async () => { - await connectDb(); - - console.log("Checking Certificate Expiry Date"); - - // TODO: Query monitor_tls_info - - exit(); -})(); diff --git a/server/model/monitor.js b/server/model/monitor.js index 2f28e5b95..01d0a1f51 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -168,7 +168,14 @@ class Monitor extends BeanModel { let certInfoStartTime = dayjs().valueOf(); if (this.getUrl()?.protocol === "https:") { try { - tlsInfo = await this.updateTlsInfo(checkCertificate(res)); + let tlsInfoObject = checkCertificate(res); + tlsInfo = await this.updateTlsInfo(tlsInfoObject); + + if (!this.getIgnoreTls()) { + debug("call sendCertNotification"); + await this.sendCertNotification(tlsInfoObject); + } + } catch (e) { if (e.message !== "No TLS certificate in response") { console.error(e.message); @@ -595,9 +602,7 @@ class Monitor extends BeanModel { static async sendNotification(isFirstBeat, monitor, bean) { if (!isFirstBeat || bean.status === DOWN) { - let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [ - monitor.id, - ]); + const notificationList = await Monitor.getNotificationList(monitor); let text; if (bean.status === UP) { @@ -619,6 +624,70 @@ class Monitor extends BeanModel { } } + static async getNotificationList(monitor) { + let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [ + monitor.id, + ]); + return notificationList; + } + + async sendCertNotification(tlsInfoObject) { + if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) { + const notificationList = await Monitor.getNotificationList(this); + + debug("call sendCertNotificationByTargetDays"); + await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 21, notificationList); + await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 14, notificationList); + await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 7, notificationList); + } + } + + async sendCertNotificationByTargetDays(daysRemaining, targetDays, notificationList) { + + if (daysRemaining > targetDays) { + debug(`No need to send cert notification. ${daysRemaining} > ${targetDays}`); + return; + } + + if (notificationList.length > 0) { + + let row = await R.getRow("SELECT * FROM notification_sent_history WHERE type = ? AND monitor_id = ? AND days = ?", [ + "certificate", + this.id, + targetDays, + ]); + + // Sent already, no need to send again + if (row) { + debug("Sent already, no need to send again"); + return; + } + + let sent = false; + debug("Send certificate notification"); + + for (let notification of notificationList) { + try { + debug("Sending to " + notification.name); + await Notification.send(JSON.parse(notification.config), `The certificate of ${this.url} will be expired in ${daysRemaining} days`); + sent = true; + } catch (e) { + console.error("Cannot send cert notification to " + notification.name); + console.error(e); + } + } + + if (sent) { + await R.exec("INSERT INTO notification_sent_history (type, monitor_id, days) VALUES(?, ?, ?)", [ + "certificate", + this.id, + targetDays, + ]); + } + } else { + debug("No notification, no need to send cert notification"); + } + } } module.exports = Monitor; From f5f4835b743a4119517937083de80979d9fc29e1 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 27 Oct 2021 16:03:16 +0800 Subject: [PATCH 3/4] [certificate notification] clear sent history if the cert is changed --- server/model/monitor.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/server/model/monitor.js b/server/model/monitor.js index 01d0a1f51..7f56cd42e 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -455,6 +455,27 @@ class Monitor extends BeanModel { tls_info_bean = R.dispense("monitor_tls_info"); tls_info_bean.monitor_id = this.id; } + + // Clear sent history if the cert changed. + let oldCertInfo = JSON.parse(tls_info_bean.info_json); + + let isValidObjects = oldCertInfo && oldCertInfo.certInfo && checkCertificateResult && checkCertificateResult.certInfo; + + if (isValidObjects) { + if (oldCertInfo.certInfo.fingerprint256 !== checkCertificateResult.certInfo.fingerprint256) { + debug("Resetting sent_history"); + await R.exec("DELETE FROM notification_sent_history WHERE type = 'certificate' AND monitor_id = ?", [ + this.id + ]); + } else { + debug("No need to reset sent_history"); + debug(oldCertInfo.certInfo.fingerprint256); + debug(checkCertificateResult.certInfo.fingerprint256); + } + } else { + debug("Not valid object"); + } + tls_info_bean.info_json = JSON.stringify(checkCertificateResult); await R.store(tls_info_bean); From eb22ad5ffeee73eac830edb240e7fa88160aaee6 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 27 Oct 2021 16:12:18 +0800 Subject: [PATCH 4/4] [certificate notification] error handling and better msg --- server/model/monitor.js | 45 +++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 7f56cd42e..a1aa1656d 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -451,29 +451,34 @@ class Monitor extends BeanModel { let tls_info_bean = await R.findOne("monitor_tls_info", "monitor_id = ?", [ this.id, ]); + if (tls_info_bean == null) { tls_info_bean = R.dispense("monitor_tls_info"); tls_info_bean.monitor_id = this.id; - } - - // Clear sent history if the cert changed. - let oldCertInfo = JSON.parse(tls_info_bean.info_json); - - let isValidObjects = oldCertInfo && oldCertInfo.certInfo && checkCertificateResult && checkCertificateResult.certInfo; - - if (isValidObjects) { - if (oldCertInfo.certInfo.fingerprint256 !== checkCertificateResult.certInfo.fingerprint256) { - debug("Resetting sent_history"); - await R.exec("DELETE FROM notification_sent_history WHERE type = 'certificate' AND monitor_id = ?", [ - this.id - ]); - } else { - debug("No need to reset sent_history"); - debug(oldCertInfo.certInfo.fingerprint256); - debug(checkCertificateResult.certInfo.fingerprint256); - } } else { - debug("Not valid object"); + + // Clear sent history if the cert changed. + try { + let oldCertInfo = JSON.parse(tls_info_bean.info_json); + + let isValidObjects = oldCertInfo && oldCertInfo.certInfo && checkCertificateResult && checkCertificateResult.certInfo; + + if (isValidObjects) { + if (oldCertInfo.certInfo.fingerprint256 !== checkCertificateResult.certInfo.fingerprint256) { + debug("Resetting sent_history"); + await R.exec("DELETE FROM notification_sent_history WHERE type = 'certificate' AND monitor_id = ?", [ + this.id + ]); + } else { + debug("No need to reset sent_history"); + debug(oldCertInfo.certInfo.fingerprint256); + debug(checkCertificateResult.certInfo.fingerprint256); + } + } else { + debug("Not valid object"); + } + } catch (e) { } + } tls_info_bean.info_json = JSON.stringify(checkCertificateResult); @@ -690,7 +695,7 @@ class Monitor extends BeanModel { for (let notification of notificationList) { try { debug("Sending to " + notification.name); - await Notification.send(JSON.parse(notification.config), `The certificate of ${this.url} will be expired in ${daysRemaining} days`); + await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] Certificate will be expired in ${daysRemaining} days`); sent = true; } catch (e) { console.error("Cannot send cert notification to " + notification.name);