mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-23 23:50:03 -06:00
b5bdd07bc5
Add absolute_import from __future__ so that pylint does not fail and to achieve python3 behavior in python2. Reviewed-By: Christian Heimes <cheimes@redhat.com> Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
203 lines
6.6 KiB
Python
203 lines
6.6 KiB
Python
#
|
|
# Copyright (C) 2014 FreeIPA Contributors see COPYING for license
|
|
#
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import logging
|
|
|
|
import ldap.dn
|
|
import os
|
|
|
|
import dns.name
|
|
|
|
from ipaplatform.paths import paths
|
|
from ipapython import ipautil
|
|
|
|
from ipaserver.dnssec.syncrepl import SyncReplConsumer
|
|
from ipaserver.dnssec.odsmgr import ODSMgr
|
|
from ipaserver.dnssec.bindmgr import BINDMgr
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
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
|
|
self.dnssec_zones = set()
|
|
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 = {b'idnszone', b'idnsseckey', b'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, [b'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) == b'TRUE'
|
|
|
|
def __is_replica_pubkey(self, attrs):
|
|
vals = attrs.get('ipk11label', [])
|
|
if len(vals) != 1:
|
|
return False
|
|
return vals[0].startswith(b'dnssec-replica:')
|
|
|
|
def application_add(self, uuid, dn, newattrs):
|
|
objclass = self._get_objclass(newattrs)
|
|
if objclass == b'idnszone':
|
|
self.zone_add(uuid, dn, newattrs)
|
|
elif objclass == b'idnsseckey':
|
|
self.key_meta_add(uuid, dn, newattrs)
|
|
elif objclass == b'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 == b'idnszone':
|
|
self.zone_del(uuid, dn, oldattrs)
|
|
elif objclass == b'idnsseckey':
|
|
self.key_meta_del(uuid, dn, oldattrs)
|
|
elif objclass == b'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 == b'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 == b'idnsseckey':
|
|
self.key_metadata_sync(uuid, dn, oldattrs, newattrs)
|
|
|
|
elif objclass == b'ipk11publickey' and \
|
|
self.__is_replica_pubkey(newattrs):
|
|
self.hsm_master_sync()
|
|
|
|
def syncrepl_refreshdone(self):
|
|
logger.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(self.dnssec_zones)
|
|
|
|
# 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(self.dnssec_zones)
|
|
|
|
def key_meta_del(self, uuid, dn, oldattrs):
|
|
self.bindmgr.ldap_event('del', uuid, oldattrs)
|
|
self.bindmgr_sync(self.dnssec_zones)
|
|
self.hsm_replica_sync()
|
|
|
|
def key_metadata_sync(self, uuid, dn, oldattrs, newattrs):
|
|
self.bindmgr.ldap_event('mod', uuid, newattrs)
|
|
self.bindmgr_sync(self.dnssec_zones)
|
|
|
|
def bindmgr_sync(self, dnssec_zones):
|
|
if self.init_done:
|
|
self.bindmgr.sync(dnssec_zones)
|
|
|
|
# idnsZone wrapper
|
|
def zone_add(self, uuid, dn, newattrs):
|
|
zone = dns.name.from_text(newattrs['idnsname'][0])
|
|
if self.__is_dnssec_enabled(newattrs):
|
|
self.dnssec_zones.add(zone)
|
|
else:
|
|
self.dnssec_zones.discard(zone)
|
|
|
|
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):
|
|
zone = dns.name.from_text(oldattrs['idnsname'][0])
|
|
self.dnssec_zones.discard(zone)
|
|
|
|
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, 'ipa-hsm-update'])
|