From 5c907e34ae4496482151cd9c8271d9931ac4056d Mon Sep 17 00:00:00 2001 From: Stanislav Levin Date: Mon, 30 Sep 2019 16:47:08 +0300 Subject: [PATCH] named: Allow using of a custom OpenSSL engine for BIND For now Debian, Fedora, RHEL, etc. build BIND with 'native PKCS11' support. Till recently, that was the strict requirement of DNSSEC. The problem is that this restricts cross-platform features of FreeIPA. With the help of libp11, which provides `pkcs11` engine plugin for the OpenSSL library for accessing PKCS11 modules in a semi- transparent way, FreeIPA could utilize OpenSSL version of BIND. BIND in turn provides ability to specify the OpenSSL engine on the command line of `named` and all the BIND `dnssec-*` tools by using the `-E engine_name`. Fixes: https://pagure.io/freeipa/issue/8094 Signed-off-by: Stanislav Levin Reviewed-By: Alexander Bokovoy Reviewed-By: Christian Heimes --- install/share/Makefile.am | 1 + install/share/bind.openssl.cnf.template | 14 ++++ ipaplatform/base/constants.py | 2 + ipaplatform/base/paths.py | 1 + ipaplatform/redhat/services.py | 1 + ipaserver/dnssec/bindmgr.py | 6 ++ ipaserver/install/bindinstance.py | 56 ++++--------- ipaserver/install/dnskeysyncinstance.py | 105 ++++++++++++++++-------- ipaserver/install/ipa_backup.py | 1 + ipaserver/install/kra.py | 4 +- ipaserver/install/opendnssecinstance.py | 2 +- ipaserver/install/service.py | 1 + 12 files changed, 120 insertions(+), 74 deletions(-) create mode 100644 install/share/bind.openssl.cnf.template diff --git a/install/share/Makefile.am b/install/share/Makefile.am index ae09afdc4..b93cb0cec 100644 --- a/install/share/Makefile.am +++ b/install/share/Makefile.am @@ -48,6 +48,7 @@ dist_app_DATA = \ bind.ipa-ext.conf.template \ bind.ipa-options-ext.conf.template \ bind.named.conf.template \ + bind.openssl.cnf.template \ certmap.conf.template \ kdc.conf.template \ kdc_extensions.template \ diff --git a/install/share/bind.openssl.cnf.template b/install/share/bind.openssl.cnf.template new file mode 100644 index 000000000..b43b46fef --- /dev/null +++ b/install/share/bind.openssl.cnf.template @@ -0,0 +1,14 @@ +# OpenSSL configuration file +# File generated by IPA instalation +openssl_conf = openssl_init + +[openssl_init] +engines = engine_section + +[engine_section] +$OPENSSL_ENGINE = ${OPENSSL_ENGINE}_section + +[${OPENSSL_ENGINE}_section] +engine_id = $OPENSSL_ENGINE +MODULE_PATH = $SOFTHSM_MODULE +init=0 diff --git a/ipaplatform/base/constants.py b/ipaplatform/base/constants.py index eac60cac3..08b34708a 100644 --- a/ipaplatform/base/constants.py +++ b/ipaplatform/base/constants.py @@ -23,6 +23,8 @@ class BaseConstantsNamespace: NAMED_USER = "named" NAMED_GROUP = "named" NAMED_DATA_DIR = "data/" + NAMED_OPTIONS_VAR = "OPTIONS" + NAMED_OPENSSL_ENGINE = None NAMED_ZONE_COMMENT = "" PKI_USER = 'pkiuser' PKI_GROUP = 'pkiuser' diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py index e1c7199b1..5e1f41f38 100644 --- a/ipaplatform/base/paths.py +++ b/ipaplatform/base/paths.py @@ -68,6 +68,7 @@ class BasePathNamespace: IPA_DEFAULT_CONF = "/etc/ipa/default.conf" IPA_DNSKEYSYNCD_KEYTAB = "/etc/ipa/dnssec/ipa-dnskeysyncd.keytab" IPA_ODS_EXPORTER_KEYTAB = "/etc/ipa/dnssec/ipa-ods-exporter.keytab" + DNSSEC_OPENSSL_CONF = None DNSSEC_SOFTHSM2_CONF = "/etc/ipa/dnssec/softhsm2.conf" DNSSEC_SOFTHSM_PIN_SO = "/etc/ipa/dnssec/softhsm_pin_so" IPA_NSSDB_DIR = "/etc/ipa/nssdb" diff --git a/ipaplatform/redhat/services.py b/ipaplatform/redhat/services.py index 042431849..3cc8a71b8 100644 --- a/ipaplatform/redhat/services.py +++ b/ipaplatform/redhat/services.py @@ -68,6 +68,7 @@ redhat_system_units['ipa-dnskeysyncd'] = 'ipa-dnskeysyncd.service' redhat_system_units['named-regular'] = 'named.service' redhat_system_units['named-pkcs11'] = 'named-pkcs11.service' redhat_system_units['named'] = redhat_system_units['named-pkcs11'] +redhat_system_units['named-conflict'] = redhat_system_units['named-regular'] redhat_system_units['ods-enforcerd'] = 'ods-enforcerd.service' redhat_system_units['ods_enforcerd'] = redhat_system_units['ods-enforcerd'] redhat_system_units['ods-signerd'] = 'ods-signerd.service' diff --git a/ipaserver/dnssec/bindmgr.py b/ipaserver/dnssec/bindmgr.py index c2f9c5a04..4f7cad893 100644 --- a/ipaserver/dnssec/bindmgr.py +++ b/ipaserver/dnssec/bindmgr.py @@ -16,11 +16,14 @@ import stat import six import ipalib.constants + from ipapython.dn import DN from ipapython import ipautil +from ipaplatform.constants import constants as platformconstants from ipaplatform.paths import paths from ipaserver.dnssec.temp import TemporaryDirectory +from ipaserver.install import installutils logger = logging.getLogger(__name__) @@ -133,8 +136,11 @@ class BINDMgr: cmd.extend(['-f', 'KSK']) if attrs.get('idnsSecKeyRevoke', [b'FALSE'])[0].upper() == b'TRUE': cmd.extend(['-R', datetime.now().strftime(time_bindfmt)]) + if platformconstants.NAMED_OPENSSL_ENGINE is not None: + cmd.extend(['-E', platformconstants.NAMED_OPENSSL_ENGINE]) cmd.append(zone.to_text()) + installutils.check_entropy() # keys has to be readable by ODS & named result = ipautil.run(cmd, capture_output=True) basename = result.output.strip() diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py index 90cc9b38b..b27548144 100644 --- a/ipaserver/install/bindinstance.py +++ b/ipaserver/install/bindinstance.py @@ -657,7 +657,7 @@ class BindInstance(service.Service): self.no_dnssec_validation = False self.sub_dict = None self.reverse_zones = () - self.named_regular = services.service('named-regular', api) + self.named_conflict = services.service('named-conflict', api) suffix = ipautil.dn_attribute_property('_suffix') @@ -764,7 +764,7 @@ class BindInstance(service.Service): # named has to be started after softhsm initialization # self.step("restarting named", self.__start) - self.step("configuring named to start on boot", self.__enable) + self.step("configuring named to start on boot", self.switch_service) self.step("changing resolv.conf to point to ourselves", self.__setup_resolv_conf) self.start_creation() @@ -774,19 +774,16 @@ class BindInstance(service.Service): def __start(self): try: - if self.get_state("running") is None: - # first time store status - self.backup_state("running", self.is_running()) self.restart() except Exception as e: logger.error("Named service failed to start (%s)", e) print("named service failed to start") + def switch_service(self): + self.mask_conflict() + self.__enable() + def __enable(self): - if self.get_state("enabled") is None: - self.backup_state("enabled", self.is_running()) - self.backup_state("named-regular-enabled", - self.named_regular.is_running()) # We do not let the system start IPA components on its own, # Instead we reply on the IPA init script to start only enabled # components as found in our LDAP configuration tree @@ -797,20 +794,19 @@ class BindInstance(service.Service): # don't crash, just report error logger.error("DNS service already exists") - # disable named, we need to run named-pkcs11 only - if self.get_state("named-regular-running") is None: - # first time store status - self.backup_state("named-regular-running", - self.named_regular.is_running()) + def mask_conflict(self): + # disable named-conflict (either named or named-pkcs11) try: - self.named_regular.stop() + self.named_conflict.stop() except Exception as e: - logger.debug("Unable to stop named (%s)", e) + logger.debug("Unable to stop %s (%s)", + self.named_conflict.systemd_name, e) try: - self.named_regular.mask() + self.named_conflict.mask() except Exception as e: - logger.debug("Unable to mask named (%s)", e) + logger.debug("Unable to mask %s (%s)", + self.named_conflict.systemd_name, e) def _get_dnssec_validation(self): """get dnssec-validation value @@ -1307,11 +1303,6 @@ class BindInstance(service.Service): if self.is_configured(): self.print_msg("Unconfiguring %s" % self.service_name) - running = self.restore_state("running") - enabled = self.restore_state("enabled") - named_regular_running = self.restore_state("named-regular-running") - named_regular_enabled = self.restore_state("named-regular-enabled") - self.dns_backup.clear_records(self.api.Backend.ldap2.isconnected()) try: @@ -1326,23 +1317,10 @@ class BindInstance(service.Service): ipautil.rmtree(paths.BIND_LDAP_DNS_IPA_WORKDIR) - # disabled by default, by ldap_configure() - if enabled: - self.enable() - else: - self.disable() + self.disable() + self.stop() - if running: - self.restart() - else: - self.stop() - - self.named_regular.unmask() - if named_regular_enabled: - self.named_regular.enable() - - if named_regular_running: - self.named_regular.start() + self.named_conflict.unmask() ipautil.remove_file(paths.NAMED_CONF_BAK) ipautil.remove_file(paths.NAMED_CUSTOM_CONF) diff --git a/ipaserver/install/dnskeysyncinstance.py b/ipaserver/install/dnskeysyncinstance.py index 0cc5cd0c4..d3637cf79 100644 --- a/ipaserver/install/dnskeysyncinstance.py +++ b/ipaserver/install/dnskeysyncinstance.py @@ -164,6 +164,61 @@ class DNSKeySyncInstance(service.Service): self._ldap_mod("dnssec.ldif", {'SUFFIX': self.suffix, }) + def setup_named_openssl_conf(self): + if constants.NAMED_OPENSSL_ENGINE is not None: + logger.debug("Setup OpenSSL config for BIND") + # setup OpenSSL config for BIND, + # this one is needed because FreeIPA installation + # disables p11-kit-proxy PKCS11 module + conf_file_dict = { + 'OPENSSL_ENGINE': constants.NAMED_OPENSSL_ENGINE, + 'SOFTHSM_MODULE': paths.LIBSOFTHSM2_SO, + } + named_openssl_txt = ipautil.template_file( + os.path.join(paths.USR_SHARE_IPA_DIR, + "bind.openssl.cnf.template"), + conf_file_dict) + with open(paths.DNSSEC_OPENSSL_CONF, 'w') as f: + os.fchmod(f.fileno(), 0o640) + os.fchown(f.fileno(), 0, self.named_gid) + f.write(named_openssl_txt) + + def setup_named_sysconfig(self): + logger.debug("Setup BIND sysconfig") + sysconfig = paths.SYSCONFIG_NAMED + self.fstore.backup_file(sysconfig) + + directivesetter.set_directive( + sysconfig, + 'SOFTHSM2_CONF', paths.DNSSEC_SOFTHSM2_CONF, + quotes=False, separator='=') + + if constants.NAMED_OPENSSL_ENGINE is not None: + directivesetter.set_directive( + sysconfig, + 'OPENSSL_CONF', paths.DNSSEC_OPENSSL_CONF, + quotes=False, separator='=') + + engine_txt = "-E {}".format(constants.NAMED_OPENSSL_ENGINE) + directivesetter.set_directive( + sysconfig, + constants.NAMED_OPTIONS_VAR, engine_txt, + quotes=True, separator='=') + + def setup_ipa_dnskeysyncd_sysconfig(self): + logger.debug("Setup ipa-dnskeysyncd sysconfig") + sysconfig = paths.SYSCONFIG_IPA_DNSKEYSYNCD + directivesetter.set_directive( + sysconfig, + 'SOFTHSM2_CONF', paths.DNSSEC_SOFTHSM2_CONF, + quotes=False, separator='=') + + if constants.NAMED_OPENSSL_ENGINE is not None: + directivesetter.set_directive( + sysconfig, + 'OPENSSL_CONF', paths.DNSSEC_OPENSSL_CONF, + quotes=False, separator='=') + def __setup_softhsm(self): assert self.ods_uid is not None assert self.named_gid is not None @@ -186,23 +241,15 @@ class DNSKeySyncInstance(service.Service): 'tokens_dir': paths.DNSSEC_TOKENS_DIR } logger.debug("Creating new softhsm config file") - named_fd = open(paths.DNSSEC_SOFTHSM2_CONF, 'w') - named_fd.seek(0) - named_fd.truncate(0) - named_fd.write(softhsm_conf_txt) - named_fd.close() - os.chmod(paths.DNSSEC_SOFTHSM2_CONF, 0o644) + with open(paths.DNSSEC_SOFTHSM2_CONF, 'w') as f: + os.fchmod(f.fileno(), 0o644) + f.write(softhsm_conf_txt) - # setting up named to use softhsm2 - if not self.fstore.has_file(paths.SYSCONFIG_NAMED): - self.fstore.backup_file(paths.SYSCONFIG_NAMED) - - # setting up named and ipa-dnskeysyncd to use our softhsm2 config - for sysconfig in [paths.SYSCONFIG_NAMED, - paths.SYSCONFIG_IPA_DNSKEYSYNCD]: - directivesetter.set_directive(sysconfig, 'SOFTHSM2_CONF', - paths.DNSSEC_SOFTHSM2_CONF, - quotes=False, separator='=') + # setting up named and ipa-dnskeysyncd to use our softhsm2 and + # openssl configs + self.setup_named_openssl_conf() + self.setup_named_sysconfig() + self.setup_ipa_dnskeysyncd_sysconfig() if (token_dir_exists and os.path.exists(paths.DNSSEC_SOFTHSM_PIN) and os.path.exists(paths.DNSSEC_SOFTHSM_PIN_SO)): @@ -231,23 +278,17 @@ class DNSKeySyncInstance(service.Service): entropy_bits=0, special=None, min_len=pin_length) logger.debug("Saving user PIN to %s", paths.DNSSEC_SOFTHSM_PIN) - named_fd = open(paths.DNSSEC_SOFTHSM_PIN, 'w') - named_fd.seek(0) - named_fd.truncate(0) - named_fd.write(pin) - named_fd.close() - os.chmod(paths.DNSSEC_SOFTHSM_PIN, 0o770) - # chown to ods:named - os.chown(paths.DNSSEC_SOFTHSM_PIN, self.ods_uid, self.named_gid) + with open(paths.DNSSEC_SOFTHSM_PIN, 'w') as f: + # chown to ods:named + os.fchown(f.fileno(), self.ods_uid, self.named_gid) + os.fchmod(f.fileno(), 0o660) + f.write(pin) logger.debug("Saving SO PIN to %s", paths.DNSSEC_SOFTHSM_PIN_SO) - named_fd = open(paths.DNSSEC_SOFTHSM_PIN_SO, 'w') - named_fd.seek(0) - named_fd.truncate(0) - named_fd.write(pin_so) - named_fd.close() - # owner must be root - os.chmod(paths.DNSSEC_SOFTHSM_PIN_SO, 0o400) + with open(paths.DNSSEC_SOFTHSM_PIN_SO, 'w') as f: + # owner must be root + os.fchmod(f.fileno(), 0o400) + f.write(pin_so) # initialize SoftHSM @@ -377,7 +418,7 @@ class DNSKeySyncInstance(service.Service): os.chown(dir_path, self.ods_uid, self.named_gid) for filename in files: file_path = os.path.join(root, filename) - os.chmod(file_path, 0o770 | stat.S_ISGID) + os.chmod(file_path, 0o660 | stat.S_ISGID) # chown to ods:named os.chown(file_path, self.ods_uid, self.named_gid) diff --git a/ipaserver/install/ipa_backup.py b/ipaserver/install/ipa_backup.py index d4b7b4377..64806db4c 100644 --- a/ipaserver/install/ipa_backup.py +++ b/ipaserver/install/ipa_backup.py @@ -185,6 +185,7 @@ class Backup(admintool.AdminTool): paths.OPENDNSSEC_KASP_FILE, paths.OPENDNSSEC_ZONELIST_FILE, paths.OPENDNSSEC_KASP_DB, + paths.DNSSEC_OPENSSL_CONF, paths.DNSSEC_SOFTHSM2_CONF, paths.DNSSEC_SOFTHSM_PIN_SO, paths.IPA_ODS_EXPORTER_KEYTAB, diff --git a/ipaserver/install/kra.py b/ipaserver/install/kra.py index 746c534dc..c7a097b58 100644 --- a/ipaserver/install/kra.py +++ b/ipaserver/install/kra.py @@ -106,9 +106,9 @@ def install(api, replica_config, options, custodia): # Restart apache for new proxy config file services.knownservices.httpd.restart(capture_output=True) - # Restarted named-pkcs11 to restore bind-dyndb-ldap operation, see + # Restarted named to restore bind-dyndb-ldap operation, see # https://pagure.io/freeipa/issue/5813 - named = services.knownservices.named # alias for named-pkcs11 + named = services.knownservices.named # alias for current named if named.is_running(): named.restart(capture_output=True) diff --git a/ipaserver/install/opendnssecinstance.py b/ipaserver/install/opendnssecinstance.py index 95029fd5e..044db794b 100644 --- a/ipaserver/install/opendnssecinstance.py +++ b/ipaserver/install/opendnssecinstance.py @@ -269,7 +269,7 @@ class OpenDNSSECInstance(service.Service): 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.chmod(file_path, 0o660 | stat.S_ISGID) os.chown(file_path, self.ods_uid, self.named_gid) # chown to ods:named finally: diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py index 3706bdd86..73373a15b 100644 --- a/ipaserver/install/service.py +++ b/ipaserver/install/service.py @@ -660,6 +660,7 @@ class Service: ] extra_config_opts.extend(config) + self.unmask() self.disable() set_service_entry_config(