DNSSEC: remove keys purged by OpenDNSSEC from master HSM from LDAP

Key purging has to be only only after key metadata purging so
ipa-dnskeysyncd on replices does not fail while dereferencing
non-existing keys.

https://fedorahosted.org/freeipa/ticket/5334

Reviewed-By: Martin Basti <mbasti@redhat.com>
This commit is contained in:
Petr Spacek 2015-12-15 15:22:45 +01:00 committed by Martin Basti
parent 6bdc18d0c5
commit ddf7397a4b
2 changed files with 99 additions and 18 deletions

View File

@ -383,7 +383,10 @@ def master2ldap_master_keys_sync(log, ldapkeydb, localhsm):
ldapkeydb.flush()
def master2ldap_zone_keys_sync(log, ldapkeydb, localhsm):
# synchroniza zone keys
"""add and update zone key material from local HSM to LDAP
No key material will be removed, only new keys will be added or updated.
Key removal is hanled by master2ldap_zone_keys_purge()."""
log = log.getChild('master2ldap_zone_keys')
keypairs_ldap = ldapkeydb.zone_keypairs
log.debug("zone keys in LDAP: %s", hex_set(keypairs_ldap))
@ -392,10 +395,10 @@ def master2ldap_zone_keys_sync(log, ldapkeydb, localhsm):
privkeys_local = localhsm.zone_privkeys
log.debug("zone keys in local HSM: %s", hex_set(privkeys_local))
assert set(pubkeys_local) == set(privkeys_local), \
"IDs of private and public keys for DNS zones in local HSM does " \
"not match to key pairs: %s vs. %s" % \
(hex_set(pubkeys_local), hex_set(privkeys_local))
assert set(pubkeys_local) == set(privkeys_local), (
"IDs of private and public keys for DNS zones in local HSM does "
"not match to key pairs: %s vs. %s" %
(hex_set(pubkeys_local), hex_set(privkeys_local)))
new_keys = set(pubkeys_local) - set(keypairs_ldap)
log.debug("new zone keys in local HSM: %s", hex_set(new_keys))
@ -416,6 +419,29 @@ def master2ldap_zone_keys_sync(log, ldapkeydb, localhsm):
sync_set_metadata_2ldap(log, privkeys_local, keypairs_ldap)
ldapkeydb.flush()
def master2ldap_zone_keys_purge(log, ldapkeydb, localhsm):
"""purge removed key material from LDAP (but not metadata)
Keys which are present in LDAP but not in local HSM will be removed.
Key metadata must be removed first so references to removed key material
are removed before actually removing the keys."""
keypairs_ldap = ldapkeydb.zone_keypairs
log.debug("zone keys in LDAP: %s", hex_set(keypairs_ldap))
pubkeys_local = localhsm.zone_pubkeys
privkeys_local = localhsm.zone_privkeys
log.debug("zone keys in local HSM: %s", hex_set(privkeys_local))
assert set(pubkeys_local) == set(privkeys_local), \
"IDs of private and public keys for DNS zones in local HSM does " \
"not match to key pairs: %s vs. %s" % \
(hex_set(pubkeys_local), hex_set(privkeys_local))
deleted_key_ids = set(keypairs_ldap) - set(pubkeys_local)
log.debug("zone keys deleted from local HSM but present in LDAP: %s",
hex_set(deleted_key_ids))
for zkey_id in deleted_key_ids:
keypairs_ldap[zkey_id].schedule_deletion()
ldapkeydb.flush()
def hex_set(s):
out = set()
@ -595,7 +621,7 @@ ldap.gssapi_bind()
log.debug('Connected')
### DNSSEC master: key synchronization
### DNSSEC master: key material upload & synchronization (but not deletion)
ldapkeydb = LdapKeyDB(log, ldap, DN(('cn', 'keys'), ('cn', 'sec'),
ipalib.api.env.container_dns,
ipalib.api.env.basedn))
@ -607,7 +633,7 @@ master2ldap_master_keys_sync(log, ldapkeydb, localhsm)
master2ldap_zone_keys_sync(log, ldapkeydb, localhsm)
### DNSSEC master: DNSSEC key metadata upload
### DNSSEC master: DNSSEC key metadata upload & synchronization & deletion
# command receive is delayed so the command will stay in socket queue until
# the problem with LDAP server or HSM is fixed
try:
@ -661,6 +687,11 @@ try:
for zone_row in db.execute("SELECT name FROM zones"):
sync_zone(log, ldap, dns_dn, zone_row['name'])
### DNSSEC master: DNSSEC key material purging
# references to old key material were removed above in sync_zone()
# so now we can purge old key material from LDAP
master2ldap_zone_keys_purge(log, ldapkeydb, localhsm)
except Exception as ex:
msg = "ipa-ods-exporter exception: %s" % traceback.format_exc(ex)
log.exception(ex)

View File

@ -104,40 +104,56 @@ def get_default_attrs(object_classes):
result.update(defaults[cls])
return result
class Key(collections.MutableMapping):
"""abstraction to hide LDAP entry weirdnesses:
- non-normalized attribute names
- boolean attributes returned as strings
- planned entry deletion prevents subsequent use of the instance
"""
def __init__(self, entry, ldap, ldapkeydb):
self.entry = entry
self._delentry = None # indicates that object was deleted
self.ldap = ldap
self.ldapkeydb = ldapkeydb
self.log = ldap.log.getChild(__name__)
def __assert_not_deleted(self):
assert self.entry and not self._delentry, (
"attempt to use to-be-deleted entry %s detected"
% self._delentry.dn)
def __getitem__(self, key):
self.__assert_not_deleted()
val = self.entry.single_value[key]
if key.lower() in bool_attr_names:
val = ldap_bool(val)
return val
def __setitem__(self, key, value):
self.__assert_not_deleted()
self.entry[key] = value
def __delitem__(self, key):
self.__assert_not_deleted()
del self.entry[key]
def __iter__(self):
"""generates list of ipa names of all PKCS#11 attributes present in the object"""
self.__assert_not_deleted()
for ipa_name in list(self.entry.keys()):
lowercase = ipa_name.lower()
if lowercase in attrs_name2id:
yield lowercase
def __len__(self):
self.__assert_not_deleted()
return len(self.entry)
def __repr__(self):
if self._delentry:
return 'deleted entry: %s' % repr(self._delentry)
sanitized = dict(self.entry)
for attr in ['ipaPrivateKey', 'ipaPublicKey', 'ipk11publickeyinfo']:
if attr in sanitized:
@ -152,6 +168,49 @@ class Key(collections.MutableMapping):
if self.get(attr, empty) == default_attrs[attr]:
del self[attr]
def _update_key(self):
"""remove default values from LDAP entry and write back changes"""
if self._delentry:
self._delete_key()
return
self._cleanup_key()
try:
self.ldap.update_entry(self.entry)
except ipalib.errors.EmptyModlist:
pass
def _delete_key(self):
"""remove key metadata entry from LDAP
After calling this, the python object is no longer valid and all
subsequent method calls on it will fail.
"""
assert not self.entry, (
"Key._delete_key() called before Key.schedule_deletion()")
assert self._delentry, "Key._delete_key() called more than once"
self.log.debug('deleting key id 0x%s DN %s from LDAP',
hexlify(self._delentry.single_value['ipk11id']),
self._delentry.dn)
self.ldap.delete_entry(self._delentry)
self._delentry = None
self.ldap = None
self.ldapkeydb = None
def schedule_deletion(self):
"""schedule key deletion from LDAP
Calling schedule_deletion() will make this object incompatible with
normal Key. After that the object must not be read or modified.
Key metadata will be actually deleted when LdapKeyDB.flush() is called.
"""
assert not self._delentry, (
"Key.schedule_deletion() called more than once")
self._delentry = self.entry
self.entry = None
class ReplicaKey(Key):
# TODO: object class assert
def __init__(self, entry, ldap, ldapkeydb):
@ -238,21 +297,12 @@ class LdapKeyDB(AbstractHSM):
self._update_keys()
return keys
def _update_key(self, key):
"""remove default values from LDAP entry and write back changes"""
key._cleanup_key()
try:
self.ldap.update_entry(key.entry)
except ipalib.errors.EmptyModlist:
pass
def _update_keys(self):
for cache in [self.cache_masterkeys, self.cache_replica_pubkeys_wrap,
self.cache_zone_keypairs]:
self.cache_zone_keypairs]:
if cache:
for key in cache.values():
self._update_key(key)
key._update_key()
def flush(self):
"""write back content of caches to LDAP"""