Commit 1a966a29 authored by Daniel Salzman's avatar Daniel Salzman

Merge branch 'child_records_evo' into 'master'

Child records evo

See merge request !855
parents 456f7a70 d3713840
...@@ -572,6 +572,7 @@ policy: ...@@ -572,6 +572,7 @@ policy:
nsec3\-salt\-length: INT nsec3\-salt\-length: INT
nsec3\-salt\-lifetime: TIME nsec3\-salt\-lifetime: TIME
ksk\-submission: submission_id ksk\-submission: submission_id
child\-records\-publish: none | empty | rollover | always
.ft P .ft P
.fi .fi
.UNINDENT .UNINDENT
...@@ -737,6 +738,31 @@ A reference to \fI\%submission\fP section holding parameters of ...@@ -737,6 +738,31 @@ A reference to \fI\%submission\fP section holding parameters of
KSK submittion checks. KSK submittion checks.
.sp .sp
\fIDefault:\fP not set \fIDefault:\fP not set
.SS child\-records\-publish
.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
\fBnone\fP \- never publish any CDS or CDNSKEY records in the zone
.IP \(bu 2
\fBempty\fP \- publish special CDS and CDNSKEY records indicating turning off DNSSEC
.IP \(bu 2
\fBrollover\fP \- publish CDS and CDNSKEY records only for the period of KSK submission
(newly generated KSK either initial or during rollover)
.IP \(bu 2
\fBalways\fP \- always publish CDS and CDNSKEY records for the current KSK
.UNINDENT
.sp
\fIDefault:\fP always
.SH REMOTE SECTION .SH REMOTE SECTION
.sp .sp
Definitions of remote servers for outgoing connections (source of a zone Definitions of remote servers for outgoing connections (source of a zone
......
...@@ -500,14 +500,14 @@ More precisely, we tell the parent zone to remove our zone's DS record by ...@@ -500,14 +500,14 @@ More precisely, we tell the parent zone to remove our zone's DS record by
publishing a special formatted CDNSKEY and CDS record. This is mostly useful publishing a special formatted CDNSKEY and CDS record. This is mostly useful
if we want to turn off DNSSEC on our zone so it becomes insecure, but not bogus. if we want to turn off DNSSEC on our zone so it becomes insecure, but not bogus.
First we have to turn off automatic DNSSEC siging and key management. See With automatic DNSSEC signing and key management by Knot, this is as easy as
:ref:`configuration reference<zone_dnssec-signing>` for details. After that, configuring :ref:`policy_child-records-publish` option and reloading the configuration.
we add special CDNSKEY and CDS records with the rdata "0 3 0 AA==" and "0 0 0 00", We check if the special CDNSKEY and CDS records with the rdata "0 3 0 AA==" and "0 0 0 00",
respectively, by editing zone file or DDNS. respectively, appeared in the zone.
After the parent zone notices and reflects the change, we wait for TTL expire After the parent zone notices and reflects the change, we wait for TTL expire
(so all resolvers' caches get updated), and finally we may do anything with the (so all resolvers' caches get updated), and finally we may do anything with the
zone, e.g. removing all the keys and signatures as desired. zone, e.g. turning off DNSSEC, removing all the keys and signatures as desired.
.. _Controlling running daemon: .. _Controlling running daemon:
......
...@@ -642,6 +642,7 @@ DNSSEC policy configuration. ...@@ -642,6 +642,7 @@ DNSSEC policy configuration.
nsec3-salt-length: INT nsec3-salt-length: INT
nsec3-salt-lifetime: TIME nsec3-salt-lifetime: TIME
ksk-submission: submission_id ksk-submission: submission_id
child-records-publish: none | empty | rollover | always
.. _policy_id: .. _policy_id:
...@@ -854,6 +855,26 @@ KSK submittion checks. ...@@ -854,6 +855,26 @@ KSK submittion checks.
*Default:* not set *Default:* not set
.. _policy_child-records-publish:
child-records-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
- ``empty`` - publish special CDS and CDNSKEY records indicating turning off DNSSEC
- ``rollover`` - publish CDS and CDNSKEY records only for the period of KSK submission
(newly generated KSK either initial or during rollover)
- ``always`` - always publish CDS and CDNSKEY records for the current KSK
*Default:* always
.. _Remote section: .. _Remote section:
Remote section Remote section
......
...@@ -72,6 +72,14 @@ static const knot_lookup_t dnssec_key_algs[] = { ...@@ -72,6 +72,14 @@ static const knot_lookup_t dnssec_key_algs[] = {
{ 0, NULL } { 0, NULL }
}; };
const knot_lookup_t child_record[] = {
{ CHILD_RECORDS_NONE, "none" },
{ CHILD_RECORDS_EMPTY, "empty" },
{ CHILD_RECORDS_ROLLOVER, "rollover" },
{ CHILD_RECORDS_ALWAYS, "always" },
{ 0, NULL }
};
const knot_lookup_t acl_actions[] = { const knot_lookup_t acl_actions[] = {
{ ACL_ACTION_NOTIFY, "notify" }, { ACL_ACTION_NOTIFY, "notify" },
{ ACL_ACTION_TRANSFER, "transfer" }, { ACL_ACTION_TRANSFER, "transfer" },
...@@ -261,6 +269,7 @@ static const yp_item_t desc_policy[] = { ...@@ -261,6 +269,7 @@ static const yp_item_t desc_policy[] = {
CONF_IO_FRLD_ZONES }, CONF_IO_FRLD_ZONES },
{ C_KSK_SBM, YP_TREF, YP_VREF = { C_SBM }, CONF_IO_FRLD_ZONES, { C_KSK_SBM, YP_TREF, YP_VREF = { C_SBM }, CONF_IO_FRLD_ZONES,
{ check_ref } }, { check_ref } },
{ C_CHILD_RECORDS, YP_TOPT, YP_VOPT = { child_record, CHILD_RECORDS_ALWAYS } },
{ C_COMMENT, YP_TSTR, YP_VNONE }, { C_COMMENT, YP_TSTR, YP_VNONE },
{ NULL } { NULL }
}; };
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
#define C_ASYNC_START "\x0B""async-start" #define C_ASYNC_START "\x0B""async-start"
#define C_BACKEND "\x07""backend" #define C_BACKEND "\x07""backend"
#define C_BG_WORKERS "\x12""background-workers" #define C_BG_WORKERS "\x12""background-workers"
#define C_CHILD_RECORDS "\x15""child-records-publish"
#define C_CHK_INTERVAL "\x0E""check-interval" #define C_CHK_INTERVAL "\x0E""check-interval"
#define C_COMMENT "\x07""comment" #define C_COMMENT "\x07""comment"
#define C_CONFIG "\x06""config" #define C_CONFIG "\x06""config"
...@@ -136,6 +137,13 @@ enum { ...@@ -136,6 +137,13 @@ enum {
KEYSTORE_BACKEND_PKCS11 = 2 KEYSTORE_BACKEND_PKCS11 = 2
}; };
enum {
CHILD_RECORDS_NONE = 0,
CHILD_RECORDS_EMPTY = 1,
CHILD_RECORDS_ROLLOVER = 2,
CHILD_RECORDS_ALWAYS = 3,
};
enum { enum {
SERIAL_POLICY_INCREMENT = 1, SERIAL_POLICY_INCREMENT = 1,
SERIAL_POLICY_UNIXTIME = 2 SERIAL_POLICY_UNIXTIME = 2
......
...@@ -85,6 +85,9 @@ static void policy_load(knot_kasp_policy_t *policy, conf_val_t *id) ...@@ -85,6 +85,9 @@ static void policy_load(knot_kasp_policy_t *policy, conf_val_t *id)
val = conf_id_get(conf(), C_POLICY, C_NSEC3_SALT_LIFETIME, id); val = conf_id_get(conf(), C_POLICY, C_NSEC3_SALT_LIFETIME, id);
policy->nsec3_salt_lifetime = conf_int(&val); policy->nsec3_salt_lifetime = conf_int(&val);
val = conf_id_get(conf(), C_POLICY, C_CHILD_RECORDS, id);
policy->child_records_publish = conf_opt(&val);
conf_val_t ksk_sbm = conf_id_get(conf(), C_POLICY, C_KSK_SBM, id); conf_val_t ksk_sbm = conf_id_get(conf(), C_POLICY, C_KSK_SBM, id);
if (ksk_sbm.code == KNOT_EOK) { if (ksk_sbm.code == KNOT_EOK) {
val = conf_id_get(conf(), C_SBM, C_CHK_INTERVAL, &ksk_sbm); val = conf_id_get(conf(), C_SBM, C_CHK_INTERVAL, &ksk_sbm);
......
...@@ -92,5 +92,6 @@ typedef struct { ...@@ -92,5 +92,6 @@ typedef struct {
// various // various
uint32_t ksk_sbm_timeout; uint32_t ksk_sbm_timeout;
uint32_t ksk_sbm_check_interval; uint32_t ksk_sbm_check_interval;
unsigned child_records_publish;
} knot_kasp_policy_t; } knot_kasp_policy_t;
// TODO make the time parameters knot_timediff_t ?? // TODO make the time parameters knot_timediff_t ??
...@@ -627,92 +627,6 @@ typedef struct { ...@@ -627,92 +627,6 @@ typedef struct {
/*- private API - DNSKEY handling --------------------------------------------*/ /*- private API - DNSKEY handling --------------------------------------------*/
/*!
* \brief Check if DNSKEY RDATA match with DNSSEC key.
*
* \param zone_key Zone key.
* \param rdata DNSKEY RDATA.
*
* \return DNSKEY RDATA match with DNSSEC key.
*/
static bool dnskey_rdata_match(zone_key_t *key,
const dnssec_binary_t *rdata)
{
assert(key);
assert(rdata);
dnssec_binary_t dnskey_rdata = { 0 };
dnssec_key_get_rdata(key->key, &dnskey_rdata);
return dnssec_binary_cmp(&dnskey_rdata, rdata) == 0;
}
static bool cds_rdata_match(zone_key_t *key,
const dnssec_binary_t *rdata)
{
assert(key);
assert(rdata);
dnssec_binary_t cds_rdata = { 0 };
int ret = zone_key_calculate_ds(key, &cds_rdata);
int res = dnssec_binary_cmp(&cds_rdata, rdata);
return (ret == KNOT_EOK && res == 0);
}
bool knot_match_key_ds(zone_key_t *key, const knot_rdata_t *rdata)
{
dnssec_binary_t rdata_bin = {
.data = knot_rdata_data(rdata),
.size = knot_rdata_rdlen(rdata)
};
return cds_rdata_match(key, &rdata_bin);
}
/*!
* \brief Check if DNSKEY/DS is present in public zone key set.
*/
static bool is_from_keyset(zone_keyset_t *keyset,
const knot_rdata_t *record,
bool is_ds, // otherwise, it's DNSKEY
bool is_cds_cdnskey, // in this case we match only ready keys
zone_key_t **matching_key) // out, optional
{
assert(keyset);
assert(record);
dnssec_binary_t rdata = {
.data = knot_rdata_data(record),
.size = knot_rdata_rdlen(record)
};
uint16_t tag = 0;
bool (*match_fce)(zone_key_t *, const dnssec_binary_t *);
if (is_ds) {
wire_ctx_t wrdata = wire_ctx_init(rdata.data, rdata.size);
tag = wire_ctx_read_u16(&wrdata); // key tag in DS is just at beginning of wire
match_fce = cds_rdata_match;
} else {
dnssec_keytag(&rdata, &tag);
match_fce = dnskey_rdata_match;
}
bool found = false;
struct keyptr_dynarray keys = get_zone_keys(keyset, tag);
for (size_t i = 0; i < keys.size; i++) {
bool usekey = (is_cds_cdnskey ? (keys.arr(&keys)[i]->cds_priority > 1) : keys.arr(&keys)[i]->is_public);
if (usekey && match_fce(keys.arr(&keys)[i], &rdata)) {
found = true;
if (matching_key != NULL) {
*matching_key = keys.arr(&keys)[i];
}
break;
}
}
keyptr_dynarray_free(&keys);
return found;
}
static int rrset_add_zone_key(knot_rrset_t *rrset, static int rrset_add_zone_key(knot_rrset_t *rrset,
zone_key_t *zone_key, zone_key_t *zone_key,
uint32_t ttl) uint32_t ttl)
...@@ -1014,38 +928,15 @@ int knot_zone_sign_update_dnskeys(zone_update_t *update, ...@@ -1014,38 +928,15 @@ int knot_zone_sign_update_dnskeys(zone_update_t *update,
return ret; return ret;
} }
// remove invalid/old DNSKEYS #define CHECK_RET if (ret != KNOT_EOK) goto cleanup
knot_rrset_t to_remove;
knot_rrset_init(&to_remove, apex->owner, KNOT_RRTYPE_DNSKEY, dnskeys.rclass);
for (uint16_t i = 0; i < dnskeys.rrs.rr_count; i++) {
const knot_rdata_t *r = knot_rdataset_at(&dnskeys.rrs, i);
uint32_t r_ttl = knot_rdata_ttl(r);
if (r_ttl == dnskey_ttl && is_from_keyset(zone_keys, r, false, false, NULL)) {
continue;
}
ret = knot_rdataset_add(&to_remove.rrs, r, NULL); // remove all. This will cancel out with additions later
if (ret != KNOT_EOK) { ret = changeset_add_removal(&ch, &dnskeys, 0);
break; CHECK_RET;
} ret = changeset_add_removal(&ch, &cdnskeys, 0);
} CHECK_RET;
if (!knot_rrset_empty(&to_remove) && ret == KNOT_EOK) { ret = changeset_add_removal(&ch, &cdss, 0);
ret = changeset_add_removal(&ch, &to_remove, 0); CHECK_RET;
}
knot_rdataset_clear(&to_remove.rrs, NULL);
if (ret != KNOT_EOK) {
goto cleanup;
}
// remove CDS and CDNSKEY if no submission
if (!zone_has_key_sbm(dnssec_ctx)) {
if (!knot_rrset_empty(&cdnskeys)) {
ret = changeset_add_removal(&ch, &cdnskeys, 0);
}
if (ret == KNOT_EOK && !knot_rrset_empty(&cdss)) {
ret = changeset_add_removal(&ch, &cdss, 0);
}
}
// add DNSKEYs, CDNSKEYs and CDSs // add DNSKEYs, CDNSKEYs and CDSs
add_dnskeys = knot_rrset_new(apex->owner, KNOT_RRTYPE_DNSKEY, soa.rclass, NULL); add_dnskeys = knot_rrset_new(apex->owner, KNOT_RRTYPE_DNSKEY, soa.rclass, NULL);
...@@ -1055,15 +946,17 @@ int knot_zone_sign_update_dnskeys(zone_update_t *update, ...@@ -1055,15 +946,17 @@ int knot_zone_sign_update_dnskeys(zone_update_t *update,
ret = KNOT_ENOMEM; ret = KNOT_ENOMEM;
} }
zone_key_t *ksk_for_cds = NULL; zone_key_t *ksk_for_cds = NULL;
int kfc_prio = 0; unsigned crp = dnssec_ctx->policy->child_records_publish;
for (int i = 0; i < zone_keys->count && ret == KNOT_EOK; i++) { 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]; zone_key_t *key = &zone_keys->keys[i];
if (!key->is_public) { if (key->is_public) {
continue; ret = rrset_add_zone_key(add_dnskeys, key, dnskey_ttl);
CHECK_RET;
} }
ret = rrset_add_zone_key(add_dnskeys, key, dnskey_ttl); // determine which key (if any) will be the one for CDS/CDNSKEY
if (key->is_ksk && ret == KNOT_EOK && key->cds_priority > kfc_prio) { if (key->is_ksk && key->cds_priority > kfc_prio) {
ksk_for_cds = key; ksk_for_cds = key;
kfc_prio = key->cds_priority; kfc_prio = key->cds_priority;
} }
...@@ -1071,45 +964,43 @@ int knot_zone_sign_update_dnskeys(zone_update_t *update, ...@@ -1071,45 +964,43 @@ int knot_zone_sign_update_dnskeys(zone_update_t *update,
if (ksk_for_cds != NULL) { if (ksk_for_cds != NULL) {
ret = rrset_add_zone_key(add_cdnskeys, ksk_for_cds, 0); ret = rrset_add_zone_key(add_cdnskeys, ksk_for_cds, 0);
if (ret == KNOT_EOK) { CHECK_RET;
ret = rrset_add_zone_ds(add_cdss, ksk_for_cds, 0); ret = rrset_add_zone_ds(add_cdss, ksk_for_cds, 0);
} CHECK_RET;
}
if (ret != KNOT_EOK) {
goto cleanup;
} }
if (!knot_rrset_equal(&cdnskeys, add_cdnskeys, KNOT_RRSET_COMPARE_WHOLE)) { if (crp == CHILD_RECORDS_EMPTY) {
if (!knot_rrset_empty(&cdnskeys)) { const uint8_t cdnskey_empty[5] = { 0, 0, 3, 0, 0 };
ret = changeset_add_removal(&ch, &cdnskeys, 0); const uint8_t cds_empty[5] = { 0, 0, 0, 0, 0 };
} ret = knot_rrset_add_rdata(add_cdnskeys, cdnskey_empty,
if (ret == KNOT_EOK && !knot_rrset_empty(add_cdnskeys)) { sizeof(cdnskey_empty), 0, NULL);
ret = changeset_add_addition(&ch, add_cdnskeys, 0); CHECK_RET;
} ret = knot_rrset_add_rdata(add_cdss, cds_empty,
} sizeof(cds_empty), 0, NULL);
if (ret != KNOT_EOK) { CHECK_RET;
goto cleanup;
} }
if (!knot_rrset_equal(&cdss, add_cdss, KNOT_RRSET_COMPARE_WHOLE)) { if (!knot_rrset_empty(add_cdnskeys)) {
if (!knot_rrset_empty(&cdss)) { ret = changeset_add_addition(&ch, add_cdnskeys, CHANGESET_CHECK |
ret = changeset_add_removal(&ch, &cdss, 0); CHANGESET_CHECK_CANCELOUT);
} CHECK_RET;
if (ret == KNOT_EOK && !knot_rrset_empty(add_cdss)) {
ret = changeset_add_addition(&ch, add_cdss, 0);
}
} }
if (ret != KNOT_EOK) {
goto cleanup; if (!knot_rrset_empty(add_cdss)) {
ret = changeset_add_addition(&ch, add_cdss, CHANGESET_CHECK |
CHANGESET_CHECK_CANCELOUT);
CHECK_RET;
} }
if (!knot_rrset_empty(add_dnskeys)) { if (!knot_rrset_empty(add_dnskeys)) {
ret = changeset_add_addition(&ch, add_dnskeys, 0); ret = changeset_add_addition(&ch, add_dnskeys, CHANGESET_CHECK |
CHANGESET_CHECK_CANCELOUT);
CHECK_RET;
} }
if (ret == KNOT_EOK) { ret = zone_update_apply_changeset(update, &ch);
ret = zone_update_apply_changeset_fix(update, &ch);
} #undef CHECK_RET
cleanup: cleanup:
knot_rrset_free(&add_dnskeys, NULL); knot_rrset_free(&add_dnskeys, NULL);
......
...@@ -91,17 +91,6 @@ int knot_zone_sign_nsecs_in_changeset(const zone_keyset_t *zone_keys, ...@@ -91,17 +91,6 @@ int knot_zone_sign_nsecs_in_changeset(const zone_keyset_t *zone_keys,
bool knot_zone_sign_rr_should_be_signed(const zone_node_t *node, bool knot_zone_sign_rr_should_be_signed(const zone_node_t *node,
const knot_rrset_t *rrset); const knot_rrset_t *rrset);
/*!
* \brief Checks whether a DS record corresponds to a key.
*
* \param key A DNSSEC key in zone.
* \param rdata The DS record rdata.
*
* \return true if corresponds.
*/
bool knot_match_key_ds(zone_key_t *key,
const knot_rdata_t *rdata);
/*! /*!
* \brief Sign updates of the zone, storing new RRSIGs in this update again. * \brief Sign updates of the zone, storing new RRSIGs in this update again.
* *
......
...@@ -43,7 +43,12 @@ void event_dnssec_reschedule(conf_t *conf, zone_t *zone, ...@@ -43,7 +43,12 @@ void event_dnssec_reschedule(conf_t *conf, zone_t *zone,
log_dnssec_next(zone->name, (time_t)refresh_at); log_dnssec_next(zone->name, (time_t)refresh_at);
if (refresh->plan_ds_query) { if (refresh->plan_ds_query) {
log_zone_notice(zone->name, "DNSSEC, published CDS, CDNSKEY for submission"); conf_val_t id = conf_zone_get(conf, C_DNSSEC_POLICY, zone->name);
conf_val_t val = conf_id_get(conf, C_POLICY, C_CHILD_RECORDS, &id);
if (conf_opt(&val) != CHILD_RECORDS_NONE) {
log_zone_notice(zone->name, "DNSSEC, published CDS, CDNSKEY "
"for submission");
}
zone->timers.next_parent_ds_q = now; zone->timers.next_parent_ds_q = now;
} }
......
...@@ -16,16 +16,35 @@ ...@@ -16,16 +16,35 @@
#include <assert.h> #include <assert.h>
#include "knot/zone/zone.h"
#include "knot/common/log.h" #include "knot/common/log.h"
#include "knot/conf/conf.h" #include "knot/conf/conf.h"
#include "knot/dnssec/context.h" #include "knot/dnssec/context.h"
#include "knot/dnssec/key-events.h" #include "knot/dnssec/key-events.h"
#include "knot/dnssec/zone-keys.h" #include "knot/dnssec/zone-keys.h"
#include "knot/dnssec/zone-sign.h" // match key and DS rdata
#include "knot/query/layer.h" #include "knot/query/layer.h"
#include "knot/query/query.h" #include "knot/query/query.h"
#include "knot/query/requestor.h" #include "knot/query/requestor.h"
#include "knot/zone/zone.h"
static bool match_key_ds(zone_key_t *key, const knot_rdata_t *ds)
{
assert(key);
assert(ds);
dnssec_binary_t ds_rdata = {
.data = knot_rdata_data(ds),
.size = knot_rdata_rdlen(ds)
};
dnssec_binary_t cds_rdata = { 0 };
int ret = zone_key_calculate_ds(key, &cds_rdata);
if (ret != KNOT_EOK) {
return false;
}
return (dnssec_binary_cmp(&cds_rdata, &ds_rdata) == 0);
}
struct ds_query_data { struct ds_query_data {
zone_t *zone; zone_t *zone;
...@@ -84,7 +103,7 @@ static int ds_query_consume(knot_layer_t *layer, knot_pkt_t *pkt) ...@@ -84,7 +103,7 @@ static int ds_query_consume(knot_layer_t *layer, knot_pkt_t *pkt)
return KNOT_STATE_FAIL; return KNOT_STATE_FAIL;
} }
if (knot_match_key_ds(data->key, knot_rdataset_at(&rr->rrs, 0))) { if (match_key_ds(data->key, knot_rdataset_at(&rr->rrs, 0))) {
match = true; match = true;
break; break;
} }
...@@ -234,4 +253,3 @@ int event_parent_ds_q(conf_t *conf, zone_t *zone) ...@@ -234,4 +253,3 @@ int event_parent_ds_q(conf_t *conf, zone_t *zone)
return KNOT_EOK; // allways ok, if failure it has been rescheduled return KNOT_EOK; // allways ok, if failure it has been rescheduled
} }
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