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 <slev@altlinux.org>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Christian Heimes <cheimes@redhat.com>
This commit is contained in:
Stanislav Levin
2019-09-30 16:47:08 +03:00
committed by Alexander Bokovoy
parent a9334ce5e5
commit 5c907e34ae
12 changed files with 120 additions and 74 deletions

View File

@@ -48,6 +48,7 @@ dist_app_DATA = \
bind.ipa-ext.conf.template \ bind.ipa-ext.conf.template \
bind.ipa-options-ext.conf.template \ bind.ipa-options-ext.conf.template \
bind.named.conf.template \ bind.named.conf.template \
bind.openssl.cnf.template \
certmap.conf.template \ certmap.conf.template \
kdc.conf.template \ kdc.conf.template \
kdc_extensions.template \ kdc_extensions.template \

View File

@@ -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

View File

@@ -23,6 +23,8 @@ class BaseConstantsNamespace:
NAMED_USER = "named" NAMED_USER = "named"
NAMED_GROUP = "named" NAMED_GROUP = "named"
NAMED_DATA_DIR = "data/" NAMED_DATA_DIR = "data/"
NAMED_OPTIONS_VAR = "OPTIONS"
NAMED_OPENSSL_ENGINE = None
NAMED_ZONE_COMMENT = "" NAMED_ZONE_COMMENT = ""
PKI_USER = 'pkiuser' PKI_USER = 'pkiuser'
PKI_GROUP = 'pkiuser' PKI_GROUP = 'pkiuser'

View File

@@ -68,6 +68,7 @@ class BasePathNamespace:
IPA_DEFAULT_CONF = "/etc/ipa/default.conf" IPA_DEFAULT_CONF = "/etc/ipa/default.conf"
IPA_DNSKEYSYNCD_KEYTAB = "/etc/ipa/dnssec/ipa-dnskeysyncd.keytab" IPA_DNSKEYSYNCD_KEYTAB = "/etc/ipa/dnssec/ipa-dnskeysyncd.keytab"
IPA_ODS_EXPORTER_KEYTAB = "/etc/ipa/dnssec/ipa-ods-exporter.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_SOFTHSM2_CONF = "/etc/ipa/dnssec/softhsm2.conf"
DNSSEC_SOFTHSM_PIN_SO = "/etc/ipa/dnssec/softhsm_pin_so" DNSSEC_SOFTHSM_PIN_SO = "/etc/ipa/dnssec/softhsm_pin_so"
IPA_NSSDB_DIR = "/etc/ipa/nssdb" IPA_NSSDB_DIR = "/etc/ipa/nssdb"

View File

@@ -68,6 +68,7 @@ redhat_system_units['ipa-dnskeysyncd'] = 'ipa-dnskeysyncd.service'
redhat_system_units['named-regular'] = 'named.service' redhat_system_units['named-regular'] = 'named.service'
redhat_system_units['named-pkcs11'] = 'named-pkcs11.service' redhat_system_units['named-pkcs11'] = 'named-pkcs11.service'
redhat_system_units['named'] = redhat_system_units['named-pkcs11'] 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'] = 'ods-enforcerd.service'
redhat_system_units['ods_enforcerd'] = redhat_system_units['ods-enforcerd'] redhat_system_units['ods_enforcerd'] = redhat_system_units['ods-enforcerd']
redhat_system_units['ods-signerd'] = 'ods-signerd.service' redhat_system_units['ods-signerd'] = 'ods-signerd.service'

View File

@@ -16,11 +16,14 @@ import stat
import six import six
import ipalib.constants import ipalib.constants
from ipapython.dn import DN from ipapython.dn import DN
from ipapython import ipautil from ipapython import ipautil
from ipaplatform.constants import constants as platformconstants
from ipaplatform.paths import paths from ipaplatform.paths import paths
from ipaserver.dnssec.temp import TemporaryDirectory from ipaserver.dnssec.temp import TemporaryDirectory
from ipaserver.install import installutils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -133,8 +136,11 @@ class BINDMgr:
cmd.extend(['-f', 'KSK']) cmd.extend(['-f', 'KSK'])
if attrs.get('idnsSecKeyRevoke', [b'FALSE'])[0].upper() == b'TRUE': if attrs.get('idnsSecKeyRevoke', [b'FALSE'])[0].upper() == b'TRUE':
cmd.extend(['-R', datetime.now().strftime(time_bindfmt)]) 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()) cmd.append(zone.to_text())
installutils.check_entropy()
# keys has to be readable by ODS & named # keys has to be readable by ODS & named
result = ipautil.run(cmd, capture_output=True) result = ipautil.run(cmd, capture_output=True)
basename = result.output.strip() basename = result.output.strip()

