Skip to content

Commit f324a4e

Browse files
committed
feat: Implement DNS monitoring configuration and secret handling
1 parent f6a8cef commit f324a4e

File tree

5 files changed

+212
-1
lines changed

5 files changed

+212
-1
lines changed

APIReference/Service/DataTypeDetail.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,19 @@ const dataTypeDetails: Dictionary<DataTypePageData> = {
521521
},
522522
],
523523
},
524+
{
525+
name: "dnsMonitor",
526+
type: "MonitorStepDnsMonitor",
527+
required: false,
528+
description:
529+
"Configuration for DNS monitoring. Required for DNS monitor type. Defines query name (domain), record type, optional DNS server, port, timeout, and retry settings. See MonitorStepDnsMonitor.",
530+
typeLinks: [
531+
{
532+
label: "MonitorStepDnsMonitor",
533+
path: "monitor-step-dns-monitor",
534+
},
535+
],
536+
},
524537
],
525538
values: [],
526539
jsonExample: JSON.stringify(
@@ -2560,6 +2573,31 @@ const dataTypeDetails: Dictionary<DataTypePageData> = {
25602573
description:
25612574
"Whether the SNMP device is reachable. Use with 'True' or 'False'. Applies to: SNMP monitors.",
25622575
},
2576+
{
2577+
value: "DNS Response Time (in ms)",
2578+
description:
2579+
"The DNS query response time in milliseconds. Use with numeric FilterTypes. Applies to: DNS monitors.",
2580+
},
2581+
{
2582+
value: "DNS Is Online",
2583+
description:
2584+
"Whether the DNS resolution succeeded. Use with 'True' or 'False'. Applies to: DNS monitors.",
2585+
},
2586+
{
2587+
value: "DNS Record Value",
2588+
description:
2589+
"The value of a DNS record returned by the query. Use with string FilterTypes (Contains, EqualTo, etc.). Applies to: DNS monitors.",
2590+
},
2591+
{
2592+
value: "DNSSEC Is Valid",
2593+
description:
2594+
"Whether DNSSEC validation passed (AD flag present). Use with 'True' or 'False'. Applies to: DNS monitors.",
2595+
},
2596+
{
2597+
value: "DNS Record Exists",
2598+
description:
2599+
"Whether any DNS records were returned for the query. Use with 'True' or 'False'. Applies to: DNS monitors.",
2600+
},
25632601
{
25642602
value: "JavaScript Expression",
25652603
description:
@@ -3112,6 +3150,94 @@ const dataTypeDetails: Dictionary<DataTypePageData> = {
31123150
2,
31133151
),
31143152
},
3153+
"monitor-step-dns-monitor": {
3154+
title: "MonitorStepDnsMonitor",
3155+
description:
3156+
"Configuration for a DNS monitor step. Defines the domain to query, record type, optional custom DNS server, and timeout settings. Used as the 'dnsMonitor' property on a MonitorStep when the monitor type is 'DNS'. The criteria filters can then use 'DNS Is Online', 'DNS Response Time (in ms)', 'DNS Record Value', 'DNSSEC Is Valid', and 'DNS Record Exists' as CheckOn values.",
3157+
isEnum: false,
3158+
relatedTypes: [
3159+
{
3160+
name: "MonitorStep",
3161+
path: "monitor-step",
3162+
relationship: "Parent that holds this as dnsMonitor property",
3163+
},
3164+
{
3165+
name: "CheckOn",
3166+
path: "check-on",
3167+
relationship: "Use DNS-specific CheckOn values with DNS monitors",
3168+
},
3169+
],
3170+
properties: [
3171+
{
3172+
name: "queryName",
3173+
type: "string",
3174+
required: true,
3175+
description:
3176+
"The domain name to query (e.g., 'example.com').",
3177+
},
3178+
{
3179+
name: "recordType",
3180+
type: "string (enum)",
3181+
required: true,
3182+
description:
3183+
"The DNS record type to query. Possible values: 'A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'SOA', 'PTR', 'SRV', 'CAA'.",
3184+
},
3185+
{
3186+
name: "hostname",
3187+
type: "string",
3188+
required: false,
3189+
description:
3190+
"Custom DNS server to use for the query (e.g., '8.8.8.8'). Leave empty to use system default DNS resolver.",
3191+
},
3192+
{
3193+
name: "port",
3194+
type: "number",
3195+
required: false,
3196+
description: "DNS port. Default is 53.",
3197+
},
3198+
{
3199+
name: "timeout",
3200+
type: "number",
3201+
required: false,
3202+
description:
3203+
"Timeout for DNS queries in milliseconds. Default is 5000 (5 seconds).",
3204+
},
3205+
{
3206+
name: "retries",
3207+
type: "number",
3208+
required: false,
3209+
description: "Number of retries for failed DNS queries. Default is 3.",
3210+
},
3211+
],
3212+
values: [],
3213+
jsonExample: JSON.stringify(
3214+
{
3215+
"// Example 1: Basic A record lookup": {
3216+
queryName: "example.com",
3217+
recordType: "A",
3218+
timeout: 5000,
3219+
retries: 3,
3220+
},
3221+
"// Example 2: MX record with custom DNS server": {
3222+
queryName: "example.com",
3223+
recordType: "MX",
3224+
hostname: "8.8.8.8",
3225+
port: 53,
3226+
timeout: 5000,
3227+
retries: 3,
3228+
},
3229+
"// Example 3: TXT record for SPF verification": {
3230+
queryName: "example.com",
3231+
recordType: "TXT",
3232+
hostname: "1.1.1.1",
3233+
timeout: 5000,
3234+
retries: 3,
3235+
},
3236+
},
3237+
null,
3238+
2,
3239+
),
3240+
},
31153241
};
31163242

