Merge pull request #781 from louislam/cert-notification

Certificate renewal notifications
This commit is contained in:
Louis Lam 2021-10-27 22:43:43 +08:00 committed by GitHub
commit b85c9186f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 119 additions and 5 deletions

View file

@ -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;

View file

@ -51,6 +51,7 @@ class Database {
"patch-monitor-push_token.sql": true, "patch-monitor-push_token.sql": true,
"patch-http-monitor-method-body-and-headers.sql": true, "patch-http-monitor-method-body-and-headers.sql": true,
"patch-2fa-invalidate-used-token.sql": true, "patch-2fa-invalidate-used-token.sql": true,
"patch-notification_sent_history.sql": true,
} }
/** /**

View file

@ -6,7 +6,7 @@ const jobs = [
{ {
name: "clear-old-data", name: "clear-old-data",
interval: "at 03:14", interval: "at 03:14",
} },
]; ];
const initBackgroundJobs = function (args) { const initBackgroundJobs = function (args) {

View file

@ -168,7 +168,14 @@ class Monitor extends BeanModel {
let certInfoStartTime = dayjs().valueOf(); let certInfoStartTime = dayjs().valueOf();
if (this.getUrl()?.protocol === "https:") { if (this.getUrl()?.protocol === "https:") {
try { 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) { } catch (e) {
if (e.message !== "No TLS certificate in response") { if (e.message !== "No TLS certificate in response") {
console.error(e.message); console.error(e.message);
@ -444,10 +451,36 @@ class Monitor extends BeanModel {
let tls_info_bean = await R.findOne("monitor_tls_info", "monitor_id = ?", [ let tls_info_bean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
this.id, this.id,
]); ]);
if (tls_info_bean == null) { if (tls_info_bean == null) {
tls_info_bean = R.dispense("monitor_tls_info"); tls_info_bean = R.dispense("monitor_tls_info");
tls_info_bean.monitor_id = this.id; tls_info_bean.monitor_id = this.id;
} else {
// 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); tls_info_bean.info_json = JSON.stringify(checkCertificateResult);
await R.store(tls_info_bean); await R.store(tls_info_bean);
@ -595,9 +628,7 @@ class Monitor extends BeanModel {
static async sendNotification(isFirstBeat, monitor, bean) { static async sendNotification(isFirstBeat, monitor, bean) {
if (!isFirstBeat || bean.status === DOWN) { 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 ", [ const notificationList = await Monitor.getNotificationList(monitor);
monitor.id,
]);
let text; let text;
if (bean.status === UP) { if (bean.status === UP) {
@ -619,6 +650,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), `[${this.name}][${this.url}] Certificate 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; module.exports = Monitor;