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:
parent
b230ab0a06
commit
5bf9a51522
@ -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
|
PING_PER_REQUEST_TIMEOUT_MIN, PING_PER_REQUEST_TIMEOUT_MAX, PING_PER_REQUEST_TIMEOUT_DEFAULT
|
||||||
} = require("../../src/util");
|
} = require("../../src/util");
|
||||||
const { ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius,
|
const { ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius,
|
||||||
kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal, checkCertificateHostname
|
||||||
} = require("../util-server");
|
} = require("../util-server");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
@ -565,6 +565,7 @@ class Monitor extends BeanModel {
|
|||||||
tlsSocket.once("secureConnect", async () => {
|
tlsSocket.once("secureConnect", async () => {
|
||||||
tlsInfo = checkCertificate(tlsSocket);
|
tlsInfo = checkCertificate(tlsSocket);
|
||||||
tlsInfo.valid = tlsSocket.authorized || false;
|
tlsInfo.valid = tlsSocket.authorized || false;
|
||||||
|
tlsInfo.hostnameMatchMonitorUrl = checkCertificateHostname(tlsInfo.certInfo.raw, this.getUrl()?.hostname);
|
||||||
|
|
||||||
await this.handleTlsInfo(tlsInfo);
|
await this.handleTlsInfo(tlsInfo);
|
||||||
});
|
});
|
||||||
@ -587,6 +588,7 @@ class Monitor extends BeanModel {
|
|||||||
if (tlsSocket) {
|
if (tlsSocket) {
|
||||||
tlsInfo = checkCertificate(tlsSocket);
|
tlsInfo = checkCertificate(tlsSocket);
|
||||||
tlsInfo.valid = tlsSocket.authorized || false;
|
tlsInfo.valid = tlsSocket.authorized || false;
|
||||||
|
tlsInfo.hostnameMatchMonitorUrl = checkCertificateHostname(tlsInfo.certInfo.raw, this.getUrl()?.hostname);
|
||||||
|
|
||||||
await this.handleTlsInfo(tlsInfo);
|
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
|
* Check if the provided status code is within the accepted ranges
|
||||||
* @param {number} status The status code to check
|
* @param {number} status The status code to check
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
faArrowUp,
|
faArrowUp,
|
||||||
faCog,
|
faCog,
|
||||||
faEdit,
|
faEdit,
|
||||||
|
faExclamationTriangle,
|
||||||
faEye,
|
faEye,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
faList,
|
faList,
|
||||||
@ -60,6 +61,7 @@ library.add(
|
|||||||
faArrowUp,
|
faArrowUp,
|
||||||
faCog,
|
faCog,
|
||||||
faEdit,
|
faEdit,
|
||||||
|
faExclamationTriangle,
|
||||||
faEye,
|
faEye,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
faList,
|
faList,
|
||||||
|
|||||||
@ -445,6 +445,7 @@
|
|||||||
"Query": "Query",
|
"Query": "Query",
|
||||||
"settingsCertificateExpiry": "TLS Certificate Expiry",
|
"settingsCertificateExpiry": "TLS Certificate Expiry",
|
||||||
"certificationExpiryDescription": "HTTPS Monitors trigger notification when TLS certificate expires in:",
|
"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",
|
"Setup Docker Host": "Set Up Docker Host",
|
||||||
"Connection Type": "Connection Type",
|
"Connection Type": "Connection Type",
|
||||||
"Docker Daemon": "Docker Daemon",
|
"Docker Daemon": "Docker Daemon",
|
||||||
|
|||||||
@ -294,15 +294,8 @@
|
|||||||
/>)
|
/>)
|
||||||
</p>
|
</p>
|
||||||
<span class="col-4 col-sm-12 num">
|
<span class="col-4 col-sm-12 num">
|
||||||
<a
|
<a href="#" @click.prevent="toggleCertInfoBox = !toggleCertInfoBox">{{ tlsInfo.certInfo.daysRemaining }} {{ $tc("day", tlsInfo.certInfo.daysRemaining) }}</a>
|
||||||
href="#"
|
<font-awesome-icon v-if="tlsInfo.hostnameMatchMonitorUrl === false" class="cert-info-warn" icon="exclamation-triangle" :title="$t('certHostnameMismatch')" />
|
||||||
@click.prevent="
|
|
||||||
toggleCertInfoBox = !toggleCertInfoBox
|
|
||||||
"
|
|
||||||
>{{ tlsInfo.certInfo.daysRemaining }}
|
|
||||||
{{
|
|
||||||
$tc("day", tlsInfo.certInfo.daysRemaining)
|
|
||||||
}}</a>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1140,4 +1133,14 @@ table {
|
|||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cert-info-warn {
|
||||||
|
margin-left: 4px;
|
||||||
|
opacity: 0.5;
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</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);
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user