54f607d063
control runs blackbox_exporter on loopback, probing each nameserver's public v4+v6 address for every zone: SOA (zone served) and DNSKEY (still signed, since blackbox has no DO-bit option). Probe definitions are shared between the exporter config and the VictoriaMetrics scrape jobs so they can't drift. Verified live against ns1/ns2 over v4 and v6.
109 lines
3.1 KiB
Nix
109 lines
3.1 KiB
Nix
# Blackbox DNS probe definitions, shared between the exporter module
|
|
# (modules/monitoring/blackbox.nix, which renders these into the blackbox
|
|
# config) and the scraper (modules/monitoring/server.nix, which turns them into
|
|
# VictoriaMetrics scrape jobs). Kept in one place so the module list and the
|
|
# scrape jobs can never drift apart.
|
|
#
|
|
# These query the nameservers' PUBLIC addresses, i.e. the path a real internet
|
|
# resolver takes, not the mesh — the whole point is to catch outside-in
|
|
# resolution failures the Knot stats can't see. For each zone we run two probes
|
|
# per endpoint: an SOA query (is the zone being served at all?) and a DNSKEY
|
|
# query (is it still DNSSEC-signed?). Blackbox has no DO-bit option, so we ask
|
|
# for DNSKEY directly — an authoritative signed zone returns it without EDNS0,
|
|
# and its absence means signing has broken.
|
|
{ lib }:
|
|
let
|
|
domains = import ../dns/domains.nix;
|
|
|
|
blackboxAddr = "127.0.0.1:9115";
|
|
|
|
# Public endpoints of the authoritative nameservers. The v4 addresses also
|
|
# appear in the `internet` instance in clan.nix; the v6 ones in each ns
|
|
# machine's cnx.staticIPv6. IPv6 literals are bracketed for host:port.
|
|
endpoints = [
|
|
{
|
|
instance = "ns1 v4";
|
|
target = "46.224.170.206:53";
|
|
}
|
|
{
|
|
instance = "ns1 v6";
|
|
target = "[2a01:4f8:c014:b5c5::1]:53";
|
|
}
|
|
{
|
|
instance = "ns2 v4";
|
|
target = "157.180.70.82:53";
|
|
}
|
|
{
|
|
instance = "ns2 v6";
|
|
target = "[2a01:4f9:c014:6d87::1]:53";
|
|
}
|
|
];
|
|
|
|
queries = [
|
|
{
|
|
name = "soa";
|
|
type = "SOA";
|
|
}
|
|
{
|
|
name = "dnskey";
|
|
type = "DNSKEY";
|
|
}
|
|
];
|
|
|
|
sanitize = lib.replaceStrings [ "." ] [ "_" ];
|
|
moduleName = zone: q: "dns_${q.name}_${sanitize zone}";
|
|
|
|
modules = lib.listToAttrs (
|
|
lib.concatMap (
|
|
zone:
|
|
map (
|
|
q:
|
|
lib.nameValuePair (moduleName zone q) {
|
|
prober = "dns";
|
|
timeout = "5s";
|
|
dns = {
|
|
query_name = "${zone}.";
|
|
query_type = q.type;
|
|
valid_rcodes = [ "NOERROR" ];
|
|
# Fail unless at least one answer RR of the queried type is present:
|
|
# a NOERROR with an empty answer (or a missing DNSKEY) still fails.
|
|
validate_answer_rrs.fail_if_not_matches_regexp = [ "\\s${q.type}\\s" ];
|
|
};
|
|
}
|
|
) queries
|
|
) domains
|
|
);
|
|
|
|
scrapeConfigs = lib.concatMap (
|
|
zone:
|
|
map (q: {
|
|
job_name = "blackbox_${moduleName zone q}";
|
|
metrics_path = "/probe";
|
|
params.module = [ (moduleName zone q) ];
|
|
static_configs = map (e: {
|
|
targets = [ e.target ];
|
|
labels = {
|
|
instance = e.instance;
|
|
zone = zone;
|
|
query = q.type;
|
|
};
|
|
}) endpoints;
|
|
# Hand the real DNS server to blackbox as ?target=, then point the scrape
|
|
# at the exporter itself.
|
|
relabel_configs = [
|
|
{
|
|
source_labels = [ "__address__" ];
|
|
target_label = "__param_target";
|
|
}
|
|
{
|
|
target_label = "__address__";
|
|
replacement = blackboxAddr;
|
|
}
|
|
];
|
|
}) queries
|
|
) domains;
|
|
in
|
|
{
|
|
inherit modules scrapeConfigs blackboxAddr;
|
|
}
|