Commit a50d707f authored by Matthijs Mekking's avatar Matthijs Mekking 🏡

Introduce dnssec-policy configuration

This commit introduces the initial `dnssec-policy` configuration
statement. It has an initial set of options to deal with signature
and key maintenance.

Add some checks to ensure that dnssec-policy is configured at the
right locations, and that policies referenced to in zone statements
actually exist.

Add some checks that when a user adds the new `dnssec-policy`
configuration, it will no longer contain existing DNSSEC
configuration options.  Specifically: `inline-signing`,
`auto-dnssec`, `dnssec-dnskey-kskonly`, `dnssec-secure-to-insecure`,
`update-check-ksk`, `dnssec-update-mode`, `dnskey-sig-validity`,
and `sig-validity-interval`.

Test a good kasp configuration, and some bad configurations.
parent 1fbd8bb1
......@@ -842,6 +842,7 @@ view <replaceable>string</replaceable> [ <replaceable>class</replaceable> ] {
dnskey-sig-validity <replaceable>integer</replaceable>;
dnssec-dnskey-kskonly <replaceable>boolean</replaceable>;
dnssec-loadkeys-interval <replaceable>integer</replaceable>;
dnssec-policy <replaceable>string</replaceable>;
dnssec-secure-to-insecure <replaceable>boolean</replaceable>;
dnssec-update-mode ( maintain | no-resign );
file <replaceable>quoted_string</replaceable>;
......@@ -943,6 +944,7 @@ zone <replaceable>string</replaceable> [ <replaceable>class</replaceable> ] {
dnskey-sig-validity <replaceable>integer</replaceable>;
dnssec-dnskey-kskonly <replaceable>boolean</replaceable>;
dnssec-loadkeys-interval <replaceable>integer</replaceable>;
dnssec-policy <replaceable>string</replaceable>;
dnssec-secure-to-insecure <replaceable>boolean</replaceable>;
dnssec-update-mode ( maintain | no-resign );
file <replaceable>quoted_string</replaceable>;
......@@ -1008,6 +1010,21 @@ zone <replaceable>string</replaceable> [ <replaceable>class</replaceable> ] {
</literallayout>
</refsection>
<refsection><info><title>DNSSEC-POLICY</title></info>
<literallayout class="normal">
dnssec-policy <replaceable>string</replaceable> {
dnskey-ttl <replaceable>ttlval</replaceable>;
keys { ( csk | ksk | zsk ) key-directory <replaceable>duration</replaceable> <replaceable>integer</replaceable> [ <replaceable>integer</replaceable> ] ; ... };
publish-safety <replaceable>duration</replaceable>;
retire-safety <replaceable>duration</replaceable>;
signatures-refresh <replaceable>duration</replaceable>;
signatures-validity <replaceable>duration</replaceable>;
signatures-validity-dnskey <replaceable>duration</replaceable>;
};
</literallayout>
</refsection>
<refsection><info><title>FILES</title></info>
<para><filename>/etc/named.conf</filename>
......
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* 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 http://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
options {
dnssec-policy "notatzonelevel";
};
zone "example.net" {
type master;
file "example.db";
};
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* 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 http://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
include "good-kasp.conf";
// Bad zone configuration because this has dnssec-policy and other DNSSEC sign
// configuration options (auto-dnssec).
zone "example.net" {
type master;
file "example.db";
dnssec-policy "test";
auto-dnssec maintain;
allow-update { any; };
};
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* 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 http://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
include "good-kasp.conf";
// Bad zone configuration because this has dnssec-policy with no matching
// dnssec-policy configuration (good-kasp.conf has "test", zone refers to
// "nosuchpolicy".
zone "example.net" {
type master;
file "example.db";
dnssec-policy "nosuchpolicy";
};
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* 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 http://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
// Bad kasp configuration because this has an invalid duration for
// signatures-refresh.
dnssec-policy "badduration" {
signatures-refresh PT20Sabcd;
};
zone "example.net" {
type master;
file "example.db";
dnssec-policy "badduration";
};
......@@ -10,6 +10,7 @@
# information regarding copyright ownership.
rm -f good.conf.in good.conf.out badzero.conf *.out
rm -f good-kasp.conf.in
rm -rf test.keydir
rm -f checkconf.out*
rm -f diff.out*
......
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* 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 http://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
/*
* This is just a random selection of DNSSEC configuration options.
*/
/* cut here */
dnssec-policy "test" {
dnskey-ttl 3600;
keys {
ksk key-directory P1Y 13 256;
zsk key-directory P30D 13;
csk key-directory P30D 8 2048;
};
publish-safety PT3600S;
retire-safety PT3600S;
signatures-refresh P3D;
signatures-validity P2W;
signatures-validity-dnskey P14D;
zone-max-ttl 86400;
zone-propagation-delay PT5M;
parent-ds-ttl 7200;
parent-propagation-delay PT1H;
parent-registration-delay P1D;
};
options {
dnssec-policy "default";
};
zone "example1" {
type master;
dnssec-policy "test";
file "example1.db";
};
zone "example2" {
type master;
dnssec-policy "default";
file "example2.db";
};
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* 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 http://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
include "good-kasp.conf";
zone "nsec3.net" {
type master;
file "nsec3.db";
dnssec-policy "test";
auto-dnssec maintain;
dnskey-sig-validity 3600;
dnssec-dnskey-kskonly yes;
dnssec-secure-to-insecure yes;
dnssec-update-mode maintain;
inline-signing yes;
sig-validity-interval 3600;
update-check-ksk yes;
allow-update { any; };
};
......@@ -466,5 +466,38 @@ grep "'geoip-use-ecs' is obsolete" < checkconf.out$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; ret=1; fi
status=`expr $status + $ret`
n=`expr $n + 1`
echo_i "checking named-checkconf kasp warnings ($n)"
ret=0
$CHECKCONF kasp-and-other-dnssec-options.conf > checkconf.out$n 2>&1
grep "'auto-dnssec maintain;' cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1
grep "dnskey-sig-validity: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1
grep "dnssec-dnskey-kskonly: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1
grep "dnssec-secure-to-insecure: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1
grep "dnssec-update-mode: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1
grep "inline-signing: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1
grep "sig-validity-interval: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1
grep "update-check-ksk: cannot be configured if dnssec-policy is also set" < checkconf.out$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=`expr $status + $ret`
n=`expr $n + 1`
echo_i "check that a good 'kasp' configuration is accepted ($n)"
ret=0
$CHECKCONF good-kasp.conf > checkconf.out$n 2>/dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=`expr $status + $ret`
n=`expr $n + 1`
echo_i "checking that named-checkconf prints a known good kasp config ($n)"
ret=0
awk 'BEGIN { ok = 0; } /cut here/ { ok = 1; getline } ok == 1 { print }' good-kasp.conf > good-kasp.conf.in
[ -s good-kasp.conf.in ] || ret=1
$CHECKCONF -p good-kasp.conf.in | grep -v '^good-kasp.conf.in:' > good-kasp.conf.out 2>&1 || ret=1
cmp good-kasp.conf.in good-kasp.conf.out || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=`expr $status + $ret`
echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1
......@@ -3120,6 +3120,16 @@ $ORIGIN 0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.
</para>
</entry>
</row>
<row rowsep="0">
<entry colname="1">
<para><command>dnssec-policy</command></para>
</entry>
<entry colname="2">
<para>
describes a DNSSEC key and signing policy for zones.
</para>
</entry>
</row>
<row rowsep="0">
<entry colname="1">
<para><command>include</command></para>
......@@ -11004,6 +11014,147 @@ example.com CNAME rpz-tcp-only.
</para>
</section>
<section xml:id="dnssec_policy_grammar"><info><title><command>dnssec-policy</command> Statement Grammar</title></info>
<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="dnssec-policy.grammar.xml"/>
</section>
<section xml:id="dnssec_policy"><info><title><command>dnssec-policy</command> Statement Definition
and Usage</title></info>
<para>
The <command>dnssec-policy</command> statement defines a key and
signing policy (KASP) for zones.
</para>
<para>
KASP is used to determine how one or more zones need to be signed
with DNSSEC. For example, how often RRSIG records need to be
refreshed, or what cryptographic algorithms to use.
</para>
<para>
You can configure multiple policies. To attach a policy to a zone
simply add <userinput>dnssec-policy "policy_name"</userinput>
option to the <command>zone</command> statement with a matching
policy name.
</para>
<variablelist>
<varlistentry>
<term><command>dnskey-ttl</command></term>
<listitem>
<para>
The TTL of the DNSKEY resource records.
Default is <constant>3600</constant> seconds.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>keys</command></term>
<listitem>
<para>
A list of keys to use. Each line represents one key. Here is
an example (for illustration purposes only) of some possible
keys in a <command>dnssec-policy</command>:
</para>
<programlisting>keys {
ksk key-directory P5Y 8 2048;
zsk key-directory P30D 8;
csk key-directory P6MT12H3M15S 13;
};
</programlisting>
<para>
This example lists three keys. The first token determines
what RRsets the key will sign. If set to
<userinput>ksk</userinput> the key will sign the DNSKEY, CDS,
and CDNSKEY RRsets, if set to <userinput>zsk</userinput> the
key will sign the other RRsets, and if set to
<userinput>csk</userinput> the key will sign all RRsets.
</para>
<para>
The following part determines where the key will be stored.
Currently keys can only be stored in the configured
<command>key-directory</command>.
</para>
<para>
The third token tells how long the key may be used. In the
example the first key has a lifetime of 5 years, the second
key may be used for 30 days and the third key has a rather
peculiar lifetime of 6 months, 12 hours, 3 minutes and 15
seconds.
</para>
<para>
The last token(s) are the key's algorithm and algorithm length.
The length may be omitted as shown in the example for the
second and third key.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>publish-safety</command></term>
<listitem>
<para>
A margin that is added to the publish interval in key timing
equations to give some extra time to cover unforeseen events.
Default is <constant>PT5M</constant> (5 minutes).
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>retire-safety</command></term>
<listitem>
<para>
A margin that is added to the retire interval in key timing
equations to give some extra time to cover unforeseen events.
Default is <constant>PT5M</constant> (5 minutes).
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>signatures-refresh</command></term>
<listitem>
<para>
This determines when a RRSIG record needs to be refreshed.
The signatures is renewed when the time until the expiration
time is closer than <command>signatures-refresh</command>.
<command>signatures-resign</command> interval.
Default is <constant>P5D</constant> (5 days), meaning a
signature that will expire in 5 days or sooner will be
refreshed.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>signatures-validity</command></term>
<listitem>
<para>
The validity period of an RRSIG record (minus the inception
offset and jitter). Default is <constant>P2W</constant>
(2 weeks).
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>signatures-validity-dnskey</command></term>
<listitem>
<para>
Like <command>signatures-validity</command> but for DNSKEY
records. Default is <constant>P2W</constant> (2 weeks).
</para>
</listitem>
</varlistentry>
</variablelist>
</section>
<section xml:id="managed-keys"><info><title><command>managed-keys</command> Statement Grammar</title></info>
<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="managed-keys.grammar.xml"/>
</section>
......@@ -11878,6 +12029,17 @@ view "external" {
</listitem>
</varlistentry>
<varlistentry>
<term><command>dnssec-policy</command></term>
<listitem>
<para>
The key and signing policy for this zone. Set to
<userinput>"default"</userinput> if you want to make use
of the default policy.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>dnssec-update-mode</command></term>
<listitem>
......
<!--
- Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-
- 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 http://mozilla.org/MPL/2.0/.
-
- See the COPYRIGHT file distributed with this work for additional
- information regarding copyright ownership.
-->
<!-- Generated by doc/misc/docbook-options.pl -->
<programlisting>
<command>dnssec-policy</command> <replaceable>string</replaceable> {
<command>dnskey-ttl</command> <replaceable>ttlval</replaceable>;
<command>keys</command> { ( csk | ksk | zsk ) key-directory <replaceable>duration</replaceable> <replaceable>integer</replaceable> [ <replaceable>integer</replaceable> ] ; ... };
<command>publish-safety</command> <replaceable>duration</replaceable>;
<command>retire-safety</command> <replaceable>duration</replaceable>;
<command>signatures-refresh</command> <replaceable>duration</replaceable>;
<command>signatures-validity</command> <replaceable>duration</replaceable>;
<command>signatures-validity-dnskey</command> <replaceable>duration</replaceable>;
};
</programlisting>
......@@ -36,6 +36,7 @@
<command>dnskey-sig-validity</command> <replaceable>integer</replaceable>;
<command>dnssec-dnskey-kskonly</command> <replaceable>boolean</replaceable>;
<command>dnssec-loadkeys-interval</command> <replaceable>integer</replaceable>;
<command>dnssec-policy</command> <replaceable>string</replaceable>;
<command>dnssec-secure-to-insecure</command> <replaceable>boolean</replaceable>;
<command>dnssec-update-mode</command> ( maintain | no-resign );
<command>file</command> <replaceable>quoted_string</replaceable>;
......
......@@ -29,6 +29,7 @@
<command>dnskey-sig-validity</command> <replaceable>integer</replaceable>;
<command>dnssec-dnskey-kskonly</command> <replaceable>boolean</replaceable>;
<command>dnssec-loadkeys-interval</command> <replaceable>integer</replaceable>;
<command>dnssec-policy</command> <replaceable>string</replaceable>;
<command>dnssec-update-mode</command> ( maintain | no-resign );
<command>file</command> <replaceable>quoted_string</replaceable>;
<command>forward</command> ( first | only );
......
......@@ -122,7 +122,6 @@ dnssec-policy "nsec3" {
description "policy for zones that require zone walking mitigation";
// Signatures
signatures-resign PT2H;
signatures-refresh P3D;
signatures-validity P14D;
signatures-validity-dnskey P14D;
......
......@@ -23,6 +23,7 @@ zone <string> [ <class> ] {
dnskey-sig-validity <integer>;
dnssec-dnskey-kskonly <boolean>;
dnssec-loadkeys-interval <integer>;
dnssec-policy <string>;
dnssec-secure-to-insecure <boolean>;
dnssec-update-mode ( maintain | no-resign );
file <quoted_string>;
......
......@@ -25,6 +25,17 @@ dnssec-keys { <string> ( static-key |
initial-key ) <integer> <integer> <integer>
<quoted_string>; ... }; // may occur multiple times
dnssec-policy <string> {
dnskey-ttl <ttlval>;
keys { ( csk | ksk | zsk ) key-directory <duration> <string>
[ <integer> ]; ... };
publish-safety <duration>;
retire-safety <duration>;
signatures-refresh <duration>;
signatures-validity <duration>;
signatures-validity-dnskey <duration>;
}; // may occur multiple times
dyndb <string> <quoted_string> {
<unspecified-text> }; // may occur multiple times
......
......@@ -16,6 +16,7 @@ zone <string> [ <class> ] {
dnskey-sig-validity <integer>;
dnssec-dnskey-kskonly <boolean>;
dnssec-loadkeys-interval <integer>;
dnssec-policy <string>;
dnssec-update-mode ( maintain | no-resign );
file <quoted_string>;
forward ( first | only );
......
......@@ -856,6 +856,7 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
const char *str;
isc_buffer_t b;
uint32_t lifetime = 3600;
bool has_dnssecpolicy = false;
const char *ccalg = "siphash24";
/*
......@@ -948,6 +949,44 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
}
}
/*
* Check dnssec-policy at the view/options level
*/
obj = NULL;
(void)cfg_map_get(options, "dnssec-policy", &obj);
if (obj != NULL) {
bool bad_kasp = true;
if (optlevel == optlevel_zone && cfg_obj_isstring(obj)) {
bad_kasp = false;
} else if (optlevel == optlevel_config) {
if (cfg_obj_islist(obj)) {
for (element = cfg_list_first(obj);
element != NULL;
element = cfg_list_next(element))
{
if (!cfg_obj_istuple(
cfg_listelt_value(element)))
{
break;
}
}
bad_kasp = false;
}
}
if (bad_kasp) {
cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
"dnssec-policy may only be activated at "
"the top level and referenced to at the "
"zone level");
if (result == ISC_R_SUCCESS) {
result = ISC_R_FAILURE;
}
}
has_dnssecpolicy = true;
}
obj = NULL;
cfg_map_get(options, "max-rsa-exponent-size", &obj);
if (obj != NULL) {
......@@ -996,6 +1035,13 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
result = ISC_R_RANGE;
}
}
if (has_dnssecpolicy) {
cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
"sig-validity-interval: cannot be "
"configured if dnssec-policy is also set");
result = ISC_R_FAILURE;
}
}
obj = NULL;
......@@ -1012,6 +1058,12 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
result = ISC_R_RANGE;
}
if (has_dnssecpolicy) {
cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
"dnskey-sig-validity: cannot be "
"configured if dnssec-policy is also set");
result = ISC_R_FAILURE;
}
}
obj = NULL;
......@@ -1117,8 +1169,9 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
if (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS)
result = tresult;
}
if (symtab != NULL)
if (symtab != NULL) {
isc_symtab_destroy(&symtab);
}
}
/*
......@@ -1858,6 +1911,7 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
bool dlz;
dns_masterformat_t masterformat;
bool ddns = false;
bool has_dnssecpolicy = false;
const void *clauses = NULL;
const char *option = NULL;
static const char *acls[] = {
......@@ -2070,6 +2124,42 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
if (check_nonzero(zoptions, logctx) != ISC_R_SUCCESS)
result = ISC_R_FAILURE;
/*
* Check if a dnssec-policy is set.
*/
obj = NULL;
(void)cfg_map_get(zoptions, "dnssec-policy", &obj);
if (obj != NULL) {
const cfg_obj_t *kasps = NULL;
const char* kaspname = cfg_obj_asstring(obj);
if (strcmp(kaspname, "default") == 0) {
has_dnssecpolicy = true;
} else {
(void)cfg_map_get(config, "dnssec-policy", &kasps);
for (element = cfg_list_first(kasps); element != NULL;
element = cfg_list_next(element))
{
const char* kn = cfg_obj_asstring(
cfg_tuple_get(cfg_listelt_value(element),
"name"));
if (strcmp(kaspname, kn) == 0) {
has_dnssecpolicy = true;
}
}
}
if (!has_dnssecpolicy) {
cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR,
"zone '%s': option 'dnssec-policy %s' "
"has no matching dnssec-policy config",
znamestr, kaspname);
if (result == ISC_R_SUCCESS) {
result = ISC_R_FAILURE;
}
}
}
/*
* Check validity of the zone options.
*/
......@@ -2256,19 +2346,36 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
if (res1 == ISC_R_SUCCESS)
signing = cfg_obj_asboolean(obj);
if (signing && has_dnssecpolicy) {
cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
"inline-signing: cannot be configured if "
"dnssec-policy is also set");
result = ISC_R_FAILURE;
}
obj = NULL;
arg = "off";
res3 = cfg_map_get(zoptions, "auto-dnssec", &obj);
if (res3 == ISC_R_SUCCESS)
if (res3 == ISC_R_SUCCESS) {
arg = cfg_obj_asstring(obj);
if (strcasecmp(arg, "off") != 0 && !ddns && !signing) {
cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
"'auto-dnssec %s;' requires%s "
"inline-signing to be configured for "
"the zone", arg,