# # Copyright (C) 2014 FreeIPA Contributors see COPYING for license # import os import pwd import grp import stat import shutil from subprocess import CalledProcessError from ipapython import p11helper as _ipap11helper from ipaserver.install import service from ipaserver.install import installutils from ipapython.ipa_log_manager import root_logger from ipapython.dn import DN from ipapython import sysrestore, ipautil, ipaldap, p11helper from ipaplatform import services from ipaplatform.paths import paths from ipalib import errors, api from ipaserver.install import dnskeysyncinstance KEYMASTER = u'dnssecKeyMaster' softhsm_slot = 0 def get_dnssec_key_masters(conn): """ This method can be used only for admin connections, common users do not have permission to access content of service containers. :return: list of active dnssec key masters """ assert conn is not None # please check ipalib/dns.py:dnssec_installed() method too, if you do # any modifications here dn = DN(api.env.container_masters, api.env.basedn) filter_attrs = { u'cn': u'DNSSEC', u'objectclass': u'ipaConfigObject', u'ipaConfigString': [KEYMASTER, u'enabledService'], } only_masters_f = conn.make_filter(filter_attrs, rules=conn.MATCH_ALL) try: entries = conn.find_entries(filter=only_masters_f, base_dn=dn) except errors.NotFound: return [] keymasters_list = [] for entry in entries[0]: keymasters_list.append(str(entry.dn[1].value)) return keymasters_list class OpenDNSSECInstance(service.Service): def __init__(self, fstore=None, dm_password=None, ldapi=False, start_tls=False, autobind=ipaldap.AUTOBIND_ENABLED): service.Service.__init__( self, "ods-enforcerd", service_desc="OpenDNSSEC enforcer daemon", dm_password=dm_password, ldapi=ldapi, autobind=autobind, start_tls=start_tls ) self.dm_password = dm_password self.ods_uid = None self.ods_gid = None self.conf_file_dict = { 'SOFTHSM_LIB': paths.LIBSOFTHSM2_SO, 'TOKEN_LABEL': dnskeysyncinstance.softhsm_token_label, 'KASP_DB': paths.OPENDNSSEC_KASP_DB, } self.kasp_file_dict = {} self.extra_config = [KEYMASTER] if fstore: self.fstore = fstore else: self.fstore = sysrestore.FileStore(paths.SYSRESTORE) suffix = ipautil.dn_attribute_property('_suffix') def get_masters(self): if not self.admin_conn: self.ldap_connect() return get_dnssec_key_masters(self.admin_conn) def create_instance(self, fqdn, realm_name, generate_master_key=True, kasp_db_file=None): if self.get_state("enabled") is None: self.backup_state("enabled", self.is_enabled()) if self.get_state("running") is None: self.backup_state("running", self.is_running()) self.fqdn = fqdn self.realm = realm_name self.suffix = ipautil.realm_to_suffix(self.realm) self.kasp_db_file = kasp_db_file try: self.stop() except Exception: pass # get a connection to the DS if not self.admin_conn: self.ldap_connect() # checking status must be first self.step("checking status", self.__check_dnssec_status) self.step("setting up configuration files", self.__setup_conf_files) self.step("setting up ownership and file mode bits", self.__setup_ownership_file_modes) if generate_master_key: self.step("generating master key", self.__generate_master_key) self.step("setting up OpenDNSSEC", self.__setup_dnssec) self.step("setting up ipa-dnskeysyncd", self.__setup_dnskeysyncd) self.step("starting OpenDNSSEC enforcer", self.__start) self.step("configuring OpenDNSSEC enforcer to start on boot", self.__enable) self.start_creation() def __check_dnssec_status(self): named = services.knownservices.named ods_enforcerd = services.knownservices.ods_enforcerd try: self.named_uid = pwd.getpwnam(named.get_user_name()).pw_uid except KeyError: raise RuntimeError("Named UID not found") try: self.named_gid = grp.getgrnam(named.get_group_name()).gr_gid except KeyError: raise RuntimeError("Named GID not found") try: self.ods_uid = pwd.getpwnam(ods_enforcerd.get_user_name()).pw_uid except KeyError: raise RuntimeError("OpenDNSSEC UID not found") try: self.ods_gid = grp.getgrnam(ods_enforcerd.get_group_name()).gr_gid except KeyError: raise RuntimeError("OpenDNSSEC GID not found") def __enable(self): try: self.ldap_enable('DNSSEC', self.fqdn, self.dm_password, self.suffix, self.extra_config) except errors.DuplicateEntry: root_logger.error("DNSSEC service already exists") # add the KEYMASTER identifier into ipaConfigString # this is needed for the re-enabled DNSSEC master dn = DN(('cn', 'DNSSEC'), ('cn', self.fqdn), api.env.container_masters, api.env.basedn) try: entry = self.admin_conn.get_entry(dn, ['ipaConfigString']) except errors.NotFound as e: root_logger.error( "DNSSEC service entry not found in the LDAP (%s)", e) else: config = entry.setdefault('ipaConfigString', []) if KEYMASTER not in config: config.append(KEYMASTER) self.admin_conn.update_entry(entry) def __setup_conf_files(self): if not self.fstore.has_file(paths.OPENDNSSEC_CONF_FILE): self.fstore.backup_file(paths.OPENDNSSEC_CONF_FILE) if not self.fstore.has_file(paths.OPENDNSSEC_KASP_FILE): self.fstore.backup_file(paths.OPENDNSSEC_KASP_FILE) if not self.fstore.has_file(paths.OPENDNSSEC_ZONELIST_FILE): self.fstore.backup_file(paths.OPENDNSSEC_ZONELIST_FILE) pin_fd = open(paths.DNSSEC_SOFTHSM_PIN, "r") pin = pin_fd.read() pin_fd.close() # add pin to template sub_conf_dict = self.conf_file_dict sub_conf_dict['PIN'] = pin ods_conf_txt = ipautil.template_file( ipautil.SHARE_DIR + "opendnssec_conf.template", sub_conf_dict) ods_conf_fd = open(paths.OPENDNSSEC_CONF_FILE, 'w') ods_conf_fd.seek(0) ods_conf_fd.truncate(0) ods_conf_fd.write(ods_conf_txt) ods_conf_fd.close() ods_kasp_txt = ipautil.template_file( ipautil.SHARE_DIR + "opendnssec_kasp.template", self.kasp_file_dict) ods_kasp_fd = open(paths.OPENDNSSEC_KASP_FILE, 'w') ods_kasp_fd.seek(0) ods_kasp_fd.truncate(0) ods_kasp_fd.write(ods_kasp_txt) ods_kasp_fd.close() if not self.fstore.has_file(paths.SYSCONFIG_ODS): self.fstore.backup_file(paths.SYSCONFIG_ODS) installutils.set_directive(paths.SYSCONFIG_ODS, 'SOFTHSM2_CONF', paths.DNSSEC_SOFTHSM2_CONF, quotes=False, separator='=') def __setup_ownership_file_modes(self): assert self.ods_uid is not None assert self.ods_gid is not None # workarounds for packaging bugs in opendnssec-1.4.5-2.fc20.x86_64 # https://bugzilla.redhat.com/show_bug.cgi?id=1098188 for (root, dirs, files) in os.walk(paths.ETC_OPENDNSSEC_DIR): for directory in dirs: dir_path = os.path.join(root, directory) os.chmod(dir_path, 0o770) # chown to root:ods os.chown(dir_path, 0, self.ods_gid) for filename in files: file_path = os.path.join(root, filename) os.chmod(file_path, 0o660) # chown to root:ods os.chown(file_path, 0, self.ods_gid) for (root, dirs, files) in os.walk(paths.VAR_OPENDNSSEC_DIR): for directory in dirs: dir_path = os.path.join(root, directory) os.chmod(dir_path, 0o770) # chown to ods:ods os.chown(dir_path, self.ods_uid, self.ods_gid) for filename in files: file_path = os.path.join(root, filename) os.chmod(file_path, 0o660) # chown to ods:ods os.chown(file_path, self.ods_uid, self.ods_gid) def __generate_master_key(self): with open(paths.DNSSEC_SOFTHSM_PIN, "r") as f: pin = f.read() os.environ["SOFTHSM2_CONF"] = paths.DNSSEC_SOFTHSM2_CONF p11 = _ipap11helper.P11_Helper(softhsm_slot, pin, paths.LIBSOFTHSM2_SO) try: # generate master key root_logger.debug("Creating master key") p11helper.generate_master_key(p11) # change tokens mod/owner root_logger.debug("Changing ownership of token files") for (root, dirs, files) in os.walk(paths.DNSSEC_TOKENS_DIR): for directory in dirs: dir_path = os.path.join(root, directory) os.chmod(dir_path, 0o770 | stat.S_ISGID) os.chown(dir_path, self.ods_uid, self.named_gid) # chown to ods:named for filename in files: file_path = os.path.join(root, filename) os.chmod(file_path, 0o770 | stat.S_ISGID) os.chown(file_path, self.ods_uid, self.named_gid) # chown to ods:named finally: p11.finalize() def __setup_dnssec(self): # run once only if self.get_state("kasp_db_configured") and not self.kasp_db_file: root_logger.debug("Already configured, skipping step") return self.backup_state("kasp_db_configured", True) if not self.fstore.has_file(paths.OPENDNSSEC_KASP_DB): self.fstore.backup_file(paths.OPENDNSSEC_KASP_DB) if self.kasp_db_file: # copy user specified kasp.db to proper location and set proper # privileges shutil.copy(self.kasp_db_file, paths.OPENDNSSEC_KASP_DB) os.chown(paths.OPENDNSSEC_KASP_DB, self.ods_uid, self.ods_gid) os.chmod(paths.OPENDNSSEC_KASP_DB, 0o660) # regenerate zonelist.xml ods_enforcerd = services.knownservices.ods_enforcerd cmd = [paths.ODS_KSMUTIL, 'zonelist', 'export'] result = ipautil.run(cmd, runas=ods_enforcerd.get_user_name(), capture_output=True) with open(paths.OPENDNSSEC_ZONELIST_FILE, 'w') as zonelistf: zonelistf.write(result.output) os.chown(paths.OPENDNSSEC_ZONELIST_FILE, self.ods_uid, self.ods_gid) os.chmod(paths.OPENDNSSEC_ZONELIST_FILE, 0o660) else: # initialize new kasp.db command = [ paths.ODS_KSMUTIL, 'setup' ] ods_enforcerd = services.knownservices.ods_enforcerd ipautil.run(command, stdin="y", runas=ods_enforcerd.get_user_name()) def __setup_dnskeysyncd(self): # set up dnskeysyncd this is DNSSEC master installutils.set_directive(paths.SYSCONFIG_IPA_DNSKEYSYNCD, 'ISMASTER', '1', quotes=False, separator='=') def __start(self): self.restart() # needed to reload conf files def uninstall(self): if not self.is_configured(): return self.print_msg("Unconfiguring %s" % self.service_name) running = self.restore_state("running") enabled = self.restore_state("enabled") # stop DNSSEC services before backing up kasp.db try: self.stop() except Exception: pass ods_exporter = services.service('ipa-ods-exporter') try: ods_exporter.stop() except Exception: pass # remove directive from ipa-dnskeysyncd, this server is not DNSSEC # master anymore installutils.set_directive(paths.SYSCONFIG_IPA_DNSKEYSYNCD, 'ISMASTER', None, quotes=False, separator='=') restore_list = [paths.OPENDNSSEC_CONF_FILE, paths.OPENDNSSEC_KASP_FILE, paths.SYSCONFIG_ODS, paths.OPENDNSSEC_ZONELIST_FILE] if ipautil.file_exists(paths.OPENDNSSEC_KASP_DB): # force to export data ods_enforcerd = services.knownservices.ods_enforcerd cmd = [paths.IPA_ODS_EXPORTER, 'ipa-full-update'] try: self.print_msg("Exporting DNSSEC data before uninstallation") ipautil.run(cmd, runas=ods_enforcerd.get_user_name()) except CalledProcessError: root_logger.error("DNSSEC data export failed") try: shutil.copy(paths.OPENDNSSEC_KASP_DB, paths.IPA_KASP_DB_BACKUP) except IOError as e: root_logger.error( "Unable to backup OpenDNSSEC database %s, " "restore will be skipped: %s", paths.OPENDNSSEC_KASP_DB, e) else: root_logger.info("OpenDNSSEC database backed up in %s", paths.IPA_KASP_DB_BACKUP) # restore OpenDNSSEC's KASP DB only if backup succeeded # removing the file without backup could totally break DNSSEC restore_list.append(paths.OPENDNSSEC_KASP_DB) for f in restore_list: try: self.fstore.restore_file(f) except ValueError as error: root_logger.debug(error) self.restore_state("kasp_db_configured") # just eat state # disabled by default, by ldap_enable() if enabled: self.enable() if running: self.restart()