summaryrefslogtreecommitdiffstats
path: root/ipapython/dnssec/keysyncer.py
diff options
context:
space:
mode:
Diffstat (limited to 'ipapython/dnssec/keysyncer.py')
-rw-r--r--ipapython/dnssec/keysyncer.py181
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])