Skip to content

Commit

Permalink
Port zone verification from CreDNS to NSD
Browse files Browse the repository at this point in the history
Add support for verification of zones on updates. Allows the user to execute a
script or program when a zone is updated to verify if the contents is correct.
The verifier can keep the zone from being published if for example the zone was
incorrectly signed.
  • Loading branch information
Jeroen Koekkoek authored and k0ekk0ek committed Jun 14, 2022
1 parent dbfb828 commit 0293adf
Show file tree
Hide file tree
Showing 23 changed files with 1,385 additions and 81 deletions.
11 changes: 7 additions & 4 deletions Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,12 @@ MANUALS=nsd.8 nsd-checkconf.8 nsd-checkzone.8 nsd-control.8 nsd.conf.5

COMMON_OBJ=answer.o axfr.o ixfr.o ixfrcreate.o buffer.o configlexer.o configparser.o dname.o dns.o edns.o iterated_hash.o lookup3.o namedb.o nsec3.o options.o packet.o query.o rbtree.o radtree.o rdata.o region-allocator.o rrl.o siphash.o tsig.o tsig-openssl.o udb.o udbradtree.o udbzone.o util.o bitset.o popen3.o
XFRD_OBJ=xfrd-disk.o xfrd-notify.o xfrd-tcp.o xfrd.o remote.o $(DNSTAP_OBJ)
NSD_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) difffile.o ipc.o mini_event.o netio.o nsd.o server.o dbaccess.o dbcreate.o zlexer.o zonec.o zparser.o
NSD_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) difffile.o ipc.o mini_event.o netio.o nsd.o server.o dbaccess.o dbcreate.o zlexer.o zonec.o zparser.o verify.o
ALL_OBJ=$(NSD_OBJ) nsd-checkconf.o nsd-checkzone.o nsd-control.o nsd-mem.o xfr-inspect.o
NSD_CHECKCONF_OBJ=$(COMMON_OBJ) nsd-checkconf.o
NSD_CHECKZONE_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) dbaccess.o dbcreate.o difffile.o ipc.o mini_event.o netio.o server.o zonec.o zparser.o zlexer.o nsd-checkzone.o
NSD_CHECKZONE_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) dbaccess.o dbcreate.o difffile.o ipc.o mini_event.o netio.o server.o zonec.o zparser.o zlexer.o nsd-checkzone.o verify.o
NSD_CONTROL_OBJ=$(COMMON_OBJ) nsd-control.o
CUTEST_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) dbaccess.o dbcreate.o difffile.o ipc.o mini_event.o netio.o server.o zonec.o zparser.o zlexer.o cutest_dname.o cutest_dns.o cutest_iterated_hash.o cutest_run.o cutest_radtree.o cutest_rbtree.o cutest_namedb.o cutest_options.o cutest_region.o cutest_rrl.o cutest_udb.o cutest_udbrad.o cutest_util.o cutest_bitset.o cutest_popen3.o cutest_iter.o cutest_event.o cutest.o qtest.o
CUTEST_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) dbaccess.o dbcreate.o difffile.o ipc.o mini_event.o netio.o server.o verify.o zonec.o zparser.o zlexer.o cutest_dname.o cutest_dns.o cutest_iterated_hash.o cutest_run.o cutest_radtree.o cutest_rbtree.o cutest_namedb.o cutest_options.o cutest_region.o cutest_rrl.o cutest_udb.o cutest_udbrad.o cutest_util.o cutest_bitset.o cutest_popen3.o cutest_iter.o cutest_event.o cutest.o qtest.o
NSD_MEM_OBJ=$(COMMON_OBJ) $(XFRD_OBJ) dbaccess.o dbcreate.o difffile.o ipc.o mini_event.o netio.o server.o zonec.o zparser.o zlexer.o nsd-mem.o
all: $(TARGETS) $(MANUALS)

