Commit 5c9ef20c authored by Libor Peltan's avatar Libor Peltan Committed by Daniel Salzman

dnssec: allow manual double-DS rollover

parent de486f67
......@@ -675,7 +675,7 @@ policy:
nsec3\-salt\-lifetime: TIME
signing\-threads: INT
ksk\-submission: submission_id
cds\-cdnskey\-publish: none | delete\-dnssec | rollover | always
cds\-cdnskey\-publish: none | delete\-dnssec | rollover | always | double\-ds
offline\-ksk: BOOL
.ft P
.fi
......@@ -886,13 +886,6 @@ Some steps of the DNSSEC signing operation are not parallelized.
.sp
Controls if and how shall the CDS and CDNSKEY be published in the zone.
.sp
\fBNOTE:\fP
.INDENT 0.0
.INDENT 3.5
This only applies if the zone keys are automatically managed by the server.
.UNINDENT
.UNINDENT
.sp
Possible values:
.INDENT 0.0
.IP \(bu 2
......@@ -902,7 +895,17 @@ Possible values:
.IP \(bu 2
\fBrollover\fP – Publish CDS and CDNSKEY records only in the submission phase of KSK rollover.
.IP \(bu 2
\fBalways\fP – Always publish CDS and CDNSKEY records for the current KSK.
\fBalways\fP – Always publish one CDS and one CDNSKEY records for the current KSK.
.IP \(bu 2
\fBdouble\-ds\fP – Always publish up to two CDS and two CDNSKEY records for ready and/or active KSKs.
.UNINDENT
.sp
\fBNOTE:\fP
.INDENT 0.0
.INDENT 3.5
If the zone keys are managed manually, the CDS and CDNSKEY rrsets may contain
more records depending on the keys available.
.UNINDENT
.UNINDENT
.sp
\fIDefault:\fP always
......
......@@ -747,7 +747,7 @@ DNSSEC policy configuration.
nsec3-salt-lifetime: TIME
signing-threads: INT
ksk-submission: submission_id
cds-cdnskey-publish: none | delete-dnssec | rollover | always
cds-cdnskey-publish: none | delete-dnssec | rollover | always | double-ds
offline-ksk: BOOL
.. _policy_id:
......@@ -1006,15 +1006,17 @@ cds-cdnskey-publish
Controls if and how shall the CDS and CDNSKEY be published in the zone.
.. NOTE::
This only applies if the zone keys are automatically managed by the server.
Possible values:
- ``none`` – Never publish any CDS or CDNSKEY records in the zone.
- ``delete-dnssec`` – Publish special CDS and CDNSKEY records indicating turning off DNSSEC.
- ``rollover`` – Publish CDS and CDNSKEY records only in the submission phase of KSK rollover.
- ``always`` – Always publish CDS and CDNSKEY records for the current KSK.
- ``always`` – Always publish one CDS and one CDNSKEY records for the current KSK.
- ``double-ds`` – Always publish up to two CDS and two CDNSKEY records for ready and/or active KSKs.
.. NOTE::
If the zone keys are managed manually, the CDS and CDNSKEY rrsets may contain
more records depending on the keys available.
*Default:* always
......
......@@ -76,6 +76,7 @@ const knot_lookup_t child_record[] = {
{ CHILD_RECORDS_EMPTY, "delete-dnssec" },
{ CHILD_RECORDS_ROLLOVER, "rollover" },
{ CHILD_RECORDS_ALWAYS, "always" },
{ CHILD_RECORDS_DOUBLE_DS,"double-ds" },
{ 0, NULL }
};
......
......@@ -135,6 +135,7 @@ enum {
CHILD_RECORDS_EMPTY = 1,
CHILD_RECORDS_ROLLOVER = 2,
CHILD_RECORDS_ALWAYS = 3,
CHILD_RECORDS_DOUBLE_DS= 4,
};
enum {
......
......@@ -230,7 +230,8 @@ int knot_parent_ds_query(kdnssec_ctx_t *kctx, zone_keyset_t *keyset, size_t time
for (size_t i = 0; i < keyset->count; i++) {
zone_key_t *key = &keyset->keys[i];
if (key->is_ksk && key->cds_priority > 1) {
if (key->is_ready) {
assert(key->is_ksk);
if (parents_have_ds(kctx, key, timeout, &max_ds_ttl)) {
return knot_dnssec_ksk_sbm_confirm(kctx, max_ds_ttl);
} else {
......
......@@ -132,7 +132,7 @@ int key_records_dump(char **buf, size_t *buf_size, const key_records_t *r, bool
int key_records_sign(const zone_key_t *key, key_records_t *r, const kdnssec_ctx_t *kctx)
{
if (!key->is_active) {
if (!key->is_active && !key->is_post_active) {
return KNOT_EOK;
}
......
......@@ -217,18 +217,30 @@ static void set_key(knot_kasp_key_t *kasp_key, knot_time_t now, zone_key_t *zone
knot_time_cmp(timing->post_active, now) > 0 &&
knot_time_cmp(timing->remove, now) > 0);
zone_key->is_active = (((zone_key->is_zsk && knot_time_cmp(timing->pre_active, now) <= 0) ||
(knot_time_cmp(timing->pre_active, now) <= 0 && knot_time_cmp(timing->publish, now) <= 0) ||
knot_time_cmp(timing->ready, now) <= 0 ||
zone_key->is_ready = (zone_key->is_ksk && knot_time_cmp(timing->ready, now) <= 0 &&
knot_time_cmp(timing->active, now) > 0);
zone_key->is_active = ((knot_time_cmp(timing->ready, now) <= 0 ||
knot_time_cmp(timing->active, now) <= 0) &&
knot_time_cmp(timing->retire, now) > 0 &&
(zone_key->is_zsk || knot_time_cmp(timing->post_active, now) > 0) &&
knot_time_cmp(timing->retire_active, now) > 0 &&
knot_time_cmp(timing->remove, now) > 0);
zone_key->cds_priority = (knot_time_cmp(timing->ready, now) <= 0 ? (
(knot_time_cmp(timing->active, now) <= 0) ? (
(knot_time_cmp(timing->retire_active, now) <= 0 ||
knot_time_cmp(timing->retire, now) <= 0) ? 0 : 1) : 2) : 0);
zone_key->is_post_active = false;
if (knot_time_cmp(timing->pre_active, now) <= 0 &&
knot_time_cmp(timing->ready, now) > 0 &&
knot_time_cmp(timing->active, now) > 0) {
zone_key->is_post_active = (zone_key->is_zsk ||
knot_time_cmp(timing->publish, now) <= 0);
}
if (knot_time_cmp(timing->retire_active, now) <= 0 &&
knot_time_cmp(timing->retire, now) > 0) {
zone_key->is_post_active = true;
} // not "else" !
if (knot_time_cmp(timing->post_active, now) <= 0 &&
knot_time_cmp(timing->remove, now) > 0) {
zone_key->is_post_active = zone_key->is_zsk;
}
}
/*!
......@@ -259,14 +271,15 @@ static int walk_algorithms(kdnssec_ctx_t *ctx, zone_keyset_t *keyset)
dnssec_key_get_keytag(key->key));
key->is_public = false;
key->is_active = false;
key->cds_priority = 0;
key->is_ready = false;
key->is_post_active = false;
continue;
}
if (key->is_ksk && key->is_public) { alg_usage[alg] |= 1; }
if (key->is_zsk && key->is_public) { alg_usage[alg] |= 2; }
if (key->is_ksk && key->is_active) { alg_usage[alg] |= 4; }
if (key->is_zsk && key->is_active) { alg_usage[alg] |= 8; }
if (key->is_ksk && (key->is_active || key->is_post_active)) { alg_usage[alg] |= 4; }
if (key->is_zsk && (key->is_active || key->is_post_active)) { alg_usage[alg] |= 8; }
}
for (size_t i = 0; i < sizeof(alg_usage); i++) {
......@@ -305,11 +318,10 @@ static int load_private_keys(dnssec_keystore_t *keystore, zone_keyset_t *keyset)
assert(keyset);
for (size_t i = 0; i < keyset->count; i++) {
if (!keyset->keys[i].is_active) {
zone_key_t *key = &keyset->keys[i];
if (!key->is_active && !key->is_post_active) {
continue;
}
zone_key_t *key = &keyset->keys[i];
int r = dnssec_key_import_keystore(key->key, keystore, key->id);
switch (r) {
case DNSSEC_EOK:
......@@ -339,13 +351,14 @@ static void log_key_info(const zone_key_t *key, char *out, size_t out_len)
(void)snprintf(alg_code_str, sizeof(alg_code_str), "%d", alg_code);
}
(void)snprintf(out, out_len, "DNSSEC, key, tag %5d, algorithm %s%s%s%s%s",
(void)snprintf(out, out_len, "DNSSEC, key, tag %5d, algorithm %s%s%s%s%s%s",
dnssec_key_get_keytag(key->key),
(alg != NULL ? alg->name : alg_code_str),
(key->is_ksk ? (key->is_zsk ? ", CSK" : ", KSK") : ""),
(key->is_public ? ", public" : ""),
(key->cds_priority > 1 ? ", ready" : ""),
(key->is_active ? ", active" : ""));
(key->is_public ? ", public" : ""),
(key->is_ready ? ", ready" : ""),
(key->is_active ? ", active" : ""),
(key->is_post_active ? ", active+" : ""));
}
int log_key_sort(const void *a, const void *b)
......
......@@ -38,7 +38,8 @@ typedef struct {
bool is_zsk;
bool is_active;
bool is_public;
int cds_priority;
bool is_ready;
bool is_post_active; // also for retire_active, pre_active
} zone_key_t;
dynarray_declare(keyptr, zone_key_t *, DYNARRAY_VISIBILITY_PUBLIC, 1)
......
......@@ -234,7 +234,8 @@ static int remove_expired_rrsigs(const knot_rrset_t *covered,
for (size_t j = 0; j < sign_ctx->count; j++) {
zone_key_t *key = &sign_ctx->keys[j];
if (!key->is_active || dnssec_key_get_keytag(key->key) != keytag) {
if ((!key->is_active && !key->is_post_active) ||
dnssec_key_get_keytag(key->key) != keytag) {
continue;
}
......@@ -794,30 +795,34 @@ keyptr_dynarray_t knot_zone_sign_get_cdnskeys(const kdnssec_ctx_t *ctx,
zone_keyset_t *zone_keys)
{
keyptr_dynarray_t r = { 0 };
zone_key_t *ksk_for_cds = NULL;
unsigned crp = ctx->policy->child_records_publish;
int kfc_prio = (crp == CHILD_RECORDS_ALWAYS ? 0 : (crp == CHILD_RECORDS_ROLLOVER ? 1 : 2));
for (int i = 0; i < zone_keys->count; i++) {
zone_key_t *key = &zone_keys->keys[i];
// determine which key (if any) will be the one for CDS/CDNSKEY
if (key->is_ksk && key->cds_priority > kfc_prio) {
ksk_for_cds = key;
kfc_prio = key->cds_priority;
if (crp == CHILD_RECORDS_ROLLOVER || crp == CHILD_RECORDS_ALWAYS ||
crp == CHILD_RECORDS_DOUBLE_DS) {
// first, add strictly-ready keys
for (int i = 0; i < zone_keys->count; i++) {
zone_key_t *key = &zone_keys->keys[i];
if (key->is_ready) {
assert(key->is_ksk);
keyptr_dynarray_add(&r, &key);
}
}
}
for (int i = 0; i < zone_keys->count; i++) {
zone_key_t *key = &zone_keys->keys[i];
// determine which key (if any) will be the one for CDS/CDNSKEY
if (key->is_ksk && key->cds_priority > kfc_prio) {
ksk_for_cds = key;
kfc_prio = key->cds_priority;
// second, add active keys
if ((crp == CHILD_RECORDS_ALWAYS && r.size == 0) ||
(crp == CHILD_RECORDS_DOUBLE_DS)) {
for (int i = 0; i < zone_keys->count; i++) {
zone_key_t *key = &zone_keys->keys[i];
if (key->is_ksk && key->is_active && !key->is_ready) {
keyptr_dynarray_add(&r, &key);
}
}
}
}
if (ksk_for_cds != NULL) {
keyptr_dynarray_add(&r, &ksk_for_cds);
if ((crp != CHILD_RECORDS_DOUBLE_DS && r.size > 1) ||
(r.size > 2)) {
log_zone_warning(ctx->zone->dname, "DNSSEC, published CDS/CDNSKEY records for too many (%zu) keys", r.size);
}
}
return r;
......@@ -950,7 +955,7 @@ bool knot_zone_sign_use_key(const zone_key_t *key, const knot_rrset_t *covered)
return false;
}
if (!key->is_active) {
if (!key->is_active && !key->is_post_active) {
return false;
}
......
......@@ -28,6 +28,7 @@
#include "knot/dnssec/policy.h"
#include "knot/dnssec/rrset-sign.h"
#include "knot/dnssec/zone-events.h"
#include "knot/dnssec/zone-sign.h"
#include "knot/nameserver/query_module.h"
#include "knot/nameserver/process_query.h"
......@@ -428,23 +429,6 @@ static knot_rrset_t *synth_dnskey(knotd_qdata_t *qdata, knotd_mod_t *mod,
return dnskey;
}
/* copied from the middle of zone-sign.c */
static zone_key_t *ksk_for_cds(knotd_mod_t *mod)
{
zone_key_t *res = NULL;
unsigned crp = mod->dnssec->policy->child_records_publish;
int kfc_prio = (crp == CHILD_RECORDS_ALWAYS ?
0 : (crp == CHILD_RECORDS_ROLLOVER ? 1 : 2));
for (int i = 0; i < mod->keyset->count; i++) {
zone_key_t *key = &mod->keyset->keys[i];
if (key->is_ksk && key->cds_priority > kfc_prio) {
res = key;
kfc_prio = key->cds_priority;
}
}
return res;
}
static knot_rrset_t *synth_cdnskey(knotd_qdata_t *qdata, knotd_mod_t *mod,
knot_mm_t *mm)
{
......@@ -458,21 +442,13 @@ static knot_rrset_t *synth_cdnskey(knotd_qdata_t *qdata, knotd_mod_t *mod,
dnssec_binary_t rdata = { 0 };
online_sign_ctx_t *ctx = knotd_mod_ctx(mod);
pthread_rwlock_rdlock(&ctx->signing_mutex);
zone_key_t *key = ksk_for_cds(mod);
if (key == NULL) {
pthread_rwlock_unlock(&ctx->signing_mutex);
knot_rrset_free(dnskey, mm);
return NULL;
keyptr_dynarray_t kcdnskeys = knot_zone_sign_get_cdnskeys(mod->dnssec, mod->keyset);
dynarray_foreach(keyptr, zone_key_t *, ksk_for_cdnskey, kcdnskeys) {
dnssec_key_get_rdata((*ksk_for_cdnskey)->key, &rdata);
assert(rdata.size > 0 && rdata.data);
(void)knot_rrset_add_rdata(dnskey, rdata.data, rdata.size, mm);
}
dnssec_key_get_rdata(key->key, &rdata);
assert(rdata.size > 0 && rdata.data);
int ret = knot_rrset_add_rdata(dnskey, rdata.data, rdata.size, mm);
pthread_rwlock_unlock(&ctx->signing_mutex);
if (ret != KNOT_EOK) {
knot_rrset_free(dnskey, mm);
return NULL;
}
return dnskey;
}
......@@ -490,21 +466,13 @@ static knot_rrset_t *synth_cds(knotd_qdata_t *qdata, knotd_mod_t *mod,
dnssec_binary_t rdata = { 0 };
online_sign_ctx_t *ctx = knotd_mod_ctx(mod);
pthread_rwlock_rdlock(&ctx->signing_mutex);
zone_key_t *key = ksk_for_cds(mod);
if (key == NULL) {
pthread_rwlock_unlock(&ctx->signing_mutex);
knot_rrset_free(ds, mm);
return NULL;
keyptr_dynarray_t kcdnskeys = knot_zone_sign_get_cdnskeys(mod->dnssec, mod->keyset);
dynarray_foreach(keyptr, zone_key_t *, ksk_for_cds, kcdnskeys) {
zone_key_calculate_ds(*ksk_for_cds, &rdata);
assert(rdata.size > 0 && rdata.data);
(void)knot_rrset_add_rdata(ds, rdata.data, rdata.size, mm);
}
zone_key_calculate_ds(key, &rdata);
assert(rdata.size > 0 && rdata.data);
int ret = knot_rrset_add_rdata(ds, rdata.data, rdata.size, mm);
pthread_rwlock_unlock(&ctx->signing_mutex);
if (ret != KNOT_EOK) {
knot_rrset_free(ds, mm);
return NULL;
}
return ds;
}
......
......@@ -36,6 +36,9 @@ master.dnssec(nsec_zone).alg = "rsasha1"
master.dnssec(nsec3_zone).alg = "rsasha1"
master.dnssec(nsec3_zone).nsec3 = True
master.dnssec(static_zone).alg = "rsasha1"
master.dnssec(nsec_zone).cds_publish = "rollover"
master.dnssec(nsec3_zone).cds_publish = "rollover"
master.dnssec(static_zone).cds_publish = "rollover"
# install KASP db
shutil.copytree(os.path.join(t.data_dir, "keys"), master.keydir)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment