diff options
Diffstat (limited to 'ipapython/dnssec/keysyncer.py')
-rw-r--r-- | ipapython/dnssec/keysyncer.py | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/ipapython/dnssec/keysyncer.py b/ipapython/dnssec/keysyncer.py new file mode 100644 index 000000000..1b2757326 --- /dev/null +++ b/ipapython/dnssec/keysyncer.py @@ -0,0 +1,181 @@ +#!/usr/bin/python +# +# Copyright (C) 2014 FreeIPA Contributors see COPYING for license +# + +import logging +import ldap.dn +import os + +from ipaplatform.paths import paths +from ipapython import ipautil + +from syncrepl import SyncReplConsumer +from odsmgr import ODSMgr +from bindmgr import BINDMgr + +SIGNING_ATTR = 'idnsSecInlineSigning' +OBJCLASS_ATTR = 'objectClass' + + +class KeySyncer(SyncReplConsumer): + def __init__(self, *args, **kwargs): + # hack + self.api = kwargs['ipa_api'] + del kwargs['ipa_api'] + + # DNSSEC master should have OpenDNSSEC installed + # TODO: Is this the best way? + if os.environ.get('ISMASTER', '0') == '1': + self.ismaster = True + self.odsmgr = ODSMgr() + else: + self.ismaster = False + + self.bindmgr = BINDMgr(self.api) + self.init_done = False + SyncReplConsumer.__init__(self, *args, **kwargs) + + def _get_objclass(self, attrs): + """Get object class. + + Given set of attributes has to have exactly one supported object class. + """ + supported_objclasses = set(['idnszone', 'idnsseckey', 'ipk11publickey']) + present_objclasses = set([o.lower() for o in attrs[OBJCLASS_ATTR]]).intersection(supported_objclasses) + assert len(present_objclasses) == 1, attrs[OBJCLASS_ATTR] + return present_objclasses.pop() + + def __get_signing_attr(self, attrs): + """Get SIGNING_ATTR from dictionary with LDAP zone attributes. + + Returned value is normalized to TRUE or FALSE, defaults to FALSE.""" + values = attrs.get(SIGNING_ATTR, ['FALSE']) + assert len(values) == 1, '%s is expected to be single-valued' \ + % SIGNING_ATTR + return values[0].upper() + + def __is_dnssec_enabled(self, attrs): + """Test if LDAP DNS zone with given attributes is DNSSEC enabled.""" + return self.__get_signing_attr(attrs) == 'TRUE' + + def __is_replica_pubkey(self, attrs): + vals = attrs.get('ipk11label', []) + if len(vals) != 1: + return False + return vals[0].startswith('dnssec-replica:') + + def application_add(self, uuid, dn, newattrs): + objclass = self._get_objclass(newattrs) + if objclass == 'idnszone': + self.zone_add(uuid, dn, newattrs) + elif objclass == 'idnsseckey': + self.key_meta_add(uuid, dn, newattrs) + elif objclass == 'ipk11publickey' and \ + self.__is_replica_pubkey(newattrs): + self.hsm_master_sync() + + def application_del(self, uuid, dn, oldattrs): + objclass = self._get_objclass(oldattrs) + if objclass == 'idnszone': + self.zone_del(uuid, dn, oldattrs) + elif objclass == 'idnsseckey': + self.key_meta_del(uuid, dn, oldattrs) + elif objclass == 'ipk11publickey' and \ + self.__is_replica_pubkey(oldattrs): + self.hsm_master_sync() + + def application_sync(self, uuid, dn, newattrs, oldattrs): + objclass = self._get_objclass(oldattrs) + if objclass == 'idnszone': + olddn = ldap.dn.str2dn(oldattrs['dn']) + newdn = ldap.dn.str2dn(newattrs['dn']) + assert olddn == newdn, 'modrdn operation is not supported' + + oldval = self.__get_signing_attr(oldattrs) + newval = self.__get_signing_attr(newattrs) + if oldval != newval: + if self.__is_dnssec_enabled(newattrs): + self.zone_add(uuid, olddn, newattrs) + else: + self.zone_del(uuid, olddn, oldattrs) + + elif objclass == 'idnsseckey': + self.key_metadata_sync(uuid, dn, oldattrs, newattrs) + + elif objclass == 'ipk11publickey' and \ + self.__is_replica_pubkey(newattrs): + self.hsm_master_sync() + + def syncrepl_refreshdone(self): + self.log.info('Initial LDAP dump is done, sychronizing with ODS and BIND') + self.init_done = True + self.ods_sync() + self.hsm_replica_sync() + self.hsm_master_sync() + self.bindmgr.sync() + + # idnsSecKey wrapper + # Assumption: metadata points to the same key blob all the time, + # i.e. it is not necessary to re-download blobs because of change in DNSSEC + # metadata - DNSSEC flags or timestamps. + def key_meta_add(self, uuid, dn, newattrs): + self.hsm_replica_sync() + self.bindmgr.ldap_event('add', uuid, newattrs) + self.bindmgr_sync() + + def key_meta_del(self, uuid, dn, oldattrs): + self.bindmgr.ldap_event('del', uuid, oldattrs) + self.bindmgr_sync() + self.hsm_replica_sync() + + def key_metadata_sync(self, uuid, dn, oldattrs, newattrs): + self.bindmgr.ldap_event('mod', uuid, newattrs) + self.bindmgr_sync() + + def bindmgr_sync(self): + if self.init_done: + self.bindmgr.sync() + + # idnsZone wrapper + def zone_add(self, uuid, dn, newattrs): + if not self.ismaster: + return + + if self.__is_dnssec_enabled(newattrs): + self.odsmgr.ldap_event('add', uuid, newattrs) + self.ods_sync() + + def zone_del(self, uuid, dn, oldattrs): + if not self.ismaster: + return + + if self.__is_dnssec_enabled(oldattrs): + self.odsmgr.ldap_event('del', uuid, oldattrs) + self.ods_sync() + + def ods_sync(self): + if not self.ismaster: + return + + if self.init_done: + self.odsmgr.sync() + + # triggered by modification to idnsSecKey objects + def hsm_replica_sync(self): + """Download keys from LDAP to local HSM.""" + if self.ismaster: + return + if not self.init_done: + return + ipautil.run([paths.IPA_DNSKEYSYNCD_REPLICA]) + + # triggered by modification to ipk11PublicKey objects + def hsm_master_sync(self): + """Download replica keys from LDAP to local HSM + & upload master and zone keys to LDAP.""" + if not self.ismaster: + return + if not self.init_done: + return + ipautil.run([paths.ODS_SIGNER]) |