|
|
e0ab38 |
From 6c012544655b3730ebb0a1551cdbce04ab686cfb Mon Sep 17 00:00:00 2001
|
|
|
e0ab38 |
From: Petr Spacek <pspacek@redhat.com>
|
|
|
e0ab38 |
Date: Tue, 24 Nov 2015 12:49:40 +0100
|
|
|
e0ab38 |
Subject: [PATCH] DNSSEC: Make sure that current state in OpenDNSSEC matches
|
|
|
e0ab38 |
key state in LDAP
|
|
|
e0ab38 |
|
|
|
e0ab38 |
Previously we published timestamps of planned state changes in LDAP.
|
|
|
e0ab38 |
This led to situations where state transition in OpenDNSSEC was blocked
|
|
|
e0ab38 |
by an additional condition (or unavailability of OpenDNSSEC) but BIND
|
|
|
e0ab38 |
actually did the transition as planned.
|
|
|
e0ab38 |
|
|
|
e0ab38 |
Additionally key state mapping was incorrect for KSK so sometimes KSK
|
|
|
e0ab38 |
was not used for signing when it should.
|
|
|
e0ab38 |
|
|
|
e0ab38 |
Example (for code without this fix):
|
|
|
e0ab38 |
- Add a zone and let OpenDNSSEC to generate keys.
|
|
|
e0ab38 |
- Wait until keys are in state "published" and next state is "inactive".
|
|
|
e0ab38 |
- Shutdown OpenDNSSEC or break replication from DNSSEC key master.
|
|
|
e0ab38 |
- See that keys on DNS replicas will transition to state "inactive" even
|
|
|
e0ab38 |
though it should not happen because OpenDNSSEC is not available
|
|
|
e0ab38 |
(i.e. new keys may not be available).
|
|
|
e0ab38 |
- End result is that affected zone will not be signed anymore, even
|
|
|
e0ab38 |
though it should stay signed with the old keys.
|
|
|
e0ab38 |
|
|
|
e0ab38 |
https://fedorahosted.org/freeipa/ticket/5348
|
|
|
e0ab38 |
|
|
|
e0ab38 |
Reviewed-By: Martin Basti <mbasti@redhat.com>
|
|
|
e0ab38 |
Reviewed-By: Martin Basti <mbasti@redhat.com>
|
|
|
e0ab38 |
---
|
|
|
e0ab38 |
daemons/dnssec/ipa-ods-exporter | 105 ++++++++++++++++++++++++++++++++++++----
|
|
|
e0ab38 |
1 file changed, 95 insertions(+), 10 deletions(-)
|
|
|
e0ab38 |
|
|
|
e0ab38 |
diff --git a/daemons/dnssec/ipa-ods-exporter b/daemons/dnssec/ipa-ods-exporter
|
|
|
e0ab38 |
index 12a9294ae05d2ce8d206a2bbf74cc00d81259efa..6ed7588847042e742abeef724940eec31f23ca8f 100755
|
|
|
e0ab38 |
--- a/daemons/dnssec/ipa-ods-exporter
|
|
|
e0ab38 |
+++ b/daemons/dnssec/ipa-ods-exporter
|
|
|
e0ab38 |
@@ -57,6 +57,14 @@ ODS_DB_LOCK_PATH = "%s%s" % (paths.OPENDNSSEC_KASP_DB, '.our_lock')
|
|
|
e0ab38 |
SECRETKEY_WRAPPING_MECH = 'rsaPkcsOaep'
|
|
|
e0ab38 |
PRIVKEY_WRAPPING_MECH = 'aesKeyWrapPad'
|
|
|
e0ab38 |
|
|
|
e0ab38 |
+# Constants from OpenDNSSEC's enforcer/ksm/include/ksm/ksm.h
|
|
|
e0ab38 |
+KSM_STATE_PUBLISH = 2
|
|
|
e0ab38 |
+KSM_STATE_READY = 3
|
|
|
e0ab38 |
+KSM_STATE_ACTIVE = 4
|
|
|
e0ab38 |
+KSM_STATE_RETIRE = 5
|
|
|
e0ab38 |
+KSM_STATE_DEAD = 6
|
|
|
e0ab38 |
+KSM_STATE_KEYPUBLISH = 10
|
|
|
e0ab38 |
+
|
|
|
e0ab38 |
# DNSKEY flag constants
|
|
|
e0ab38 |
dnskey_flag_by_value = {
|
|
|
e0ab38 |
0x0001: 'SEP',
|
|
|
e0ab38 |
@@ -122,6 +130,77 @@ def sql2ldap_keyid(sql_keyid):
|
|
|
e0ab38 |
#uri += '%'.join(sql_keyid[i:i+2] for i in range(0, len(sql_keyid), 2))
|
|
|
e0ab38 |
return {"idnsSecKeyRef": uri}
|
|
|
e0ab38 |
|
|
|
e0ab38 |
+def ods2bind_timestamps(key_state, key_type, ods_times):
|
|
|
e0ab38 |
+ """Transform (timestamps and key states) from ODS to set of BIND timestamps
|
|
|
e0ab38 |
+ with equivalent meaning. At the same time, remove timestamps
|
|
|
e0ab38 |
+ for future/planned state transitions to prevent ODS & BIND
|
|
|
e0ab38 |
+ from desynchronizing.
|
|
|
e0ab38 |
+
|
|
|
e0ab38 |
+ OpenDNSSEC database may contain timestamps for state transitions planned
|
|
|
e0ab38 |
+ in the future, but timestamp itself is not sufficient information because
|
|
|
e0ab38 |
+ there could be some additional condition which is guaded by OpenDNSSEC
|
|
|
e0ab38 |
+ itself.
|
|
|
e0ab38 |
+
|
|
|
e0ab38 |
+ BIND works directly with timestamps without any additional conditions.
|
|
|
e0ab38 |
+ This difference causes problem when state transition planned in OpenDNSSEC
|
|
|
e0ab38 |
+ does not happen as originally planned for some reason.
|
|
|
e0ab38 |
+
|
|
|
e0ab38 |
+ At the same time, this difference causes problem when OpenDNSSEC on DNSSEC
|
|
|
e0ab38 |
+ key master and BIND instances on replicas are not synchronized. This
|
|
|
e0ab38 |
+ happens when DNSSEC key master is down, or a replication is down. Even
|
|
|
e0ab38 |
+ a temporary desynchronization could cause DNSSEC validation failures
|
|
|
e0ab38 |
+ which could have huge impact.
|
|
|
e0ab38 |
+
|
|
|
e0ab38 |
+ To prevent this problem, this function removes all timestamps corresponding
|
|
|
e0ab38 |
+ to future state transitions. As a result, BIND will not do state transition
|
|
|
e0ab38 |
+ until it happens in OpenDNSSEC first and until the change is replicated.
|
|
|
e0ab38 |
+
|
|
|
e0ab38 |
+ Also, timestamp mapping depends on key type and is not 1:1.
|
|
|
e0ab38 |
+ For detailed description of the mapping please see
|
|
|
e0ab38 |
+ https://fedorahosted.org/bind-dyndb-ldap/wiki/BIND9/Design/DNSSEC/OpenDNSSEC2BINDKeyStates
|
|
|
e0ab38 |
+ """
|
|
|
e0ab38 |
+ bind_times = {}
|
|
|
e0ab38 |
+ # idnsSecKeyCreated is equivalent to SQL column 'created'
|
|
|
e0ab38 |
+ bind_times['idnsSecKeyCreated'] = ods_times['idnsSecKeyCreated']
|
|
|
e0ab38 |
+
|
|
|
e0ab38 |
+ # set of key states where publishing in DNS zone is desired is taken from
|
|
|
e0ab38 |
+ # opendnssec/enforcer/ksm/ksm_request.c:KsmRequestIssueKeys()
|
|
|
e0ab38 |
+ # TODO: support for RFC 5011, requires OpenDNSSEC v1.4.8+
|
|
|
e0ab38 |
+ if ('idnsSecKeyPublish' in ods_times and
|
|
|
e0ab38 |
+ key_state in {KSM_STATE_PUBLISH, KSM_STATE_READY, KSM_STATE_ACTIVE,
|
|
|
e0ab38 |
+ KSM_STATE_RETIRE, KSM_STATE_KEYPUBLISH}):
|
|
|
e0ab38 |
+ bind_times['idnsSecKeyPublish'] = ods_times['idnsSecKeyPublish']
|
|
|
e0ab38 |
+
|
|
|
e0ab38 |
+ # ZSK and KSK handling differs in enforcerd, see
|
|
|
e0ab38 |
+ # opendnssec/enforcer/enforcerd/enforcer.c:commKeyConfig()
|
|
|
e0ab38 |
+ if key_type == 'ZSK':
|
|
|
e0ab38 |
+ # idnsSecKeyActivate cannot be set before the key reaches ACTIVE state
|
|
|
e0ab38 |
+ if ('idnsSecKeyActivate' in ods_times and
|
|
|
e0ab38 |
+ key_state in {KSM_STATE_ACTIVE, KSM_STATE_RETIRE, KSM_STATE_DEAD}):
|
|
|
e0ab38 |
+ bind_times['idnsSecKeyActivate'] = ods_times['idnsSecKeyActivate']
|
|
|
e0ab38 |
+
|
|
|
e0ab38 |
+ # idnsSecKeyInactive cannot be set before the key reaches RETIRE state
|
|
|
e0ab38 |
+ if ('idnsSecKeyInactive' in ods_times and
|
|
|
e0ab38 |
+ key_state in {KSM_STATE_RETIRE, KSM_STATE_DEAD}):
|
|
|
e0ab38 |
+ bind_times['idnsSecKeyInactive'] = ods_times['idnsSecKeyInactive']
|
|
|
e0ab38 |
+
|
|
|
e0ab38 |
+ elif key_type == 'KSK':
|
|
|
e0ab38 |
+ # KSK is special: it is used for signing as long as it is in zone
|
|
|
e0ab38 |
+ if ('idnsSecKeyPublish' in ods_times and
|
|
|
e0ab38 |
+ key_state in {KSM_STATE_PUBLISH, KSM_STATE_READY, KSM_STATE_ACTIVE,
|
|
|
e0ab38 |
+ KSM_STATE_RETIRE, KSM_STATE_KEYPUBLISH}):
|
|
|
e0ab38 |
+ bind_times['idnsSecKeyActivate'] = ods_times['idnsSecKeyPublish']
|
|
|
e0ab38 |
+ # idnsSecKeyInactive is ignored for KSK on purpose
|
|
|
e0ab38 |
+
|
|
|
e0ab38 |
+ else:
|
|
|
e0ab38 |
+ assert False, "unsupported key type %s" % key_type
|
|
|
e0ab38 |
+
|
|
|
e0ab38 |
+ # idnsSecKeyDelete is relevant only in DEAD state
|
|
|
e0ab38 |
+ if 'idnsSecKeyDelete' in ods_times and key_state == KSM_STATE_DEAD:
|
|
|
e0ab38 |
+ bind_times['idnsSecKeyDelete'] = ods_times['idnsSecKeyDelete']
|
|
|
e0ab38 |
+
|
|
|
e0ab38 |
+ return bind_times
|
|
|
e0ab38 |
+
|
|
|
e0ab38 |
class ods_db_lock(object):
|
|
|
e0ab38 |
def __enter__(self):
|
|
|
e0ab38 |
self.f = open(ODS_DB_LOCK_PATH, 'w')
|
|
|
e0ab38 |
@@ -172,18 +251,20 @@ def get_ods_keys(zone_name):
|
|
|
e0ab38 |
assert len(rows) == 1, "exactly one DNS zone should exist in ODS DB"
|
|
|
e0ab38 |
zone_id = rows[0][0]
|
|
|
e0ab38 |
|
|
|
e0ab38 |
- # get all keys for given zone ID
|
|
|
e0ab38 |
- cur = db.execute("SELECT kp.HSMkey_id, kp.generate, kp.algorithm, dnsk.publish, dnsk.active, dnsk.retire, dnsk.dead, dnsk.keytype "
|
|
|
e0ab38 |
- "FROM keypairs AS kp JOIN dnsseckeys AS dnsk ON kp.id = dnsk.keypair_id "
|
|
|
e0ab38 |
- "WHERE dnsk.zone_id = ?", (zone_id,))
|
|
|
e0ab38 |
+ # get relevant keys for given zone ID:
|
|
|
e0ab38 |
+ # ignore keys which were generated but not used yet
|
|
|
e0ab38 |
+ # key state check is using constants from
|
|
|
e0ab38 |
+ # OpenDNSSEC's enforcer/ksm/include/ksm/ksm.h
|
|
|
e0ab38 |
+ # WARNING! OpenDNSSEC version 1 and 2 are using different constants!
|
|
|
e0ab38 |
+ cur = db.execute("SELECT kp.HSMkey_id, kp.generate, kp.algorithm, "
|
|
|
e0ab38 |
+ "dnsk.publish, dnsk.active, dnsk.retire, dnsk.dead, "
|
|
|
e0ab38 |
+ "dnsk.keytype, dnsk.state "
|
|
|
e0ab38 |
+ "FROM keypairs AS kp "
|
|
|
e0ab38 |
+ "JOIN dnsseckeys AS dnsk ON kp.id = dnsk.keypair_id "
|
|
|
e0ab38 |
+ "WHERE dnsk.zone_id = ?", (zone_id,))
|
|
|
e0ab38 |
keys = {}
|
|
|
e0ab38 |
for row in cur:
|
|
|
e0ab38 |
- key_data = sql2datetimes(row)
|
|
|
e0ab38 |
- if 'idnsSecKeyDelete' in key_data \
|
|
|
e0ab38 |
- and key_data['idnsSecKeyDelete'] > datetime.now():
|
|
|
e0ab38 |
- continue # ignore deleted keys
|
|
|
e0ab38 |
-
|
|
|
e0ab38 |
- key_data.update(sql2ldap_flags(row['keytype']))
|
|
|
e0ab38 |
+ key_data = sql2ldap_flags(row['keytype'])
|
|
|
e0ab38 |
assert key_data.get('idnsSecKeyZONE', None) == 'TRUE', \
|
|
|
e0ab38 |
'unexpected key type 0x%x' % row['keytype']
|
|
|
e0ab38 |
if key_data.get('idnsSecKeySEP', 'FALSE') == 'TRUE':
|
|
|
e0ab38 |
@@ -191,6 +272,10 @@ def get_ods_keys(zone_name):
|
|
|
e0ab38 |
else:
|
|
|
e0ab38 |
key_type = 'ZSK'
|
|
|
e0ab38 |
|
|
|
e0ab38 |
+ # transform key state to timestamps for BIND with equivalent semantics
|
|
|
e0ab38 |
+ ods_times = sql2datetimes(row)
|
|
|
e0ab38 |
+ key_data.update(ods2bind_timestamps(row['state'], key_type, ods_times))
|
|
|
e0ab38 |
+
|
|
|
e0ab38 |
key_data.update(sql2ldap_algorithm(row['algorithm']))
|
|
|
e0ab38 |
key_id = "%s-%s-%s" % (key_type,
|
|
|
e0ab38 |
datetime2ldap(key_data['idnsSecKeyCreated']),
|
|
|
e0ab38 |
--
|
|
|
e0ab38 |
2.4.3
|
|
|
e0ab38 |
|