31173243
export default class ServiceHandler {

Common/Server/Services/MonitorService.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,14 @@ export class Service extends DatabaseService<Model> {
127127
monitorDestination = `${monitorDestination}:${port}`;
128128
}
129129
}
130+
131+
// For DNS monitors, use the queryName from dnsMonitor config
132+
if (monitorType === MonitorType.DNS && firstStep?.data?.dnsMonitor) {
133+
monitorDestination = firstStep.data.dnsMonitor.queryName || "";
134+
if (firstStep.data.dnsMonitor.hostname) {
135+
monitorDestination = `${monitorDestination} @${firstStep.data.dnsMonitor.hostname}`;
136+
}
137+
}
130138
}
131139
}
132140

Common/Server/Utils/Monitor/MonitorTemplateUtil.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import SyntheticMonitorResponse from "../../../Types/Monitor/SyntheticMonitors/S
1414
import SnmpMonitorResponse, {
1515
SnmpOidResponse,
1616
} from "../../../Types/Monitor/SnmpMonitor/SnmpMonitorResponse";
17+
import DnsMonitorResponse, {
18+
DnsRecordResponse,
19+
} from "../../../Types/Monitor/DnsMonitor/DnsMonitorResponse";
1720
import Typeof from "../../../Types/Typeof";
1821
import VMUtil from "../VM/VMAPI";
1922
import DataToProcess from "./DataToProcess";
@@ -240,6 +243,40 @@ export default class MonitorTemplateUtil {
240243
}
241244
}
242245
}
246+
247+
if (data.monitorType === MonitorType.DNS) {
248+
const dnsResponse: DnsMonitorResponse | undefined = (
249+
data.dataToProcess as ProbeMonitorResponse
250+
).dnsResponse;
251+
252+
storageMap = {
253+
isOnline: (data.dataToProcess as ProbeMonitorResponse).isOnline,
254+
responseTimeInMs: dnsResponse?.responseTimeInMs,
255+
failureCause: dnsResponse?.failureCause,
256+
isTimeout: dnsResponse?.isTimeout,
257+
isDnssecValid: dnsResponse?.isDnssecValid,
258+
} as JSONObject;
259+
260+
// Add DNS records
261+
if (dnsResponse?.records) {
262+
storageMap["records"] = dnsResponse.records.map(
263+
(record: DnsRecordResponse) => {
264+
return {
265+
type: record.type,
266+
value: record.value,
267+
ttl: record.ttl,
268+
};
269+
},
270+
);
271+
272+
// Add record values as a flat array for easier templating
273+
storageMap["recordValues"] = dnsResponse.records.map(
274+
(record: DnsRecordResponse) => {
275+
return record.value;
276+
},
277+
);
278+
}
279+
}
243280
} catch (err) {
244281
logger.error(err);
245282
}

Common/Utils/Monitor/MonitorMetricType.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ class MonitorMetricTypeUtil {
8585
monitorType === MonitorType.Ping ||
8686
monitorType === MonitorType.IP ||
8787
monitorType === MonitorType.Port ||
88-
monitorType === MonitorType.SNMP
88+
monitorType === MonitorType.SNMP ||
89+
monitorType === MonitorType.DNS
8990
) {
9091
return [MonitorMetricType.IsOnline, MonitorMetricType.ResponseTime];
9192
}

ProbeIngest/Utils/Monitor.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,45 @@ export default class MonitorUtil {
196196
}
197197
}
198198

199+
if (monitorType === MonitorType.DNS) {
200+
for (const monitorStep of monitorSteps?.data?.monitorStepsInstanceArray ||
201+
[]) {
202+
// Handle DNS hostname secrets (custom DNS server)
203+
if (
204+
monitorStep.data?.dnsMonitor?.hostname &&
205+
this.hasSecrets(monitorStep.data.dnsMonitor.hostname)
206+
) {
207+
if (!isSecretsLoaded) {
208+
monitorSecrets = await MonitorUtil.loadMonitorSecrets(monitorId);
209+
isSecretsLoaded = true;
210+
}
211+
212+
monitorStep.data.dnsMonitor.hostname =
213+
(await MonitorUtil.fillSecretsInStringOrJSON({
214+
secrets: monitorSecrets,
215+
populateSecretsIn: monitorStep.data.dnsMonitor.hostname,
216+
})) as string;
217+
}
218+
219+
// Handle DNS query name secrets
220+
if (
221+
monitorStep.data?.dnsMonitor?.queryName &&
222+
this.hasSecrets(monitorStep.data.dnsMonitor.queryName)
223+
) {
224+
if (!isSecretsLoaded) {
225+
monitorSecrets = await MonitorUtil.loadMonitorSecrets(monitorId);
226+
isSecretsLoaded = true;
227+
}
228+
229+
monitorStep.data.dnsMonitor.queryName =
230+
(await MonitorUtil.fillSecretsInStringOrJSON({
231+
secrets: monitorSecrets,
232+
populateSecretsIn: monitorStep.data.dnsMonitor.queryName,
233+
})) as string;
234+
}
235+
}
236+
}
237+
199238
return monitorSteps;
200239
}
201240

0 commit comments

Comments
 (0)