View File

@@ -657,7 +657,7 @@ class BindInstance(service.Service):
self.no_dnssec_validation = False self.no_dnssec_validation = False
self.sub_dict = None self.sub_dict = None
self.reverse_zones = () 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') suffix = ipautil.dn_attribute_property('_suffix')
@@ -764,7 +764,7 @@ class BindInstance(service.Service):
# named has to be started after softhsm initialization # named has to be started after softhsm initialization
# self.step("restarting named", self.__start) # 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.step("changing resolv.conf to point to ourselves", self.__setup_resolv_conf)
self.start_creation() self.start_creation()
@@ -774,19 +774,16 @@ class BindInstance(service.Service):
def __start(self): def __start(self):
try: try:
if self.get_state("running") is None:
# first time store status
self.backup_state("running", self.is_running())
self.restart() self.restart()
except Exception as e: except Exception as e:
logger.error("Named service failed to start (%s)", e) logger.error("Named service failed to start (%s)", e)
print("named service failed to start") print("named service failed to start")
def switch_service(self):
self.mask_conflict()
self.__enable()
def __enable(self): 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, # We do not let the system start IPA components on its own,
# Instead we reply on the IPA init script to start only enabled # Instead we reply on the IPA init script to start only enabled
# components as found in our LDAP configuration tree # components as found in our LDAP configuration tree
@@ -797,20 +794,19 @@ class BindInstance(service.Service):
# don't crash, just report error # don't crash, just report error
logger.error("DNS service already exists") logger.error("DNS service already exists")
# disable named, we need to run named-pkcs11 only def mask_conflict(self):
if self.get_state("named-regular-running") is None: # disable named-conflict (either named or named-pkcs11)
# first time store status
self.backup_state("named-regular-running",
self.named_regular.is_running())
try: try:
self.named_regular.stop() self.named_conflict.stop()
except Exception as e: 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: try:
self.named_regular.mask() self.named_conflict.mask()
except Exception as e: 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): def _get_dnssec_validation(self):
"""get dnssec-validation value """get dnssec-validation value
@@ -1307,11 +1303,6 @@ class BindInstance(service.Service):
if self.is_configured(): if self.is_configured():
self.print_msg("Unconfiguring %s" % self.service_name) 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()) self.dns_backup.clear_records(self.api.Backend.ldap2.isconnected())
try: try:
@@ -1326,23 +1317,10 @@ class BindInstance(service.Service):
ipautil.rmtree(paths.BIND_LDAP_DNS_IPA_WORKDIR) ipautil.rmtree(paths.BIND_LDAP_DNS_IPA_WORKDIR)
# disabled by default, by ldap_configure() self.disable()
if enabled: self.stop()
self.enable()
else:
self.disable()
if running: self.named_conflict.unmask()
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()
ipautil.remove_file(paths.NAMED_CONF_BAK) ipautil.remove_file(paths.NAMED_CONF_BAK)
ipautil.remove_file(paths.NAMED_CUSTOM_CONF) ipautil.remove_file(paths.NAMED_CUSTOM_CONF)

View File

