DNSSEC support and fixed multiple conditions for DNS monitor

This commit is contained in:
ekrekeler 2025-06-09 00:13:24 -05:00
parent 9c344ad371
commit 3ed4a2a2cb
No known key found for this signature in database
GPG key ID: 4C66C864B6B00854
10 changed files with 241 additions and 55 deletions

View file

@ -88,6 +88,7 @@ async function createTables() {
table.string("dns_resolve_server", 255); table.string("dns_resolve_server", 255);
table.string("dns_transport", 3); table.string("dns_transport", 3);
table.string("doh_query_path", 255); table.string("doh_query_path", 255);
table.boolean("skip_remote_dnssec").defaultTo(false);
table.string("dns_last_result", 255); table.string("dns_last_result", 255);
table.integer("retry_interval").notNullable().defaultTo(0); table.integer("retry_interval").notNullable().defaultTo(0);
table.string("push_token", 20).defaultTo(null); table.string("push_token", 20).defaultTo(null);

View file

@ -1,8 +1,9 @@
exports.up = function (knex) { exports.up = function (knex) {
return knex.schema return knex.schema
.alterTable("monitor", function (table) { .alterTable("monitor", function (table) {
table.string("dns_transport"); table.string("dns_transport").notNullable().defaultTo("UDP");
table.string("doh_query_path"); table.string("doh_query_path");
table.boolean("skip_remote_dnssec").defaultTo(false);
}); });
}; };
@ -10,5 +11,6 @@ exports.down = function (knex) {
return knex.schema.alterTable("monitor", function (table) { return knex.schema.alterTable("monitor", function (table) {
table.dropColumn("dns_transport"); table.dropColumn("dns_transport");
table.dropColumn("doh_query_path"); table.dropColumn("doh_query_path");
table.dropColumn("skip_remote_dnssec");
}); });
}; };

43
package-lock.json generated
View file

