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-options-ext.conf.template \
bind.named.conf.template \
bind.openssl.cnf.template \
certmap.conf.template \
kdc.conf.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_GROUP = "named"
NAMED_DATA_DIR = "data/"
NAMED_OPTIONS_VAR = "OPTIONS"
NAMED_OPENSSL_ENGINE = None
NAMED_ZONE_COMMENT = ""
PKI_USER = 'pkiuser'
PKI_GROUP = 'pkiuser'

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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