Skip to content

Commit

Permalink
Merge pull request #2594 from skaempfe/skaempfe#2593
Browse files Browse the repository at this point in the history
Improvement: Support TLS Expiry alerts also for CA certs in cert chain
  • Loading branch information
louislam authored May 28, 2023
2 parents 2114295 + 8ed2b59 commit 0735f12
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 45 deletions.
95 changes: 51 additions & 44 deletions server/model/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,8 @@ class Monitor extends BeanModel {
tlsInfo = await this.updateTlsInfo(tlsInfoObject);

if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) {
log.debug("monitor", `[${this.name}] call sendCertNotification`);
await this.sendCertNotification(tlsInfoObject);
log.debug("monitor", `[${this.name}] call checkCertExpiryNotifications`);
await this.checkCertExpiryNotifications(tlsInfoObject);
}

} catch (e) {
Expand Down Expand Up @@ -1212,24 +1212,39 @@ class Monitor extends BeanModel {
}

/**
* Send notification about a certificate
* checks certificate chain for expiring certificates
* @param {Object} tlsInfoObject Information about certificate
*/
async sendCertNotification(tlsInfoObject) {
async checkCertExpiryNotifications(tlsInfoObject) {
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
const notificationList = await Monitor.getNotificationList(this);

if (! notificationList.length > 0) {
// fail fast. If no notification is set, all the following checks can be skipped.
log.debug("monitor", "No notification, no need to send cert notification");
return;
}

let notifyDays = await setting("tlsExpiryNotifyDays");
if (notifyDays == null || !Array.isArray(notifyDays)) {
// Reset Default
setSetting("tlsExpiryNotifyDays", [ 7, 14, 21 ], "general");
notifyDays = [ 7, 14, 21 ];
}

if (notifyDays != null && Array.isArray(notifyDays)) {
for (const day of notifyDays) {
log.debug("monitor", "call sendCertNotificationByTargetDays", day);
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, day, notificationList);
if (Array.isArray(notifyDays)) {
for (const targetDays of notifyDays) {
let certInfo = tlsInfoObject.certInfo;
while (certInfo) {
let subjectCN = certInfo.subject["CN"];
if (certInfo.daysRemaining > targetDays) {
log.debug("monitor", `No need to send cert notification for ${certInfo.certType} certificate "${subjectCN}" (${certInfo.daysRemaining} days valid) on ${targetDays} deadline.`);
} else {
log.debug("monitor", `call sendCertNotificationByTargetDays for ${targetDays} deadline on certificate ${subjectCN}.`);
await this.sendCertNotificationByTargetDays(subjectCN, certInfo.certType, certInfo.daysRemaining, targetDays, notificationList);
}
certInfo = certInfo.issuerCertificate;
}
}
}
}
Expand All @@ -1238,55 +1253,47 @@ class Monitor extends BeanModel {
/**
* Send a certificate notification when certificate expires in less
* than target days
* @param {number} daysRemaining Number of days remaining on certifcate
* @param {string} certCN Common Name attribute from the certificate subject
* @param {string} certType certificate type
* @param {number} daysRemaining Number of days remaining on certificate
* @param {number} targetDays Number of days to alert after
* @param {LooseObject<any>[]} notificationList List of notification providers
* @returns {Promise<void>}
*/
async sendCertNotificationByTargetDays(daysRemaining, targetDays, notificationList) {
async sendCertNotificationByTargetDays(certCN, certType, daysRemaining, targetDays, notificationList) {

if (daysRemaining > targetDays) {
log.debug("monitor", `No need to send cert notification. ${daysRemaining} > ${targetDays}`);
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) {
log.debug("monitor", "Sent already, no need to send again");
return;
}

if (notificationList.length > 0) {
let sent = false;
log.debug("monitor", "Send certificate notification");

let row = await R.getRow("SELECT * FROM notification_sent_history WHERE type = ? AND monitor_id = ? AND days <= ?", [
for (let notification of notificationList) {
try {
log.debug("monitor", "Sending to " + notification.name);
await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] ${certType} certificate ${certCN} will be expired in ${daysRemaining} days`);
sent = true;
} catch (e) {
log.error("monitor", "Cannot send cert notification to " + notification.name);
log.error("monitor", e);
}
}

if (sent) {
await R.exec("INSERT INTO notification_sent_history (type, monitor_id, days) VALUES(?, ?, ?)", [
"certificate",
this.id,
targetDays,
]);

// Sent already, no need to send again
if (row) {
log.debug("monitor", "Sent already, no need to send again");
return;
}

let sent = false;
log.debug("monitor", "Send certificate notification");

for (let notification of notificationList) {
try {
log.debug("monitor", "Sending to " + notification.name);
await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] Certificate will expire in ${daysRemaining} days`);
sent = true;
} catch (e) {
log.error("monitor", "Cannot send cert notification to " + notification.name);
log.error("monitor", e);
}
}

if (sent) {
await R.exec("INSERT INTO notification_sent_history (type, monitor_id, days) VALUES(?, ?, ?)", [
"certificate",
this.id,
targetDays,
]);
}
} else {
log.debug("monitor", "No notification, no need to send cert notification");
}
}

Expand Down
4 changes: 4 additions & 0 deletions server/util-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -519,12 +519,16 @@ const parseCertificateInfo = function (info) {

// Move up the chain until loop is encountered
if (link.issuerCertificate == null) {
link.certType = (i === 0) ? "self-signed" : "root CA";
break;
} else if (link.issuerCertificate.fingerprint in existingList) {
// a root CA certificate is typically "signed by itself" (=> "self signed certificate") and thus the "issuerCertificate" is a reference to itself.
log.debug("cert", `[Last] ${link.issuerCertificate.fingerprint}`);
link.certType = (i === 0) ? "self-signed" : "root CA";
link.issuerCertificate = null;
break;
} else {
link.certType = (i === 0) ? "server" : "intermediate CA";
link = link.issuerCertificate;
}

Expand Down
2 changes: 1 addition & 1 deletion src/pages/Details.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Tag v-for="tag in monitor.tags" :key="tag.id" :item="tag" :size="'sm'" />
</div>
<p class="url">
<a v-if="monitor.type === 'http' || monitor.type === 'keyword' " :href="monitor.url" target="_blank" rel="noopener noreferrer">{{ filterPassword(monitor.url) }}</a>
<a v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'mp-health' " :href="monitor.url" target="_blank" rel="noopener noreferrer">{{ filterPassword(monitor.url) }}</a>
<span v-if="monitor.type === 'port'">TCP Port {{ monitor.hostname }}:{{ monitor.port }}</span>
<span v-if="monitor.type === 'ping'">Ping: {{ monitor.hostname }}</span>
<span v-if="monitor.type === 'keyword'">
Expand Down

0 comments on commit 0735f12

Please sign in to comment.