@ -27,6 +27,7 @@
"croner": "~8.1.0", "croner": "~8.1.0",
"dayjs": "~1.11.5", "dayjs": "~1.11.5",
"dev-null": "^0.1.1", "dev-null": "^0.1.1",
"dns-packet": "~5.6.1",
"dotenv": "~16.0.3", "dotenv": "~16.0.3",
"express": "~4.21.0", "express": "~4.21.0",
"express-basic-auth": "~1.2.1", "express-basic-auth": "~1.2.1",
@ -40,6 +41,7 @@
"http-proxy-agent": "~7.0.2", "http-proxy-agent": "~7.0.2",
"https-proxy-agent": "~7.0.6", "https-proxy-agent": "~7.0.6",
"iconv-lite": "~0.6.3", "iconv-lite": "~0.6.3",
"ip-address": "~10.0.1",
"isomorphic-ws": "^5.0.0", "isomorphic-ws": "^5.0.0",
"jsesc": "~3.0.2", "jsesc": "~3.0.2",
"jsonata": "^2.0.3", "jsonata": "^2.0.3",
@ -2745,6 +2747,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
"license": "MIT"
},
"node_modules/@louislam/ping": { "node_modules/@louislam/ping": {
"version": "0.4.4-mod.1", "version": "0.4.4-mod.1",
"resolved": "https://registry.npmjs.org/@louislam/ping/-/ping-0.4.4-mod.1.tgz", "resolved": "https://registry.npmjs.org/@louislam/ping/-/ping-0.4.4-mod.1.tgz",
@ -7671,6 +7679,18 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/dns-packet": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz",
"integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==",
"license": "MIT",
"dependencies": {
"@leichtgewicht/ip-codec": "^2.0.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/dns2": { "node_modules/dns2": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/dns2/-/dns2-2.0.5.tgz", "resolved": "https://registry.npmjs.org/dns2/-/dns2-2.0.5.tgz",
@ -10318,14 +10338,10 @@
} }
}, },
"node_modules/ip-address": { "node_modules/ip-address": {
"version": "9.0.5", "version": "10.0.1",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
"license": "MIT", "license": "MIT",
"dependencies": {
"jsbn": "1.1.0",
"sprintf-js": "^1.1.3"
},
"engines": { "engines": {
"node": ">= 12" "node": ">= 12"
} }
@ -15261,6 +15277,19 @@
"node": ">= 14" "node": ">= 14"
} }
}, },
"node_modules/socks/node_modules/ip-address": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
"license": "MIT",
"dependencies": {
"jsbn": "1.1.0",
"sprintf-js": "^1.1.3"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/sortablejs": { "node_modules/sortablejs": {
"version": "1.14.0", "version": "1.14.0",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",

View file

@ -120,6 +120,7 @@ class Monitor extends BeanModel {
dns_resolve_server: this.dns_resolve_server, dns_resolve_server: this.dns_resolve_server,
dns_transport: this.dns_transport, dns_transport: this.dns_transport,
doh_query_path: this.doh_query_path, doh_query_path: this.doh_query_path,
skip_remote_dnssec: this.skip_remote_dnssec,
dns_last_result: this.dns_last_result, dns_last_result: this.dns_last_result,
docker_container: this.docker_container, docker_container: this.docker_container,
docker_host: this.docker_host, docker_host: this.docker_host,

View file

@ -296,6 +296,11 @@ const defaultNumberOperators = [
operatorMap.get(OP_GTE) operatorMap.get(OP_GTE)
]; ];
const defaultArrayOperators = [
operatorMap.get(OP_CONTAINS),
operatorMap.get(OP_NOT_CONTAINS)
];
module.exports = { module.exports = {
OP_STR_EQUALS, OP_STR_EQUALS,
OP_STR_NOT_EQUALS, OP_STR_NOT_EQUALS,
@ -314,5 +319,6 @@ module.exports = {
operatorMap, operatorMap,
defaultStringOperators, defaultStringOperators,
defaultNumberOperators, defaultNumberOperators,
defaultArrayOperators,
ConditionOperator, ConditionOperator,
}; };

View file

@ -4,7 +4,7 @@ const dayjs = require("dayjs");
const { dnsResolve } = require("../util-server"); const { dnsResolve } = require("../util-server");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { ConditionVariable } = require("../monitor-conditions/variables"); const { ConditionVariable } = require("../monitor-conditions/variables");
const { defaultStringOperators } = require("../monitor-conditions/operators"); const { defaultStringOperators, defaultNumberOperators, defaultArrayOperators } = require("../monitor-conditions/operators");
const { ConditionExpressionGroup } = require("../monitor-conditions/expression"); const { ConditionExpressionGroup } = require("../monitor-conditions/expression");
const { evaluateExpressionGroup } = require("../monitor-conditions/evaluator"); const { evaluateExpressionGroup } = require("../monitor-conditions/evaluator");
@ -14,7 +14,25 @@ class DnsMonitorType extends MonitorType {
supportsConditions = true; supportsConditions = true;
conditionVariables = [ conditionVariables = [
new ConditionVariable("record", defaultStringOperators ), // A, AAAA, TXT, PTR, NS
new ConditionVariable("records", defaultArrayOperators),
// CNAME
new ConditionVariable("hostname", defaultStringOperators),
// CAA
new ConditionVariable("flags", defaultStringOperators),
new ConditionVariable("tag", defaultStringOperators),
new ConditionVariable("value", defaultStringOperators),
// MX
new ConditionVariable("hostnames", defaultArrayOperators),
// SOA
new ConditionVariable("mname", defaultStringOperators),
new ConditionVariable("rname", defaultStringOperators),
new ConditionVariable("serial", defaultStringOperators),
new ConditionVariable("refresh", defaultNumberOperators),
new ConditionVariable("retry", defaultNumberOperators),
new ConditionVariable("minimum", defaultNumberOperators),
// SRV
new ConditionVariable("targets", defaultArrayOperators),
]; ];
/** /**
@ -24,7 +42,13 @@ class DnsMonitorType extends MonitorType {
let startTime = dayjs().valueOf(); let startTime = dayjs().valueOf();
let dnsMessage = ""; let dnsMessage = "";
let dnsRes = await dnsResolve(monitor.hostname, monitor.dns_resolve_server, monitor.port, monitor.dns_resolve_type, monitor.dns_transport, monitor.doh_query_path); const requestData = {
name: monitor.hostname,
rrtype: monitor.dns_resolve_type,
dnssec: true, // Request DNSSEC information in the response
dnssecCheckingDisabled: monitor.skip_remote_dnssec,
};
let dnsRes = await dnsResolve(requestData, monitor.dns_resolve_server, monitor.port, monitor.dns_transport, monitor.doh_query_path);
const records = dnsRes.answers.map(record => { const records = dnsRes.answers.map(record => {
return Buffer.isBuffer(record.data) ? record.data.toString() : record.data; return Buffer.isBuffer(record.data) ? record.data.toString() : record.data;
}); });
@ -41,22 +65,22 @@ class DnsMonitorType extends MonitorType {
case "PTR": case "PTR":
case "NS": case "NS":
dnsMessage = records.join(" | "); dnsMessage = records.join(" | ");
conditionsResult = records.some(record => handleConditions({ record })); conditionsResult = handleConditions({ records: records });
break; break;
case "CNAME": case "CNAME":
dnsMessage = records[0]; dnsMessage = records[0];
conditionsResult = handleConditions({ record: records[0] }); conditionsResult = handleConditions({ hostname: records[0].value });
break; break;
case "CAA": case "CAA":
dnsMessage = records.map(record => `${record.flags} ${record.tag} "${record.value}"`).join(" | "); dnsMessage = records.map(record => `${record.flags} ${record.tag} "${record.value}"`).join(" | ");
conditionsResult = handleConditions({ record: records[0] }); conditionsResult = handleConditions(records[0]);
break; break;
case "MX": case "MX":
dnsMessage = records.map(record => `Hostname: ${record.exchange}; Priority: ${record.priority}`).join(" | "); dnsMessage = records.map(record => `Hostname: ${record.exchange}; Priority: ${record.priority}`).join(" | ");
conditionsResult = records.some(record => handleConditions({ record })); conditionsResult = handleConditions({ hostnames: records.map(record => record.exchange) });
break; break;
case "SOA": { case "SOA": {
@ -71,7 +95,7 @@ class DnsMonitorType extends MonitorType {
}).map(([ name, value ]) => { }).map(([ name, value ]) => {
return `${name}: ${value}`; return `${name}: ${value}`;
}).join("; "); }).join("; ");
conditionsResult = handleConditions({ record: records[0] }); conditionsResult = handleConditions(records[0]);
break; break;
} }
@ -86,7 +110,7 @@ class DnsMonitorType extends MonitorType {
return `${name}: ${value}`; return `${name}: ${value}`;
}).join("; "); }).join("; ");
}).join(" | "); }).join(" | ");
conditionsResult = records.some(record => handleConditions({ record })); conditionsResult = handleConditions({ targets: records.map(record => record.target) });
break; break;
} }

View file

@ -828,6 +828,7 @@ let needSetup = false;
bean.dns_resolve_server = monitor.dns_resolve_server; bean.dns_resolve_server = monitor.dns_resolve_server;
bean.dns_transport = monitor.dns_transport; bean.dns_transport = monitor.dns_transport;
bean.doh_query_path = monitor.doh_query_path; bean.doh_query_path = monitor.doh_query_path;
bean.skip_remote_dnssec = monitor.skip_remote_dnssec;
bean.pushToken = monitor.pushToken; bean.pushToken = monitor.pushToken;
bean.docker_container = monitor.docker_container; bean.docker_container = monitor.docker_container;
bean.docker_host = monitor.docker_host; bean.docker_host = monitor.docker_host;

View file

@ -4,6 +4,7 @@ const { R } = require("redbean-node");
const { log, genSecret, badgeConstants } = require("../src/util"); const { log, genSecret, badgeConstants } = require("../src/util");
const passwordHash = require("./password-hash"); const passwordHash = require("./password-hash");
const dnsPacket = require("dns-packet"); const dnsPacket = require("dns-packet");
const optioncodes = require("dns-packet/optioncodes.js");
const dgram = require("dgram"); const dgram = require("dgram");
const { Socket, isIP, isIPv4, isIPv6 } = require("net"); const { Socket, isIP, isIPv4, isIPv6 } = require("net");
const { Address6 } = require("ip-address"); const { Address6 } = require("ip-address");
@ -285,17 +286,99 @@ exports.httpNtlm = function (options, ntlmOptions) {
}); });
}; };
/**
* Adapted from https://github.com/hildjj/dohdec/blob/v7.0.2/pkg/dohdec/lib/dnsUtils.js
* Encode a DNS query packet to a buffer.
* @param {object} opts Options for the query.
* @param {string} opts.name The name to look up.
* @param {number} [opts.id=0] ID for the query. SHOULD be 0 for DOH.
* @param {packet.RecordType} [opts.rrtype="A"] The record type to look up.
* @param {boolean} [opts.dnssec=false] Request DNSSec information?
* @param {boolean} [opts.dnssecCheckingDisabled=false] Disable DNSSec
* validation?
* @param {string} [opts.ecsSubnet] Subnet to use for ECS.
* @param {number} [opts.ecs] Number of ECS bits. Defaults to 24 or 56
* (IPv4/IPv6).
* @param {boolean} [opts.stream=false] Encode for streaming, with the packet
* prefixed by a 2-byte big-endian integer of the number of bytes in the
* packet.
* @returns {Buffer} The encoded packet.
* @throws {TypeError} opts does not contain a name attribute.
*/
exports.makePacket = function (opts) {
const PAD_SIZE = 128;
if (!opts?.name) {
throw new TypeError("Name is required");
}
/** @type {dnsPacket.OptAnswer} */
const opt = {
name: ".",
type: "OPT",
udpPayloadSize: 4096,
extendedRcode: 0,
flags: 0,
flag_do: false, // Setting here has no effect
ednsVersion: 0,
options: [],
};
/** @type {dnsPacket.Packet} */
const dns = {
type: "query",
id: opts.id || 0,
flags: dnsPacket.RECURSION_DESIRED,
questions: [{
type: opts.rrtype || "A",
class: "IN",
name: opts.name,
}],
additionals: [ opt ],
};
//assert(dns.flags !== undefined);
if (opts.dnssec) {
dns.flags |= dnsPacket.AUTHENTIC_DATA;
opt.flags |= dnsPacket.DNSSEC_OK;
}
if (opts.dnssecCheckingDisabled) {
dns.flags |= dnsPacket.CHECKING_DISABLED;
}
if (
(opts.ecs != null) ||
(opts.ecsSubnet && (isIP(opts.ecsSubnet) !== 0))
) {
// https://tools.ietf.org/html/rfc7871#section-11.1
const prefix = (opts.ecsSubnet && isIPv4(opts.ecsSubnet)) ? 24 : 56;
opt.options.push({
code: optioncodes.toCode("CLIENT_SUBNET"),
ip: opts.ecsSubnet || "0.0.0.0",
sourcePrefixLength: (opts.ecs == null) ? prefix : opts.ecs,
});
}
const unpadded = dnsPacket.encodingLength(dns);
opt.options.push({
code: optioncodes.toCode("PADDING"),
// Next pad size, minus what we already have, minus another 4 bytes for
// the option header
length: (Math.ceil(unpadded / PAD_SIZE) * PAD_SIZE) - unpadded - 4,
});
if (opts.stream) {
return dnsPacket.streamEncode(dns);
}
return dnsPacket.encode(dns);
};
/** /**
* Resolves a given record using the specified DNS server * Resolves a given record using the specified DNS server
* @param {string} hostname The hostname of the record to lookup * @param {string} opts Options for the query
* @param {string} resolverServer The DNS server to use * @param {string} resolverServer The DNS server to use
* @param {string} resolverPort Port the DNS server is listening on * @param {string} resolverPort Port the DNS server is listening on
* @param {string} rrtype The type of record to request
* @param {string} transport The transport method to use * @param {string} transport The transport method to use
* @param {string} dohQuery The query path used only for DoH * @param {string} dohQuery The query path used only for DoH
* @returns {Promise<(string[] | object[] | object)>} DNS response * @returns {Promise<(string[] | object[] | object)>} DNS response
*/ */
exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype, transport, dohQuery) { exports.dnsResolve = function (opts, resolverServer, resolverPort, transport, dohQuery) {
// Parse IPv4 and IPv6 addresses to determine address family and // Parse IPv4 and IPv6 addresses to determine address family and
// add square brackets to IPv6 addresses, following RFC 3986 syntax // add square brackets to IPv6 addresses, following RFC 3986 syntax
resolverServer = resolverServer.replace("[", "").replace("]", ""); resolverServer = resolverServer.replace("[", "").replace("]", "");
@ -306,27 +389,23 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype, t
// If performing reverse (PTR) record lookup, ensure hostname // If performing reverse (PTR) record lookup, ensure hostname
// syntax follows RFC 1034 / RFC 3596 // syntax follows RFC 1034 / RFC 3596
if (rrtype === "PTR") { if (opts.rrtype === "PTR") {
if (isIPv4(hostname)) { if (isIPv4(opts.name)) {
let octets = hostname.split("."); let octets = opts.name.split(".");
octets.reverse(); octets.reverse();
hostname = octets.join(".") + ".in-addr.arpa"; opts.name = octets.join(".") + ".in-addr.arpa";
} else if (isIPv6(hostname)) { } else if (isIPv6(opts.name)) {
let address = new Address6(hostname); let address = new Address6(opts.name);
hostname = address.reverseForm(); opts.name = address.reverseForm();
} }
} }
// This is the DNS request data that will get encoded later // Set request ID
const requestData = { if (opts.id == null) {
type: "query", // Set query ID to "0" for HTTP cache friendlyness on DoH requests.
id: Math.floor(Math.random() * 65534) + 1, // See https://github.com/mafintosh/dns-packet/issues/77
flags: dnsPacket.RECURSION_DESIRED, opts.id = (transport.toUpperCase() === "DOH") ? 0 : Math.floor(Math.random() * 65534) + 1;
questions: [{ }
type: rrtype,
name: hostname,
}],
};
let client; let client;
let resolver = null; let resolver = null;
@ -336,7 +415,8 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype, t
case "TCP": case "TCP":
case "DOT": { case "DOT": {
const buf = dnsPacket.streamEncode(requestData); opts.stream = true;
const buf = exports.makePacket(opts);
if (isSecure) { if (isSecure) {
const options = { const options = {
port: resolverPort, port: resolverPort,
@ -387,10 +467,7 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype, t
} }
case "DOH": { case "DOH": {
// Set query ID to "0" for HTTP cache friendlyness. See const buf = exports.makePacket(opts);
// https://github.com/mafintosh/dns-packet/issues/77
requestData.id = 0;
const buf = dnsPacket.encode(requestData);
// TODO: implement POST requests for wireformat and JSON // TODO: implement POST requests for wireformat and JSON
dohQuery = dohQuery || "dns-query?dns={query}"; dohQuery = dohQuery || "dns-query?dns={query}";
dohQuery = dohQuery.replace("{query}", buf.toString("base64url")); dohQuery = dohQuery.replace("{query}", buf.toString("base64url"));
@ -436,7 +513,7 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype, t
//case "UDP": //case "UDP":
default: { default: {
const buf = dnsPacket.encode(requestData); const buf = exports.makePacket(opts);
client = dgram.createSocket("udp" + String(addressFamily)); client = dgram.createSocket("udp" + String(addressFamily));
client.on("connect", () => { client.on("connect", () => {
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`); log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);

View file

@ -143,7 +143,10 @@
"Test": "Test", "Test": "Test",
"Certificate Info": "Certificate Info", "Certificate Info": "Certificate Info",
"Resolver Server": "Resolver Server", "Resolver Server": "Resolver Server",
"Transport Method": "Transport Method",
"Query Path": "Query Path",
"Resource Record Type": "Resource Record Type", "Resource Record Type": "Resource Record Type",
"Skip Remote DNSSEC Verification": "Skip Remote DNSSEC Verification",
"Last Result": "Last Result", "Last Result": "Last Result",
"Create your admin account": "Create your admin account", "Create your admin account": "Create your admin account",
"Repeat Password": "Repeat Password", "Repeat Password": "Repeat Password",
@ -269,6 +272,7 @@
"Shrink Database": "Shrink Database", "Shrink Database": "Shrink Database",
"shrinkDatabaseDescriptionSqlite": "Trigger database {vacuum} for SQLite. {auto_vacuum} is already enabled but this does not defragment the database nor repack individual database pages the way that the {vacuum} command does.", "shrinkDatabaseDescriptionSqlite": "Trigger database {vacuum} for SQLite. {auto_vacuum} is already enabled but this does not defragment the database nor repack individual database pages the way that the {vacuum} command does.",
"Pick a RR-Type...": "Pick a RR-Type…", "Pick a RR-Type...": "Pick a RR-Type…",
"Select the transport method...": "Select the transport method…",
"Pick Accepted Status Codes...": "Pick Accepted Status Codes…", "Pick Accepted Status Codes...": "Pick Accepted Status Codes…",
"Default": "Default", "Default": "Default",
"HTTP Options": "HTTP Options", "HTTP Options": "HTTP Options",
@ -576,6 +580,7 @@
"rrtypeDescription": "Select the RR type you want to monitor", "rrtypeDescription": "Select the RR type you want to monitor",
"dnsTransportDescription": "Select the transport method for querying the DNS server.", "dnsTransportDescription": "Select the transport method for querying the DNS server.",
"dohQueryPathDescription": "Set the query path to use for DNS wireformat. Must contain", "dohQueryPathDescription": "Set the query path to use for DNS wireformat. Must contain",
"skipRemoteDnssecDescription": "Requests the resolver server not to perform DNSSEC verification against queried records.",
"pauseMonitorMsg": "Are you sure want to pause?", "pauseMonitorMsg": "Are you sure want to pause?",
"enableDefaultNotificationDescription": "This notification will be enabled by default for new monitors. You can still disable the notification separately for each monitor.", "enableDefaultNotificationDescription": "This notification will be enabled by default for new monitors. You can still disable the notification separately for each monitor.",
"clearEventsMsg": "Are you sure want to delete all events for this monitor?", "clearEventsMsg": "Are you sure want to delete all events for this monitor?",

View file

@ -377,17 +377,6 @@
</div> </div>
</div> </div>
<div v-if="dohSelected" class="my-3">
<label for="doh_query_path" class="form-label">{{ $t("Query Path") }}</label>
<div class="d-flex">
<label for="doh_query_path" class="px-2 fs-5">/</label>
<input id="doh_query_path" v-model="monitor.doh_query_path" type="text" class="form-control" :pattern="urlQueryRegex" placeholder="dns-query?dns={query}">
</div>
<div class="form-text">
{{ $t("dohQueryPathDescription") + " " }}{{ '"{query}".' }}
</div>
</div>
<div class="my-3"> <div class="my-3">
<label for="dns_transport" class="form-label">{{ $t("Transport Method") }}</label> <label for="dns_transport" class="form-label">{{ $t("Transport Method") }}</label>
<VueMultiselect <VueMultiselect
@ -398,7 +387,7 @@
:close-on-select="true" :close-on-select="true"
:clear-on-select="false" :clear-on-select="false"
:preserve-search="false" :preserve-search="false"
:placeholder="$t('Select the transport method')" :placeholder="$t('Select the transport method...')"
:preselect-first="false" :preselect-first="false"
:max-height="500" :max-height="500"
:taggable="false" :taggable="false"
@ -676,6 +665,30 @@
</div> </div>
</div> </div>
<!-- Advanced DNS monitor settings -->
<div v-if="monitor.type === 'dns'" class="my-3">
<div v-if="dohSelected">
<label for="doh_query_path" class="form-label">{{ $t("Query Path") }}</label>
<div class="d-flex">
<label for="doh_query_path" class="px-2 fs-5">/</label>
<input id="doh_query_path" v-model="monitor.doh_query_path" type="text" class="form-control" :pattern="urlQueryRegex" placeholder="dns-query?dns={query}">
</div>
<div class="form-text">
{{ $t("dohQueryPathDescription") + ' "{query}".' }}
</div>
</div>
<div class="form-check">
<input id="skip_remote_dnssec" v-model="monitor.skip_remote_dnssec" class="form-check-input" type="checkbox" value="">
<label class="form-check-label" for="skip_remote_dnssec">
{{ $t("Skip Remote DNSSEC Verification") }}
</label>
<div class="form-text">
{{ $t("skipRemoteDnssecDescription") }}
</div>
</div>
</div>
<div class="my-3 form-check"> <div class="my-3 form-check">
<input id="upside-down" v-model="monitor.upsideDown" class="form-check-input" type="checkbox"> <input id="upside-down" v-model="monitor.upsideDown" class="form-check-input" type="checkbox">
<label class="form-check-label" for="upside-down"> <label class="form-check-label" for="upside-down">
@ -1125,6 +1138,7 @@ const monitorDefaults = {
dns_resolve_type: "A", dns_resolve_type: "A",
dns_resolve_server: "1.1.1.1", dns_resolve_server: "1.1.1.1",
dns_transport: "UDP", dns_transport: "UDP",
skip_remote_dnssec: false,
docker_container: "", docker_container: "",
docker_host: null, docker_host: null,
proxyId: null, proxyId: null,
@ -1464,6 +1478,32 @@ message HealthCheckResponse {
}, },
conditionVariables() { conditionVariables() {
if (this.monitor.type === "dns") {
// When the monitor type is DNS, the conditions depend on
// record type. Condition variables are all added to a single
// array defined in server\monitor-types\dns.js in order to
// pass to Vue, then sliced below based on index.
const dnsConditionVariables = this.$root.monitorTypeList["dns"]?.conditionVariables;
switch (this.monitor.dns_resolve_type) {
case "A":
case "AAAA":
case "TXT":
case "PTR":
case "NS":
return dnsConditionVariables.slice(0, 1);
case "CNAME":
return dnsConditionVariables.slice(1, 2);
case "CAA":
return dnsConditionVariables.slice(2, 5);
case "MX":
return dnsConditionVariables.slice(5, 6);
case "SOA":
return dnsConditionVariables.slice(6, 12);
case "SRV":
return dnsConditionVariables.slice(12, 13);
}
return [];
}
return this.$root.monitorTypeList[this.monitor.type]?.conditionVariables || []; return this.$root.monitorTypeList[this.monitor.type]?.conditionVariables || [];
}, },