Expand Down Expand Up @@ -494,7 +494,7 @@ rrl.o: $(srcdir)/rrl.c config.h $(srcdir)/rrl.h $(srcdir)/query.h $(srcdir)/name
server.o: $(srcdir)/server.c config.h $(srcdir)/axfr.h $(srcdir)/nsd.h $(srcdir)/dns.h $(srcdir)/edns.h $(srcdir)/buffer.h \
$(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/query.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/radtree.h $(srcdir)/rbtree.h \
$(srcdir)/packet.h $(srcdir)/tsig.h $(srcdir)/netio.h $(srcdir)/xfrd.h $(srcdir)/options.h $(srcdir)/xfrd-tcp.h $(srcdir)/xfrd-disk.h \
$(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/dnstap/dnstap_collector.h $(srcdir)/rrl.h $(srcdir)/ixfr.h
$(srcdir)/difffile.h $(srcdir)/udb.h $(srcdir)/nsec3.h $(srcdir)/ipc.h $(srcdir)/remote.h $(srcdir)/lookup3.h $(srcdir)/dnstap/dnstap_collector.h $(srcdir)/rrl.h $(srcdir)/ixfr.h $(srcdir)/verify.h
siphash.o: $(srcdir)/siphash.c
tsig.o: $(srcdir)/tsig.c config.h $(srcdir)/tsig.h $(srcdir)/buffer.h $(srcdir)/region-allocator.h $(srcdir)/util.h $(srcdir)/dname.h \
$(srcdir)/tsig-openssl.h $(srcdir)/dns.h $(srcdir)/packet.h $(srcdir)/namedb.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/query.h $(srcdir)/nsd.h \
Expand All @@ -509,6 +509,9 @@ udbzone.o: $(srcdir)/udbzone.c config.h $(srcdir)/udbzone.h $(srcdir)/udb.h $(sr
util.o: $(srcdir)/util.c config.h $(srcdir)/util.h $(srcdir)/region-allocator.h $(srcdir)/dname.h $(srcdir)/buffer.h \
$(srcdir)/namedb.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/rbtree.h $(srcdir)/rdata.h $(srcdir)/zonec.h
bitset.o: $(srcdir)/bitset.c $(srcdir)/bitset.h
verify.o: $(srcdir)/verify.c config.h $(srcdir)/region-allocator.h $(srcdir)/namedb.h $(srcdir)/dname.h $(srcdir)/buffer.h \
$(srcdir)/util.h config.h $(srcdir)/dns.h $(srcdir)/rbtree.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/options.h $(srcdir)/difffile.h \
$(srcdir)/netio.h $(srcdir)/verify.h
xfrd.o: $(srcdir)/xfrd.c config.h $(srcdir)/xfrd.h $(srcdir)/rbtree.h $(srcdir)/region-allocator.h $(srcdir)/namedb.h \
$(srcdir)/dname.h $(srcdir)/buffer.h $(srcdir)/util.h $(srcdir)/dns.h $(srcdir)/radtree.h $(srcdir)/options.h $(srcdir)/tsig.h $(srcdir)/xfrd-tcp.h \
$(srcdir)/xfrd-disk.h $(srcdir)/xfrd-notify.h $(srcdir)/netio.h $(srcdir)/nsd.h $(srcdir)/edns.h $(srcdir)/packet.h $(srcdir)/rdata.h \
Expand Down
12 changes: 10 additions & 2 deletions configlexer.lex
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,8 @@ dnstap-log-auth-query-messages{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_D
dnstap-log-auth-response-messages{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_DNSTAP_LOG_AUTH_RESPONSE_MESSAGES; }
log-time-ascii{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_LOG_TIME_ASCII;}
round-robin{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ROUND_ROBIN;}
minimal-responses{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MINIMAL_RESPONSES;}
confine-to-zone{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_CONFINE_TO_ZONE;}
minimal-responses{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MINIMAL_RESPONSES;}
confine-to-zone{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_CONFINE_TO_ZONE;}
refuse-any{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_REFUSE_ANY;}
max-refresh-time{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MAX_REFRESH_TIME;}
min-refresh-time{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_MIN_REFRESH_TIME;}
Expand All @@ -303,6 +303,14 @@ cookie-secret{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_COOKIE_SECRET;}
cookie-secret-file{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_COOKIE_SECRET_FILE;}
xfrd-tcp-max{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_XFRD_TCP_MAX;}
xfrd-tcp-pipeline{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_XFRD_TCP_PIPELINE;}
verify{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_VERIFY; }
enable{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_ENABLE; }
verify-zone{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_VERIFY_ZONE; }
verify-zones{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_VERIFY_ZONES; }
verifier{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_VERIFIER; }
verifier-count{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_VERIFIER_COUNT; }
verifier-feed-zone{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_VERIFIER_FEED_ZONE; }
verifier-timeout{COLON} { LEXOUT(("v(%s) ", yytext)); return VAR_VERIFIER_TIMEOUT; }
{NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++;}

servers={UNQUOTEDLETTER}* {
Expand Down
107 changes: 106 additions & 1 deletion configparser.y
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ static int parse_boolean(const char *str, int *bln);
static int parse_expire_expr(const char *str, long long *num, uint8_t *expr);
static int parse_number(const char *str, long long *num);
static int parse_range(const char *str, long long *low, long long *high);

struct component {
struct component *next;
char *str;
};

%}

%union {
Expand All @@ -45,6 +51,8 @@ static int parse_range(const char *str, long long *low, long long *high);
struct ip_address_option *ip;
struct range_option *range;
struct cpu_option *cpu;
char **strv;
struct component *comp;
}

%token <str> STRING
Expand All @@ -53,6 +61,8 @@ static int parse_range(const char *str, long long *low, long long *high);
%type <ip> ip_address
%type <llng> service_cpu_affinity
%type <cpu> cpus
%type <strv> command
%type <comp> arguments

/* server */
%token VAR_SERVER
Expand Down Expand Up @@ -195,6 +205,16 @@ static int parse_range(const char *str, long long *low, long long *high);
%token VAR_BINDTODEVICE
%token VAR_SETFIB

/* verify */
%token VAR_VERIFY
%token VAR_ENABLE
%token VAR_VERIFY_ZONE
%token VAR_VERIFY_ZONES
%token VAR_VERIFIER
%token VAR_VERIFIER_COUNT
%token VAR_VERIFIER_FEED_ZONE
%token VAR_VERIFIER_TIMEOUT

%%

blocks:
Expand All @@ -208,7 +228,8 @@ block:
| key
| tls_auth
| pattern
| zone ;
| zone
| verify ;

server:
VAR_SERVER server_block ;
Expand Down Expand Up @@ -986,6 +1007,90 @@ pattern_or_zone_option:
{
cfg_parser->pattern->create_ixfr = $2;
cfg_parser->pattern->create_ixfr_is_default = 0;
}
| VAR_VERIFY_ZONE boolean
{ cfg_parser->pattern->verify_zone = $2; }
| VAR_VERIFIER command
{ cfg_parser->pattern->verifier = $2; }
| VAR_VERIFIER_FEED_ZONE boolean
{ cfg_parser->pattern->verifier_feed_zone = $2; }
| VAR_VERIFIER_TIMEOUT number
{ cfg_parser->pattern->verifier_timeout = $2; } ;

verify:
VAR_VERIFY verify_block ;

verify_block:
verify_block verify_option | ;

verify_option:
VAR_ENABLE boolean
{ cfg_parser->opt->verify_enable = $2; }
| VAR_IP_ADDRESS ip_address
{
struct ip_address_option *ip = cfg_parser->opt->verify_ip_addresses;
if(!ip) {
cfg_parser->opt->verify_ip_addresses = $2;
} else {
while(ip->next) { ip = ip->next; }
ip->next = $2;
}
}
| VAR_PORT number
{
/* port number, stored as a string */
char buf[16];
(void)snprintf(buf, sizeof(buf), "%lld", $2);
cfg_parser->opt->verify_port = region_strdup(cfg_parser->opt->region, buf);
}
| VAR_VERIFY_ZONES boolean
{ cfg_parser->opt->verify_zones = $2; }
| VAR_VERIFIER command
{ cfg_parser->opt->verifier = $2; }
| VAR_VERIFIER_COUNT number
{ cfg_parser->opt->verifier_count = (int)$2; }
| VAR_VERIFIER_TIMEOUT number
{ cfg_parser->opt->verifier_timeout = (int)$2; }
| VAR_VERIFIER_FEED_ZONE boolean
{ cfg_parser->opt->verifier_feed_zone = $2; } ;

command:
STRING arguments
{
char **argv;
size_t argc = 1;
for(struct component *i = $2; i; i = i->next) {
argc++;
}
argv = region_alloc_zero(
cfg_parser->opt->region, (argc + 1) * sizeof(char *));
argc = 0;
argv[argc++] = $1;
for(struct component *j, *i = $2; i; i = j) {
j = i->next;
argv[argc++] = i->str;
region_recycle(cfg_parser->opt->region, i, sizeof(*i));
}
$$ = argv;
} ;

arguments:
{ $$ = NULL; }
| arguments STRING
{
struct component *comp = region_alloc_zero(
cfg_parser->opt->region, sizeof(*comp));
comp->str = region_strdup(cfg_parser->opt->region, $2);
if($1) {
struct component *tail = $1;
while(tail->next) {
tail = tail->next;
}
tail->next = comp;
$$ = $1;
} else {
$$ = comp;
}
} ;

ip_address:
Expand Down
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,7 @@ AC_DEFINE_UNQUOTED([TLS_PORT], ["853"], [Define to the default DNS over TLS port
AC_DEFINE_UNQUOTED([MAXSYSLOGMSGLEN], [512], [Define to the maximum message length to pass to syslog.])
AC_DEFINE_UNQUOTED([NSD_CONTROL_PORT], [8952], [Define to the default nsd-control port.])
AC_DEFINE_UNQUOTED([NSD_CONTROL_VERSION], [1], [Define to nsd-control proto version.])
AC_DEFINE_UNQUOTED([VERIFY_PORT], ["5347"], [Define to the default zone verification udp port.])

dnl
dnl Determine the syslog facility to use
Expand Down
2 changes: 2 additions & 0 deletions dbaccess.c
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ namedb_zone_create(namedb_type* db, const dname_type* dname,
zone->zonestatid = 0;
zone->is_secure = 0;
zone->is_changed = 0;
zone->is_updated = 0;
zone->is_bad = 0;
zone->is_ok = 1;
return zone;
}
Expand Down
10 changes: 5 additions & 5 deletions difffile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,7 @@ apply_ixfr_for_zone(nsd_type* nsd, zone_type* zonedb, FILE* in,
if(zonedb) prehash_zone(nsd->db, zonedb);
#endif /* NSEC3 */
zonedb->is_changed = 1;
zonedb->is_updated = 1;
if(nsd->db->udb) {
assert(z.base);
ZONE(&z)->is_changed = 1;
Expand All @@ -1485,11 +1486,9 @@ apply_ixfr_for_zone(nsd_type* nsd, zone_type* zonedb, FILE* in,
"Zone %s contents is different from master, "
"starting AXFR. Transfer %s", zone_buf, log_buf);
/* add/del failures in IXFR, get an AXFR */
task_new_soainfo(
taskudb, last_task, zonedb, soainfo_gone);
} else if(taskudb) {
task_new_soainfo(
taskudb, last_task, zonedb, soainfo_ok);
diff_update_commit(
zone_buf, DIFF_INCONSISTENT, nsd, xfrfilenr);
exit(1);
}
if(ixfr_store)
ixfr_store_finish(ixfr_store, nsd, log_buf);
Expand Down Expand Up @@ -2200,6 +2199,7 @@ task_process_apply_xfr(struct nsd* nsd, udb_base* udb, udb_ptr *last_task,
if(!apply_ixfr_for_zone(nsd, zone, df, nsd->options, udb,
last_task, TASKLIST(task)->yesno)) {
/* there is no branch for xfrd failed-update */
task_new_soainfo(udb, last_task, zone, soainfo_gone);
}

fclose(df);
Expand Down
3 changes: 2 additions & 1 deletion difffile.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ int add_RR(namedb_type* db, const dname_type* dname,

enum soainfo_hint {
soainfo_ok,
soainfo_gone
soainfo_gone,
soainfo_bad
};

/* task udb structure */
Expand Down
97 changes: 97 additions & 0 deletions doc/README
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
3.5 ... Diagnosing NSD log entries
3.6 ... Interfaces
3.7 ... Tuning
3.8 ... Zone verification
4.0 Support and Feedback
4.1 ... Your Support

Expand Down Expand Up @@ -767,6 +768,102 @@ entirely on the hardware. cpu-affinity options are supported on Linux and
FreeBSD.


3.8 Zone verification

NSD can be configured to verify a zone is correct before publishing it. This
feature is primarily aimed at fortifying DNSSEC in the DNS
notify/transfer-chain, but can be used to carry out any checks desired.

An external verifier can be configured per zone. When a zone with verification
enabled is received or updated via an (incremental) zone transfer, it will be
submitted to the verifier for evaluation. If the verifier deems the updated
zone correct (indicated with exit status 0), the zone will be served. NSD will
discard the update and continue to serve the zone before the update if the
exit status of the verifier is non-zero.

Verifier options can be configured globally in the "verify:" clause, or
specifically for a zone/pattern in the respective "zone:" and "pattern:"
clauses. The global values are applied by default.

The zone can be provided to the verifier in two ways.

1. The complete zone can be fed to the standard input of the verifier.

This modus operandi is enabled by default and can be configured
with the "verifier-feed-zone:" option.

Examples for verifiers that read from the standard input are:
"ldns-verify-zone -V2" (-V2 to suppress copying to stdout) or
"validns -" (don't forget the dash (-) to read the zone from stdin).

2. The zone can be served to the verifier.

This is disabled by default and can be enabled by configuring ip-
addresses, with the "ip-address:" option in the "verify:" clause,
on which the zone to be assessed will be served. Addresses can
contain a port number to override the default, which is 5347 by
default, but can be overridden with the "port:" option in the
verify clause.

For example to validate the SOA of a zone example.com by querying,
with a certain DS record as the trust anchor (in file example.com.ds),
the "verifier:" option could have the following value:
"drill -S -k example.com.ds @localhost -p 5347 example.com SOA"

A verifier is informed about the domain name of the zone to be verified and
the accessibility of the system submitting the zone via environment variables.

VERIFY_ZONE
Domain name of the zone to be verified.

VERIFY_ZONE_ON_STDIN
Contains "yes" if the zone is fed over standard input,
otherwise "no".

VERIFY_IP_ADDRESSES
Contains a list of <ip-address>@<port>s on which the zone
to be verified can be queried.

VERIFY_IPV6_ADDRESS and VERIFY_IPV6_PORT
Contains the first configured IPv6 address and port.

VERIFY_IPV4_ADDRESS and VERIFY_IPV4_PORT
Contains the first configured IPv4 address and port.

VERIFY_IP_ADDRESS and VERIFY_PORT
Contains the first configured address and port.
IPv6 is preferred over IPv4.

For each zone one verifier will be run at the same time, but when multiple
to-be-verified zones are received, multiple verifiers may be run
simultaneously. The number of verifiers that may be run simultaneously is
configured with the "verifier-count:" option in the "verify:" clause and
defaults to 1.

The time a verifier may take can be configured with the "verifier-timeout:"
option in the "verify:" clause (to make the general default) or in the "zone:"
or "pattern:" clause to set it for a specific zone. When the time the verifier
takes exceeds the timeout value, the zone to be verified will be considered
bad. By default the value is 0, which means that the verifier may take as long
as it needs.

To enable verification for all zones.

verify:
enable: yes
verifier: <command>

To enable verification only for a specific zone.

verify:
enable: yes
verify-zones: no

zone:
name: example.com
verify-zone: yes


4.0 Support and Feedback

NLnet Labs is committed to support NSD and its other software products on
Expand Down
3 changes: 2 additions & 1 deletion doc/RELNOTES
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
NSD RELEASE NOTES

4.5.1 (upcoming)
4.6.0 (upcoming)
================
FEATURES:
- Port zone-verification from CreDNS to NSD4.
BUG FIXES:
- Fix static analyzer reports on ixfrcreate temp file.
- Fixup wrong ixfrcreate fread return check.
Expand Down
Loading

0 comments on commit 0293adf

Please sign in to comment.