@@ -164,6 +164,61 @@ class DNSKeySyncInstance(service.Service):
self._ldap_mod("dnssec.ldif", {'SUFFIX': self.suffix, }) 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): def __setup_softhsm(self):
assert self.ods_uid is not None assert self.ods_uid is not None
assert self.named_gid is not None assert self.named_gid is not None
@@ -186,23 +241,15 @@ class DNSKeySyncInstance(service.Service):
'tokens_dir': paths.DNSSEC_TOKENS_DIR 'tokens_dir': paths.DNSSEC_TOKENS_DIR
} }
logger.debug("Creating new softhsm config file") logger.debug("Creating new softhsm config file")
named_fd = open(paths.DNSSEC_SOFTHSM2_CONF, 'w') with open(paths.DNSSEC_SOFTHSM2_CONF, 'w') as f:
named_fd.seek(0) os.fchmod(f.fileno(), 0o644)
named_fd.truncate(0) f.write(softhsm_conf_txt)
named_fd.write(softhsm_conf_txt)
named_fd.close()
os.chmod(paths.DNSSEC_SOFTHSM2_CONF, 0o644)
# setting up named to use softhsm2 # setting up named and ipa-dnskeysyncd to use our softhsm2 and
if not self.fstore.has_file(paths.SYSCONFIG_NAMED): # openssl configs
self.fstore.backup_file(paths.SYSCONFIG_NAMED) self.setup_named_openssl_conf()
self.setup_named_sysconfig()
# setting up named and ipa-dnskeysyncd to use our softhsm2 config self.setup_ipa_dnskeysyncd_sysconfig()
for sysconfig in [paths.SYSCONFIG_NAMED,
paths.SYSCONFIG_IPA_DNSKEYSYNCD]:
directivesetter.set_directive(sysconfig, 'SOFTHSM2_CONF',
paths.DNSSEC_SOFTHSM2_CONF,
quotes=False, separator='=')
if (token_dir_exists and os.path.exists(paths.DNSSEC_SOFTHSM_PIN) and if (token_dir_exists and os.path.exists(paths.DNSSEC_SOFTHSM_PIN) and
os.path.exists(paths.DNSSEC_SOFTHSM_PIN_SO)): 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) entropy_bits=0, special=None, min_len=pin_length)
logger.debug("Saving user PIN to %s", paths.DNSSEC_SOFTHSM_PIN) logger.debug("Saving user PIN to %s", paths.DNSSEC_SOFTHSM_PIN)
named_fd = open(paths.DNSSEC_SOFTHSM_PIN, 'w') with open(paths.DNSSEC_SOFTHSM_PIN, 'w') as f:
named_fd.seek(0) # chown to ods:named
named_fd.truncate(0) os.fchown(f.fileno(), self.ods_uid, self.named_gid)
named_fd.write(pin) os.fchmod(f.fileno(), 0o660)
named_fd.close() f.write(pin)
os.chmod(paths.DNSSEC_SOFTHSM_PIN, 0o770)
# chown to ods:named
os.chown(paths.DNSSEC_SOFTHSM_PIN, self.ods_uid, self.named_gid)
logger.debug("Saving SO PIN to %s", paths.DNSSEC_SOFTHSM_PIN_SO) logger.debug("Saving SO PIN to %s", paths.DNSSEC_SOFTHSM_PIN_SO)
named_fd = open(paths.DNSSEC_SOFTHSM_PIN_SO, 'w') with open(paths.DNSSEC_SOFTHSM_PIN_SO, 'w') as f:
named_fd.seek(0) # owner must be root
named_fd.truncate(0) os.fchmod(f.fileno(), 0o400)
named_fd.write(pin_so) f.write(pin_so)
named_fd.close()
# owner must be root
os.chmod(paths.DNSSEC_SOFTHSM_PIN_SO, 0o400)
# initialize SoftHSM # initialize SoftHSM
@@ -377,7 +418,7 @@ class DNSKeySyncInstance(service.Service):
os.chown(dir_path, self.ods_uid, self.named_gid) os.chown(dir_path, self.ods_uid, self.named_gid)
for filename in files: for filename in files:
file_path = os.path.join(root, filename) 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 # chown to ods:named
os.chown(file_path, self.ods_uid, self.named_gid) os.chown(file_path, self.ods_uid, self.named_gid)

View File

@@ -185,6 +185,7 @@ class Backup(admintool.AdminTool):
paths.OPENDNSSEC_KASP_FILE, paths.OPENDNSSEC_KASP_FILE,
paths.OPENDNSSEC_ZONELIST_FILE, paths.OPENDNSSEC_ZONELIST_FILE,
paths.OPENDNSSEC_KASP_DB, paths.OPENDNSSEC_KASP_DB,
paths.DNSSEC_OPENSSL_CONF,
paths.DNSSEC_SOFTHSM2_CONF, paths.DNSSEC_SOFTHSM2_CONF,
paths.DNSSEC_SOFTHSM_PIN_SO, paths.DNSSEC_SOFTHSM_PIN_SO,
paths.IPA_ODS_EXPORTER_KEYTAB, paths.IPA_ODS_EXPORTER_KEYTAB,

View File

@@ -106,9 +106,9 @@ def install(api, replica_config, options, custodia):
# Restart apache for new proxy config file # Restart apache for new proxy config file
services.knownservices.httpd.restart(capture_output=True) 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 # 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(): if named.is_running():
named.restart(capture_output=True) named.restart(capture_output=True)

View File

@@ -269,7 +269,7 @@ class OpenDNSSECInstance(service.Service):
os.chown(dir_path, self.ods_uid, self.named_gid) # chown to ods:named os.chown(dir_path, self.ods_uid, self.named_gid) # chown to ods:named
for filename in files: for filename in files:
file_path = os.path.join(root, filename) 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 os.chown(file_path, self.ods_uid, self.named_gid) # chown to ods:named
finally: finally:

View File

@@ -660,6 +660,7 @@ class Service:
] ]
extra_config_opts.extend(config) extra_config_opts.extend(config)
self.unmask()
self.disable() self.disable()
set_service_entry_config( set_service_entry_config(