New heuristic for disabling DNSSEC for domain-specific nameservers.

If there's no configured server for the parent  of the domain
covered by a domain-specific server, assume that said domain is
not signed. This extends the existing logic which makes similar
decisions for replies to DS queries from tghe parent server.
This commit is contained in:
Simon Kelley
2026-03-24 12:30:45 +00:00
parent 48348a2d77
commit 5be5dc1f16
3 changed files with 31 additions and 14 deletions

View File

@@ -1473,6 +1473,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
char *keyname, int class, int *validate_count);
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name,
char *keyname, int class, int *validate_count);
int cache_neg_ds(char *name, int flags, int class, time_t now, int neg_ttl);
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class,
int check_unsigned, int *neganswer, int *prim_ok, int *nons, int *nsec_ttl, int *validate_count);
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen);

View File

@@ -1027,21 +1027,21 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
(flags = in_arpa_name_2_addr(name, &a)) &&
((flags == F_IPV6 && private_net6(&a.addr6, 0)) || (flags == F_IPV4 && private_net(a.addr4, 0))))
{
my_syslog(LOG_INFO, _("Insecure reply received for DS %s, assuming that's OK for a RFC-1918 address."), name);
my_syslog(LOG_INFO, _("insecure reply received for DS %s, assuming that's OK for a RFC-1918 address"), name);
neganswer = 1;
nons = 0; /* If we're faking a DS, fake one with an NS. */
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
}
else if (lookup_domain(name, F_DOMAINSRV, NULL, NULL))
{
my_syslog(LOG_INFO, _("Insecure reply received for DS %s, assuming non-DNSSEC domain-specific server."), name);
my_syslog(LOG_INFO, _("insecure reply received for DS %s, assuming non-DNSSEC domain-specific server"), name);
neganswer = 1;
nons = 0; /* If we're faking a DS, fake one with an NS. */
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
}
else
{
my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
my_syslog(LOG_WARNING, _("insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure", 0);
return STAT_BOGUS | DNSSEC_FAIL_INDET;
}
@@ -1141,44 +1141,47 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
}
flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
if (neganswer)
{
if (RCODE(header) == NXDOMAIN)
flags |= F_NXDOMAIN;
/* We only cache validated DS records, DNSSECOK flag hijacked
to store presence/absence of NS. */
if (nons)
{
if (lookup_domain(name, F_DOMAINSRV, NULL, NULL))
{
my_syslog(LOG_WARNING, _("Negative DS reply without NS record received for %s, assuming non-DNSSEC domain-specific server."), name);
my_syslog(LOG_WARNING, _("negative DS reply without NS record received for %s, assuming non-DNSSEC domain-specific server"), name);
nons = 0;
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
}
else
/* We only cache validated DS records, DNSSECOK flag hijacked
to store presence/absence of NS. */
flags &= ~F_DNSSECOK;
}
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL,
servfail ? "SERVFAIL" : (nons ? "no DS/cut" : "no DS"), 0);
}
return cache_neg_ds(name, flags, class, now, neg_ttl);
}
int cache_neg_ds(char *name, int flags, int class, time_t now, int ttl)
{
cache_start_insert();
/* Use TTL from NSEC for negative cache entries */
if (!cache_insert(name, NULL, class, now, neg_ttl, flags))
if (!cache_insert(name, NULL, class, now, ttl, flags))
return STAT_ABANDONED;
cache_end_insert();
if (neganswer)
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL,
servfail ? "SERVFAIL" : (nons ? "no DS/cut" : "no DS"), 0);
return STAT_OK;
}
/* 4034 6.1 */
static int hostname_cmp(const char *a, const char *b)
{

View File

@@ -954,6 +954,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
/* As soon as anything returns BOGUS, we stop and unwind, to do otherwise
would invite infinite loops, since the answers to DNSKEY and DS queries
will not be cached, so they'll be repeated. */
ds_retry:
if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter);
else if (forward->flags & FREC_DS_QUERY)
@@ -1083,6 +1084,18 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
STAT_ISEQUAL(status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0);
return;
}
/* If there's no server for the parent of a domain-specific server's domain,
assume that said server's contents it legitimately unsigned, as if the parent
contained a negative DS record. This is part of the same logic that's found
in dnssec_validate_ds() when it gets a negative DS repsonse. */
if (STAT_ISEQUAL(status, STAT_NEED_DS) && serverind == -1 && lookup_domain(daemon->keyname, F_DOMAINSRV, NULL, NULL) &&
cache_neg_ds(daemon->keyname, F_FORWARD | F_DS | F_NEG | F_DNSSECOK, forward->class, now, DNSSEC_ASSUMED_DS_TTL) == STAT_OK)
{
my_syslog(LOG_WARNING, _("no server for parent domain of %s, assuming unsigned domain"), daemon->keyname);
blockdata_free(stash);
goto ds_retry;
}
/* error unwind */
free_rfds(&rfds);