freeipa/ipapython/dnssec/keysyncer.py
Petr Spacek 43acb994f6 DNSSEC: ipa-dnskeysyncd: Skip zones with old DNSSEC metadata in LDAP
This filtering is useful in cases where LDAP contains DNS zones which
have old metadata objects and DNSSEC disabled. Such zones must be
ignored to prevent errors while calling dnssec-keyfromlabel or rndc.

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

Reviewed-By: Martin Basti <mbasti@redhat.com>
2016-01-07 14:13:23 +01:00

192 lines
6.4 KiB
Python

#
# Copyright (C) 2014 FreeIPA Contributors see COPYING for license
#
import ldap.dn
import os
import dns.name
from ipaplatform.paths import paths
from ipapython import ipautil
from ipapython.dnssec.syncrepl import SyncReplConsumer
from ipapython.dnssec.odsmgr import ODSMgr
from ipapython.dnssec.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
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 = 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(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'])