Files
freeipa/ipaserver/install/opendnssecinstance.py
T

376 lines
14 KiB
Python
Raw Normal View History

2014-10-16 16:03:46 +02:00
#
# Copyright (C) 2014 FreeIPA Contributors see COPYING for license
#
import os
import pwd
import grp
import stat
import shutil
from subprocess import CalledProcessError
2014-10-16 16:03:46 +02:00
from ipalib.install import sysrestore
2015-07-31 10:15:01 +02:00
from ipaserver.install import service
from ipaserver.install import installutils
2015-12-16 19:04:20 +01:00
from ipapython.ipa_log_manager import root_logger
2014-10-16 16:03:46 +02:00
from ipapython.dn import DN
from ipapython import ipautil
2014-10-16 16:03:46 +02:00
from ipaplatform import services
from ipaplatform.constants import constants
2014-10-16 16:03:46 +02:00
from ipaplatform.paths import paths
from ipalib import errors, api
from ipaserver import p11helper
2014-10-16 16:03:46 +02:00
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.
2014-10-16 16:03:46 +02:00
: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)
2014-10-16 16:03:46 +02:00
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):
2016-10-06 17:35:04 +02:00
def __init__(self, fstore=None):
2014-10-16 16:03:46 +02:00
service.Service.__init__(
self, "ods-enforcerd",
service_desc="OpenDNSSEC enforcer daemon",
)
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,
'ODS_USER': constants.ODS_USER,
'ODS_GROUP': constants.ODS_GROUP,
2014-10-16 16:03:46 +02:00
}
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):
return get_dnssec_key_masters(api.Backend.ldap2)
2014-10-16 16:03:46 +02:00
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())
2014-10-16 16:03:46 +02:00
self.fqdn = fqdn
self.realm = realm_name
self.suffix = ipautil.realm_to_suffix(self.realm)
self.kasp_db_file = kasp_db_file
2014-10-16 16:03:46 +02:00
try:
self.stop()
except Exception:
pass
# 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):
try:
self.named_uid = pwd.getpwnam(constants.NAMED_USER).pw_uid
2014-10-16 16:03:46 +02:00
except KeyError:
raise RuntimeError("Named UID not found")
try:
self.named_gid = grp.getgrnam(constants.NAMED_GROUP).gr_gid
2014-10-16 16:03:46 +02:00
except KeyError:
raise RuntimeError("Named GID not found")
try:
self.ods_uid = pwd.getpwnam(constants.ODS_USER).pw_uid
2014-10-16 16:03:46 +02:00
except KeyError:
raise RuntimeError("OpenDNSSEC UID not found")
try:
self.ods_gid = grp.getgrnam(constants.ODS_GROUP).gr_gid
2014-10-16 16:03:46 +02:00
except KeyError:
raise RuntimeError("OpenDNSSEC GID not found")
def __enable(self):
try:
2016-10-06 17:35:04 +02:00
self.ldap_enable('DNSSEC', self.fqdn, None,
2014-10-16 16:03:46 +02:00
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 = api.Backend.ldap2.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)
api.Backend.ldap2.update_entry(entry)
2014-10-16 16:03:46 +02:00
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)
2014-10-16 16:03:46 +02:00
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(
os.path.join(paths.USR_SHARE_IPA_DIR, "opendnssec_conf.template"),
sub_conf_dict)
2014-10-16 16:03:46 +02:00
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(
os.path.join(paths.USR_SHARE_IPA_DIR, "opendnssec_kasp.template"),
self.kasp_file_dict)
2014-10-16 16:03:46 +02:00
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)
2015-07-15 16:38:06 +02:00
os.chmod(dir_path, 0o770)
2014-10-16 16:03:46 +02:00
# chown to root:ods
os.chown(dir_path, 0, self.ods_gid)
for filename in files:
file_path = os.path.join(root, filename)
2015-07-15 16:38:06 +02:00
os.chmod(file_path, 0o660)
2014-10-16 16:03:46 +02:00
# 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)
2015-07-15 16:38:06 +02:00
os.chmod(dir_path, 0o770)
2014-10-16 16:03:46 +02:00
# 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)
2015-07-15 16:38:06 +02:00
os.chmod(file_path, 0o660)
2014-10-16 16:03:46 +02:00
# 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
2016-03-17 19:38:53 +01:00
p11 = p11helper.P11_Helper(softhsm_slot, pin, paths.LIBSOFTHSM2_SO)
2014-10-16 16:03:46 +02:00
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)
2015-07-15 16:38:06 +02:00
os.chmod(dir_path, 0o770 | stat.S_ISGID)
2014-10-16 16:03:46 +02:00
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)
2015-07-15 16:38:06 +02:00
os.chmod(file_path, 0o770 | stat.S_ISGID)
2014-10-16 16:03:46 +02:00
os.chown(file_path, self.ods_uid, self.named_gid) # chown to ods:named
finally:
p11.finalize()
def __setup_dnssec(self):
# run once only
2016-01-05 17:58:49 +01:00
if self.get_state("kasp_db_configured") and not self.kasp_db_file:
2014-10-16 16:03:46 +02:00
root_logger.debug("Already configured, skipping step")
return
2014-10-16 16:03:46 +02:00
2016-01-05 17:58:49 +01:00
self.backup_state("kasp_db_configured", True)
2014-10-16 16:03:46 +02:00
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)
2015-07-15 16:38:06 +02:00
os.chmod(paths.OPENDNSSEC_KASP_DB, 0o660)
# regenerate zonelist.xml
cmd = [paths.ODS_KSMUTIL, 'zonelist', 'export']
2015-11-25 17:17:18 +01:00
result = ipautil.run(cmd,
runas=constants.ODS_USER,
2015-11-25 17:17:18 +01:00
capture_output=True)
with open(paths.OPENDNSSEC_ZONELIST_FILE, 'w') as zonelistf:
2015-11-25 17:17:18 +01:00
zonelistf.write(result.output)
os.chown(paths.OPENDNSSEC_ZONELIST_FILE,
self.ods_uid, self.ods_gid)
2015-07-15 16:38:06 +02:00
os.chmod(paths.OPENDNSSEC_ZONELIST_FILE, 0o660)
2014-10-16 16:03:46 +02:00
else:
# initialize new kasp.db
command = [
paths.ODS_KSMUTIL,
'setup'
]
ipautil.run(command, stdin="y", runas=constants.ODS_USER)
2014-10-16 16:03:46 +02:00
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', api)
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
cmd = [paths.IPA_ODS_EXPORTER, 'ipa-full-update']
try:
2015-10-30 10:32:43 +01:00
self.print_msg("Exporting DNSSEC data before uninstallation")
ipautil.run(cmd, runas=constants.ODS_USER)
except CalledProcessError:
2015-10-30 10:32:43 +01:00
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:
2014-10-16 16:03:46 +02:00
try:
self.fstore.restore_file(f)
2015-07-30 16:49:29 +02:00
except ValueError as error:
2014-10-16 16:03:46 +02:00
root_logger.debug(error)
2016-01-05 17:58:49 +01:00
self.restore_state("kasp_db_configured") # just eat state
# disabled by default, by ldap_enable()
if enabled:
self.enable()
2014-10-16 16:03:46 +02:00
if running:
self.restart()