#!/usr/bin/python3 # # Copyright (C) 2014 FreeIPA Contributors see COPYING for license # """ Download keys from LDAP to local HSM. This program should be run only on replicas, not on DNSSEC masters. """ from gssapi.exceptions import GSSError import logging import os import sys import ipalib from ipalib import errors from ipalib.constants import SOFTHSM_DNSSEC_TOKEN_LABEL from ipalib.install.kinit import kinit_keytab from ipapython.dn import DN from ipapython.ipa_log_manager import standard_logging_setup from ipapython.ipautil import get_config_debug from ipapython import ipaldap from ipaplatform.paths import paths from ipaserver.dnssec.abshsm import (sync_pkcs11_metadata, ldap2p11helper_api_params, wrappingmech_name2id) from ipaserver.dnssec.ldapkeydb import LdapKeyDB, str_hexlify from ipaserver.dnssec.localhsm import LocalHSM logger = logging.getLogger(os.path.basename(__file__)) DAEMONNAME = 'ipa-dnskeysyncd' PRINCIPAL = None # not initialized yet WORKDIR = '/tmp' def hex_set(s): out = set() for i in s: out.add("0x%s" % str_hexlify(i)) return out def update_metadata_set(source_set, target_set): """sync metadata from source key set to target key set Keys not present in both sets are left intact.""" name = 'sync_metadata' matching_keys = set(source_set.keys()).intersection(set(target_set.keys())) logger.info("%s: keys in local HSM & LDAP: %s", name, hex_set(matching_keys)) for key_id in matching_keys: sync_pkcs11_metadata(name, source_set[key_id], target_set[key_id]) def find_unwrapping_key(localhsm, wrapping_key_uri): wrap_keys = localhsm.find_keys(uri=wrapping_key_uri) # find usable unwrapping key with matching ID for key_id in wrap_keys.keys(): unwrap_keys = localhsm.find_keys(id=key_id, cka_unwrap=True) if len(unwrap_keys) > 0: return unwrap_keys.popitem()[1] return None def ldap2replica_master_keys_sync(ldapkeydb, localhsm): ## LDAP -> replica master key synchronization # import new master keys from LDAP new_keys = set(ldapkeydb.master_keys.keys()) \ - set(localhsm.master_keys.keys()) logger.debug("master keys in local HSM: %s", hex_set(localhsm.master_keys.keys())) logger.debug("master keys in LDAP HSM: %s", hex_set(ldapkeydb.master_keys.keys())) logger.debug("new master keys in LDAP HSM: %s", hex_set(new_keys)) for mkey_id in new_keys: mkey_ldap = ldapkeydb.master_keys[mkey_id] if not mkey_ldap.wrapped_entries: raise ValueError( "Master key 0x%s in LDAP is missing key material " "referenced by ipaSecretKeyRefObject attribute" % str_hexlify(mkey_id) ) for wrapped_ldap in mkey_ldap.wrapped_entries: unwrapping_key = find_unwrapping_key( localhsm, wrapped_ldap.single_value['ipaWrappingKey']) if unwrapping_key: break # TODO: Could it happen in normal cases? if unwrapping_key is None: raise ValueError( "Local HSM does not contain suitable unwrapping key " "for master key 0x%s" % str_hexlify(mkey_id) ) params = ldap2p11helper_api_params(mkey_ldap) params['data'] = wrapped_ldap.single_value['ipaSecretKey'] params['unwrapping_key'] = unwrapping_key.handle params['wrapping_mech'] = wrappingmech_name2id[wrapped_ldap.single_value['ipaWrappingMech']] logger.debug('Importing new master key: 0x%s %s', str_hexlify(mkey_id), params) localhsm.p11.import_wrapped_secret_key(**params) # synchronize metadata about master keys in LDAP update_metadata_set(ldapkeydb.master_keys, localhsm.master_keys) def ldap2replica_zone_keys_sync(ldapkeydb, localhsm): ## LDAP -> replica zone key synchronization # import new zone keys from LDAP new_keys = set(ldapkeydb.zone_keypairs.keys()) \ - set(localhsm.zone_privkeys.keys()) logger.debug("zone keys in local HSM: %s", hex_set(localhsm.master_keys.keys())) logger.debug("zone keys in LDAP HSM: %s", hex_set(ldapkeydb.master_keys.keys())) logger.debug("new zone keys in LDAP HSM: %s", hex_set(new_keys)) for zkey_id in new_keys: zkey_ldap = ldapkeydb.zone_keypairs[zkey_id] logger.debug('Looking for unwrapping key "%s" for zone key 0x%s', zkey_ldap['ipaWrappingKey'], str_hexlify(zkey_id)) unwrapping_key = find_unwrapping_key( localhsm, zkey_ldap['ipaWrappingKey']) if unwrapping_key is None: raise ValueError( "Local HSM does not contain suitable unwrapping key for " "zone key 0x%s" % str_hexlify(zkey_id) ) logger.debug('Importing zone key pair 0x%s', str_hexlify(zkey_id)) localhsm.import_private_key(zkey_ldap, zkey_ldap['ipaPrivateKey'], unwrapping_key) localhsm.import_public_key(zkey_ldap, zkey_ldap['ipaPublicKey']) # synchronize metadata about zone keys in LDAP & local HSM update_metadata_set(ldapkeydb.master_keys, localhsm.master_keys) # delete keys removed from LDAP deleted_keys = set(localhsm.zone_privkeys.keys()) \ - set(ldapkeydb.zone_keypairs.keys()) for zkey_id in deleted_keys: localhsm.p11.delete_key(localhsm.zone_pubkeys[zkey_id].handle) localhsm.p11.delete_key(localhsm.zone_privkeys[zkey_id].handle) # IPA framework initialization debug = get_config_debug('dns') standard_logging_setup(debug=debug, verbose=True) if not debug: logger.info("To increase debugging set debug=True in dns.conf " "See default.conf(5) for details") ipalib.api.bootstrap(context='dns', confdir=paths.ETC_IPA, in_server=True) ipalib.api.finalize() # Kerberos initialization PRINCIPAL = str('%s/%s' % (DAEMONNAME, ipalib.api.env.host)) logger.debug('Kerberos principal: %s', PRINCIPAL) ccache_filename = os.path.join(WORKDIR, 'ipa-dnskeysync-replica.ccache') try: kinit_keytab(PRINCIPAL, paths.IPA_DNSKEYSYNCD_KEYTAB, ccache_filename, attempts=5) except GSSError as e: logger.critical('Kerberos authentication failed: %s', e) sys.exit(1) os.environ['KRB5CCNAME'] = ccache_filename logger.debug('Got TGT') keys_dn = DN( ('cn', 'keys'), ('cn', 'sec'), ipalib.api.env.container_dns, ipalib.api.env.basedn ) with open(paths.DNSSEC_SOFTHSM_PIN) as f: localhsm = LocalHSM( paths.LIBSOFTHSM2_SO, SOFTHSM_DNSSEC_TOKEN_LABEL, f.read() ) try: # LDAP initialization ldap = ipaldap.LDAPClient(ipalib.api.env.ldap_uri) logger.debug('Connecting to LDAP') ldap.gssapi_bind() logger.debug('Connected') ### DNSSEC master: key synchronization ldapkeydb = LdapKeyDB(ldap, keys_dn) ldap2replica_master_keys_sync(ldapkeydb, localhsm) ldap2replica_zone_keys_sync(ldapkeydb, localhsm) except (errors.NetworkError, errors.DatabaseError) as e: # SERVER_DOWN, CONNECT_ERROR logger.error("LDAP server is down: %s", e) sys.exit(1) else: sys.exit(0)