mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-12-05 01:10:52 +00:00
Feat: Add warning for cert. hostname mismatch (#3942)
Co-authored-by: Louis Lam <louislam@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -9,7 +9,7 @@ const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MI
|
||||
PING_PER_REQUEST_TIMEOUT_MIN, PING_PER_REQUEST_TIMEOUT_MAX, PING_PER_REQUEST_TIMEOUT_DEFAULT
|
||||
} = require("../../src/util");
|
||||
const { ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius,
|
||||
kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
||||
kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal, checkCertificateHostname
|
||||
} = require("../util-server");
|
||||
const { R } = require("redbean-node");
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
@@ -565,6 +565,7 @@ class Monitor extends BeanModel {
|
||||
tlsSocket.once("secureConnect", async () => {
|
||||
tlsInfo = checkCertificate(tlsSocket);
|
||||
tlsInfo.valid = tlsSocket.authorized || false;
|
||||
tlsInfo.hostnameMatchMonitorUrl = checkCertificateHostname(tlsInfo.certInfo.raw, this.getUrl()?.hostname);
|
||||
|
||||
await this.handleTlsInfo(tlsInfo);
|
||||
});
|
||||
@@ -587,6 +588,7 @@ class Monitor extends BeanModel {
|
||||
if (tlsSocket) {
|
||||
tlsInfo = checkCertificate(tlsSocket);
|
||||
tlsInfo.valid = tlsSocket.authorized || false;
|
||||
tlsInfo.hostnameMatchMonitorUrl = checkCertificateHostname(tlsInfo.certInfo.raw, this.getUrl()?.hostname);
|
||||
|
||||
await this.handleTlsInfo(tlsInfo);
|
||||
}
|
||||
|
||||
@@ -636,6 +636,30 @@ exports.checkCertificate = function (socket) {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the certificate is valid for the provided hostname.
|
||||
* Defaults to true if feature `X509Certificate` is not available, or input is not valid.
|
||||
* @param {Buffer} certBuffer - The certificate buffer.
|
||||
* @param {string} hostname - The hostname to compare against.
|
||||
* @returns {boolean} True if the certificate is valid for the provided hostname, false otherwise.
|
||||
*/
|
||||
exports.checkCertificateHostname = function (certBuffer, hostname) {
|
||||
let X509Certificate;
|
||||
try {
|
||||
X509Certificate = require("node:crypto").X509Certificate;
|
||||
} catch (_) {
|
||||
// X509Certificate is not available in this version of Node.js
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!X509Certificate || !certBuffer || !hostname) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let certObject = new X509Certificate(certBuffer);
|
||||
return certObject.checkHost(hostname) !== undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the provided status code is within the accepted ranges
|
||||
* @param {number} status The status code to check
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
faArrowUp,
|
||||
faCog,
|
||||
faEdit,
|
||||
faExclamationTriangle,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faList,
|
||||
@@ -60,6 +61,7 @@ library.add(
|
||||
faArrowUp,
|
||||
faCog,
|
||||
faEdit,
|
||||
faExclamationTriangle,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faList,
|
||||
|
||||
@@ -445,6 +445,7 @@
|
||||
"Query": "Query",
|
||||
"settingsCertificateExpiry": "TLS Certificate Expiry",
|
||||
"certificationExpiryDescription": "HTTPS Monitors trigger notification when TLS certificate expires in:",
|
||||
"certHostnameMismatch": "Certificate hostname does not match the monitor URL.",
|
||||
"Setup Docker Host": "Set Up Docker Host",
|
||||
"Connection Type": "Connection Type",
|
||||
"Docker Daemon": "Docker Daemon",
|
||||
|
||||
@@ -294,15 +294,8 @@
|
||||
/>)
|
||||
</p>
|
||||
<span class="col-4 col-sm-12 num">
|
||||
<a
|
||||
href="#"
|
||||
@click.prevent="
|
||||
toggleCertInfoBox = !toggleCertInfoBox
|
||||
"
|
||||
>{{ tlsInfo.certInfo.daysRemaining }}
|
||||
{{
|
||||
$tc("day", tlsInfo.certInfo.daysRemaining)
|
||||
}}</a>
|
||||
<a href="#" @click.prevent="toggleCertInfoBox = !toggleCertInfoBox">{{ tlsInfo.certInfo.daysRemaining }} {{ $tc("day", tlsInfo.certInfo.daysRemaining) }}</a>
|
||||
<font-awesome-icon v-if="tlsInfo.hostnameMatchMonitorUrl === false" class="cert-info-warn" icon="exclamation-triangle" :title="$t('certHostnameMismatch')" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1140,4 +1133,14 @@ table {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.cert-info-warn {
|
||||
margin-left: 4px;
|
||||
opacity: 0.5;
|
||||
|
||||
.dark & {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
47
test/backend-test/test-cert-hostname-match.js
Normal file
47
test/backend-test/test-cert-hostname-match.js
Normal file
@@ -0,0 +1,47 @@
|
||||
const { test } = require("node:test");
|
||||
|
||||
const assert = require("node:assert");
|
||||
|
||||
const { checkCertificateHostname } = require("../../server/util-server");
|
||||
|
||||
const testCert = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFCTCCA/GgAwIBAgISBEROD0/r+BjpW4TvWCcZYxjpMA0GCSqGSIb3DQEBCwUA
|
||||
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
|
||||
EwJSMzAeFw0yMzA5MDQxMjExMThaFw0yMzEyMDMxMjExMTdaMBQxEjAQBgNVBAMM
|
||||
CSouZWZmLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALywpmHr
|
||||
GOFlhw9CcW11fVloL6dceeUexbIwVd/gOt0/rIlgBViOGCh1pFYA/Essty4vXBzx
|
||||
cp6W4WurmwU6ZOJA0/T6rxnmsjxSdrHVGBGgW18HJ9IWqBl9MigjpRo9h4SlAPJq
|
||||
cAsiBfPhQ0oSe/8IqwgKA4HTvlcTf5/HKnbe0MyQt7WNILWHm+zpfLE0AmLVXxqA
|
||||
MNc/ynQDLTsWDZnqqri4MKOW1yOAMbUoAWSsNaagoGnZU4bg8uhu/2JTi/vdjl0g
|
||||
fTDOjsELc70cWekZ9Mv4ND4w3SEthotbMCCtZE5bUqcGzSm4pQEJ37kQ7xjJ0onT
|
||||
RRcuZI6/jDWzwZ0CAwEAAaOCAjUwggIxMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE
|
||||
FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
|
||||
hTqVTd8TZ2pknzGJtKw2JaIrPJAwHwYDVR0jBBgwFoAUFC6zF7dYVsuuUAlA5h+v
|
||||
nYsUwsYwVQYIKwYBBQUHAQEESTBHMCEGCCsGAQUFBzABhhVodHRwOi8vcjMuby5s
|
||||
ZW5jci5vcmcwIgYIKwYBBQUHMAKGFmh0dHA6Ly9yMy5pLmxlbmNyLm9yZy8wPwYD
|
||||
VR0RBDgwNoIJKi5lZmYub3JnghEqLnN0YWdpbmcuZWZmLm9yZ4IWd3d3Lmh0dHBz
|
||||
LXJ1bGVzZXRzLm9yZzATBgNVHSAEDDAKMAgGBmeBDAECATCCAQMGCisGAQQB1nkC
|
||||
BAIEgfQEgfEA7wB2ALc++yTfnE26dfI5xbpY9Gxd/ELPep81xJ4dCYEl7bSZAAAB
|
||||
imBRp0EAAAQDAEcwRQIhAMW3HZwWZWXPWfahH2pr/lxCcoSluHv2huAW6rlzU3zn
|
||||
AiAOzD/p8F3gT1bzDgdSW+X5WDBeU+EutRbHMSV+Cx0mZwB1AHoyjFTYty22IOo4
|
||||
4FIe6YQWcDIThU070ivBOlejUutSAAABimBRqRQAAAQDAEYwRAIgFXvRRZS3xx83
|
||||
XdTsnto5SxSnGi1+YfzYobMdV1yqHGACIDurLvkt58TwifUbyXflGZJmOMhcC2G1
|
||||
KUd29yCUjIahMA0GCSqGSIb3DQEBCwUAA4IBAQA6t2F3PKMLlb2A/JsQhPFUJLS3
|
||||
6cx+97dzROQLBdnUQIMxPkJBN/lltNdsVxJa4A3DMbrJOayefX2l8UIvFiEFVseF
|
||||
WrxbmXDF68fwhBKBgeqZ25/S8jEdP5PWYWXHgXvx0zRdhfe9vuba5WeFyz79cR7K
|
||||
t3bSyv6GMJ2z3qBkVFGHSeYakcxPWes3CNmGxInwZNBXA2oc7xuncFrjno/USzUI
|
||||
nEefDfF3H3jC+0iP3IpsK8orwgWz4lOkcMYdan733lSZuVJ6pm7C9phTV04NGF3H
|
||||
iPenGDCg1awOyRnvxNq1MtMDkR9AHwksukzwiYNexYjyvE2t0UzXhFXwazQ3
|
||||
-----END CERTIFICATE-----
|
||||
`;
|
||||
|
||||
test("Certificate and hostname match", () => {
|
||||
const result = checkCertificateHostname(testCert, "www.eff.org");
|
||||
assert.strictEqual(result, true);
|
||||
});
|
||||
|
||||
test("Certificate and hostname mismatch", () => {
|
||||
const result = checkCertificateHostname(testCert, "example.com");
|
||||
assert.strictEqual(result, false);
|
||||
});
|
||||
Reference in New Issue
Block a user