set up logging functionality using log-report-channel

the logging of error-report queries is no longer activated by
the view's "send-report-channel" option; that now only configures
the agent-domain value that is to be sent in authoritative
responses. the warning that was logged when "send-agent-domain"
was set to a value that is not a locally configured zone has
been removed.

error-report logging is now activated by the presence of an
authoritative zone with the "log-report-channel" option set to
"yes".  this is not permitted in the root zone.

NOTE: a zone with "log-report-channel yes;" should contain a
"*._er" wildcard, but that requirement is not yet enforced.
This commit is contained in:
Evan Hunt 2024-10-19 18:16:35 -07:00
parent 5519dd2669
commit d60324891c
9 changed files with 231 additions and 183 deletions

View file

@ -38,6 +38,12 @@ view main in {
file "example.com.db";
send-report-channel "rad.example.com";
};
zone rad.example.net {
type primary;
file "rad.db";
log-report-channel yes;
};
};
view alt chaos {

View file

@ -0,0 +1,23 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
$TTL 300 ; 5 minutes
@ IN SOA ns root (
2018010100 ; serial
1800 ; refresh (30 minutes)
1800 ; retry (30 minutes)
1814400 ; expire (3 weeks)
3600 ; minimum (1 hour)
)
NS ns
ns A 10.53.0.1
server A 10.53.0.100
*._er TXT "Report received"

View file

@ -212,5 +212,15 @@ grep "; Report-Channel: rad.example.net" dig.out.test$n >/dev/null && ret=1
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
n=$((n + 1))
echo_i "check that error report queries to non-logging zones are not logged ($n)"
ret=0
nextpart ns1/named.run >/dev/null
$DIG $DIGOPTS @10.53.0.1 _er.0.example.1._er.example.com TXT >dig.out.test$n
nextpart ns1/named.run | grep "dns-reporting-agent '_er.0.example.1._er.example.com/IN'" >/dev/null && ret=1
grep "; Report-Channel: rad.example.com" dig.out.test$n >/dev/null || ret=1
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1

View file

@ -11,7 +11,8 @@
* information regarding copyright ownership.
*/
options {
/* nonexistent zone used as agent-domain */
send-report-channel example.com;
zone "." {
type primary;
file "root.db";
log-report-channel yes;
};

View file

@ -772,16 +772,5 @@ if [ $ret != 0 ]; then
fi
status=$((status + ret))
n=$((n + 1))
echo_i "check that 'send-report-channel' warns if no matching zone exists ($n)"
ret=0
$CHECKCONF -z warn-rad.conf >checkconf.out$n 2>&1 || ret=1
grep -F "send-report-channel 'example.com' is not a primary or secondary zone" checkconf.out$n >/dev/null || ret=1
if [ $ret != 0 ]; then
echo_i "failed"
ret=1
fi
status=$((status + ret))
echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1

View file

@ -44,7 +44,7 @@
A catch-all for many things that still are not classified into categories.
``dns-reporting-agent``
Logs reports from clients that the there is an error in our responses.
Reports from clients indicating that there is an error in our responses.
``lame-servers``
Misconfigurations in remote servers, discovered by BIND 9 when trying to query those servers during resolution.

View file

@ -1680,44 +1680,6 @@ default is used.
If all supported digest types are disabled, the zones covered by
:any:`disable-ds-digests` are treated as insecure.
.. namedconf:statement:: send-report-channel
:tags: query
:short: Sets the Agent Domain value for the EDNS Report-Channel option
The EDNS Report-Channel option can be added to responses by an
authoritative server to inform clients of a domain name to which
operational and protocol errors may be reported. This can help
operators find out about configuration errors that are causing
problems with resolution or validation elsewhere (for example,
expired DNSSEC signatures).
When :any:`send-report-channel` is set in :namedconf:ref:`options`,
:namedconf:ref:`view`, or :namedconf:ref:`zone`, :iscman:`named` adds a
Report-Channel option to authoritative responses, using the specified
domain name as the Agent-Domain.
When it is set in :namedconf:ref:`options` or :namedconf:ref:`view` (but
*not* :namedconf:ref:`zone`), :iscman:`named` also logs any TXT queries
received for names matching the prescribed error-reporting format
(_er.<type>.<name>.<extended-rcode>._er.<agent-domain>) to the
``dns-reporting-agent`` logging category at level ``info``.
There should be a zone delegated to respond to these queries with TXT
records, to avoid unnecessarily query repetition. For example:
::
e.g.
$ORIGIN <agent-domain>
@ 600 SOA <namserver1> <contact-email> 0 0 0 0 600
@ 600 NS <nameserver1>
@ 600 NS <nameserver2>
*._er 600 TXT ""
If :any:`send-report-channel` is not set, or is set to ``.``, then
the EDNS option is not added to responses, and error-report queries are
not logged.
.. namedconf:statement:: dnssec-must-be-secure
:tags: deprecated
:short: Defines hierarchies that must or may not be secure (signed and validated).
@ -1957,6 +1919,32 @@ default is used.
:namedconf:ref:`zone` blocks, setting :any:`max-zone-ttl` to zero
is equivalent to "unlimited".
.. namedconf:statement:: send-report-channel
:tags: query
:short: Sets the Agent Domain value for the EDNS Report-Channel option
The EDNS Report-Channel option can be added to responses by an
authoritative server to inform clients of a domain name to which
operational and protocol errors may be reported. This can help
operators find out about configuration errors that are causing
problems with resolution or validation elsewhere (for example,
expired DNSSEC signatures).
When :any:`send-report-channel` is set in :namedconf:ref:`options`,
:namedconf:ref:`view`, or :namedconf:ref:`zone`, :iscman:`named` adds a
Report-Channel option to authoritative responses, using the specified
domain name as the Agent-Domain.
If :any:`send-report-channel` is not set, or is set to ``.``, then
the EDNS option is not added to responses.
A client that wishes to report an error can send a query for type TXT
to a name matching the prescribed RFC 9567 error-reporting format:
``_er.<type>.<name>.<extended-rcode>._er.<agent-domain>``.
There should be an authoritative zone configured to respond to such
queries, with the :any:`log-report-channel` option set to ``yes``.
.. namedconf:statement:: stale-answer-ttl
:tags: query
:short: Specifies the time to live (TTL) to be returned on stale answers, in seconds.
@ -7317,6 +7305,30 @@ Zone Options
The use of this option in :any:`zone` blocks is deprecated and
will be rendered non-operational in a future release.
.. namedconf:statement:: log-report-channel
:tags: logging
:short: Specifies whether to log error-report queries.
When this option is set to ``yes``, TXT queries for names
matching the prescribed RFC 9567 error-reporting format
(``_er.<type>.<name>.<extended-rcode>._er.<zone-name>``)
are logged to the ``dns-reporting-agent`` logging category at
level ``info``.
The zone should have a wildcard record in place to respond to such
queries. For example:
::
$ORIGIN <agent-domain>
@ 600 SOA <namserver1> <contact-email> 0 0 0 0 600
@ 600 NS <nameserver1>
@ 600 NS <nameserver2>
*._er 600 TXT "Report received"
:any:`send-report-channel`
See the description of :any:`send-report-channel` in :namedconf:ref:`options`.
.. _dynamic_update_policies:
Dynamic Update Policies

View file

@ -3910,6 +3910,22 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
}
}
/*
* "log-report-channel" cannot be set for the root zone.
*/
if (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_SECONDARY) {
obj = NULL;
tresult = cfg_map_get(zoptions, "log-report-channel", &obj);
if (tresult == ISC_R_SUCCESS && cfg_obj_asboolean(obj) &&
dns_name_equal(zname, dns_rootname))
{
cfg_obj_log(zconfig, ISC_LOG_ERROR,
"'log-report-channel' cannot be set in "
"the root zone");
result = ISC_R_FAILURE;
}
}
/*
* Check various options.
*/
@ -5070,73 +5086,28 @@ cleanup:
typedef enum { special_zonetype_rpz, special_zonetype_catz } special_zonetype_t;
static bool
iszone(const cfg_obj_t *nameobj, const char *what, const char *forview,
const char *viewname, int level, isc_symtab_t *symtab) {
char namebuf[DNS_NAME_FORMATSIZE];
const cfg_obj_t *obj = NULL;
const char *zonename = cfg_obj_asstring(nameobj);
const char *zonetype = "";
dns_fixedname_t fixed;
dns_name_t *name = dns_fixedname_initname(&fixed);
isc_result_t result;
isc_symvalue_t value;
if (viewname == NULL) {
viewname = "";
forview = "";
}
result = dns_name_fromstring(name, zonename, dns_rootname, 0, NULL);
if (result != ISC_R_SUCCESS) {
cfg_obj_log(nameobj, ISC_LOG_ERROR, "bad domain name '%s'",
zonename);
return (false);
}
dns_name_format(name, namebuf, sizeof(namebuf));
result = isc_symtab_lookup(symtab, namebuf, 3, &value);
if (result == ISC_R_SUCCESS) {
const cfg_obj_t *zoneobj = value.as_cpointer;
if (zoneobj != NULL && cfg_obj_istuple(zoneobj)) {
zoneobj = cfg_tuple_get(zoneobj, "options");
}
if (zoneobj != NULL && cfg_obj_ismap(zoneobj)) {
(void)cfg_map_get(zoneobj, "type", &obj);
}
if (obj != NULL) {
zonetype = cfg_obj_asstring(obj);
}
}
if (strcasecmp(zonetype, "primary") != 0 &&
strcasecmp(zonetype, "master") != 0 &&
strcasecmp(zonetype, "secondary") != 0 &&
strcasecmp(zonetype, "slave") != 0)
{
cfg_obj_log(nameobj, level,
"%s '%s'%s%s is not a primary or secondary zone",
what, zonename, forview, viewname);
return (false);
}
return (true);
}
static isc_result_t
check_rpz_catz(const char *rpz_catz, const cfg_obj_t *rpz_obj,
const char *viewname, isc_symtab_t *symtab,
special_zonetype_t specialzonetype) {
const cfg_listelt_t *element;
const cfg_obj_t *obj, *nameobj;
const cfg_obj_t *obj, *nameobj, *zoneobj;
const char *zonename, *zonetype;
const char *forview = " for view ";
isc_result_t result = ISC_R_SUCCESS;
isc_symvalue_t value;
isc_result_t result, tresult;
dns_fixedname_t fixed;
dns_name_t *name;
char namebuf[DNS_NAME_FORMATSIZE];
unsigned int num_zones = 0;
if (viewname == NULL) {
viewname = "";
forview = "";
}
result = ISC_R_SUCCESS;
name = dns_fixedname_initname(&fixed);
obj = cfg_tuple_get(rpz_obj, "zone list");
for (element = cfg_list_first(obj); element != NULL;
@ -5144,21 +5115,56 @@ check_rpz_catz(const char *rpz_catz, const cfg_obj_t *rpz_obj,
{
obj = cfg_listelt_value(element);
nameobj = cfg_tuple_get(obj, "zone name");
zonename = cfg_obj_asstring(nameobj);
zonetype = "";
if (specialzonetype == special_zonetype_rpz) {
if (++num_zones > 64) {
cfg_obj_log(nameobj, ISC_LOG_ERROR,
"more than 64 response policy "
"zones%s'%s'",
forview, viewname);
"zones in view '%s'",
viewname);
return (ISC_R_FAILURE);
}
}
if (!iszone(nameobj, rpz_catz, forview, viewname, ISC_LOG_ERROR,
symtab))
tresult = dns_name_fromstring(name, zonename, dns_rootname, 0,
NULL);
if (tresult != ISC_R_SUCCESS) {
cfg_obj_log(nameobj, ISC_LOG_ERROR,
"bad domain name '%s'", zonename);
if (result == ISC_R_SUCCESS) {
result = tresult;
}
continue;
}
dns_name_format(name, namebuf, sizeof(namebuf));
tresult = isc_symtab_lookup(symtab, namebuf, 3, &value);
if (tresult == ISC_R_SUCCESS) {
obj = NULL;
zoneobj = value.as_cpointer;
if (zoneobj != NULL && cfg_obj_istuple(zoneobj)) {
zoneobj = cfg_tuple_get(zoneobj, "options");
}
if (zoneobj != NULL && cfg_obj_ismap(zoneobj)) {
(void)cfg_map_get(zoneobj, "type", &obj);
}
if (obj != NULL) {
zonetype = cfg_obj_asstring(obj);
}
}
if (strcasecmp(zonetype, "primary") != 0 &&
strcasecmp(zonetype, "master") != 0 &&
strcasecmp(zonetype, "secondary") != 0 &&
strcasecmp(zonetype, "slave") != 0)
{
result = ISC_R_FAILURE;
cfg_obj_log(nameobj, ISC_LOG_ERROR,
"%s '%s'%s%s is not a primary or secondary "
"zone",
rpz_catz, zonename, forview, viewname);
if (result == ISC_R_SUCCESS) {
result = ISC_R_FAILURE;
}
}
}
return (result);
@ -5438,8 +5444,7 @@ check_viewconf(const cfg_obj_t *config, const cfg_obj_t *voptions,
/*
* Check that the response-policy and catalog-zones options
* refer to zones that exist. Also warn if send-report-channel
* is not a zone.
* refer to zones that exist.
*/
if (opts != NULL) {
obj = NULL;
@ -5461,14 +5466,6 @@ check_viewconf(const cfg_obj_t *config, const cfg_obj_t *voptions,
{
result = ISC_R_FAILURE;
}
obj = NULL;
if ((cfg_map_get(opts, "send-report-channel", &obj) ==
ISC_R_SUCCESS))
{
(void)iszone(obj, "send-report-channel", " for view ",
viewname, ISC_LOG_WARNING, symtab);
}
}
/*

View file

@ -5332,6 +5332,75 @@ root_key_sentinel_detect(query_ctx_t *qctx) {
}
}
static void
qctx_reportquery(query_ctx_t *qctx) {
ns_client_t *client = qctx->client;
client->attributes |= NS_CLIENTATTR_WANTRC;
/* If this isn't a report-logging zone, there's no more to do */
dns_zoneopt_t opts = dns_zone_getoptions(qctx->zone);
if ((opts & DNS_ZONEOPT_LOGREPORTS) == 0) {
return;
}
/*
* Suppress EDNS Report-Channel in responses from report-
* logging zones; this prevents infinite loops.
*/
client->attributes &= ~NS_CLIENTATTR_WANTRC;
/* If this isn't an error-report query, there's nothing more to do */
if (client->query.qtype != dns_rdatatype_txt ||
!dns_name_israd(client->query.qname,
dns_zone_getorigin(qctx->zone)))
{
return;
}
/*
* Check for TCP or a good server cookie. If neither, send
* back BADCOOKIE or TC=1.
*/
if (!TCP(client) && !HAVECOOKIE(client)) {
if (WANTCOOKIE(client)) {
client->attributes |= NS_CLIENTATTR_BADCOOKIE;
} else {
client->attributes |= NS_CLIENTATTR_NEEDTCP;
}
}
if (isc_log_wouldlog(ISC_LOG_INFO)) {
char classbuf[DNS_RDATACLASS_FORMATSIZE];
char namebuf[DNS_NAME_FORMATSIZE];
dns_name_format(client->query.qname, namebuf, sizeof(namebuf));
dns_rdataclass_format(client->view->rdclass, classbuf,
sizeof(classbuf));
isc_log_write(NS_LOGCATEGORY_DRA, NS_LOGMODULE_QUERY,
ISC_LOG_INFO, "dns-reporting-agent '%s/%s'",
namebuf, classbuf);
}
}
static void
qctx_setrad(query_ctx_t *qctx) {
ns_client_t *client = qctx->client;
/* Set the client to send a Report-Channel option when replying */
if ((client->attributes & NS_CLIENTATTR_WANTRC) != 0) {
dns_fixedname_t fixed;
dns_name_t *rad = dns_fixedname_initname(&fixed);
if (!dns_name_dynamic(&client->rad) &&
dns_zone_getrad(qctx->zone, rad) == ISC_R_SUCCESS)
{
dns_name_dup(rad, client->manager->mctx, &client->rad);
}
}
}
/*%
* Starting point for a client query or a chaining query.
*
@ -5518,8 +5587,6 @@ ns__query_start(query_ctx_t *qctx) {
if (qctx->is_zone) {
qctx->authoritative = true;
if (qctx->zone != NULL) {
dns_fixedname_t fixed;
dns_name_t *rad;
switch (dns_zone_gettype(qctx->zone)) {
case dns_zone_mirror:
qctx->authoritative = false;
@ -5529,16 +5596,8 @@ ns__query_start(query_ctx_t *qctx) {
break;
case dns_zone_primary:
case dns_zone_secondary:
rad = dns_fixedname_initname(&fixed);
if (!dns_name_dynamic(&qctx->client->rad) &&
dns_zone_getrad(qctx->zone, rad) ==
ISC_R_SUCCESS)
{
dns_name_dup(
rad,
qctx->client->manager->mctx,
&qctx->client->rad);
}
qctx_reportquery(qctx);
qctx_setrad(qctx);
break;
default:
break;
@ -11493,54 +11552,6 @@ cleanup:
return (result);
}
static void
log_reportchannel(ns_client_t *client) {
char classbuf[DNS_RDATACLASS_FORMATSIZE];
char namebuf[DNS_NAME_FORMATSIZE];
client->attributes |= NS_CLIENTATTR_WANTRC;
if (client->view->rad != NULL &&
dns_name_issubdomain(client->query.qname, client->view->rad))
{
/*
* Don't add Report-Channel to responses at or below the
* reporting agent domain to prevent infinite loops.
*/
client->attributes &= ~NS_CLIENTATTR_WANTRC;
}
if (client->query.qtype != dns_rdatatype_txt ||
client->view->rad == NULL ||
!dns_name_israd(client->query.qname, client->view->rad))
{
return;
}
/*
* Check for TCP or a good server cookie. If neither send
* back BADCOOKIE or TC=1.
*/
if (!TCP(client) && !HAVECOOKIE(client)) {
if (WANTCOOKIE(client)) {
client->attributes |= NS_CLIENTATTR_BADCOOKIE;
} else {
client->attributes |= NS_CLIENTATTR_NEEDTCP;
}
}
if (!isc_log_wouldlog(ISC_LOG_INFO)) {
return;
}
dns_name_format(client->query.qname, namebuf, sizeof(namebuf));
dns_rdataclass_format(client->view->rdclass, classbuf,
sizeof(classbuf));
isc_log_write(NS_LOGCATEGORY_DRA, NS_LOGMODULE_QUERY, ISC_LOG_INFO,
"dns-reporting-agent '%s/%s'", namebuf, classbuf);
}
static void
log_tat(ns_client_t *client) {
char namebuf[DNS_NAME_FORMATSIZE];
@ -11796,7 +11807,6 @@ ns_query_start(ns_client_t *client, isc_nmhandle_t *handle) {
dns_rdatatypestats_increment(client->manager->sctx->rcvquerystats,
qtype);
log_reportchannel(client);
log_tat(client);
if (dns_rdatatype_ismeta(qtype)) {