diff options
Diffstat (limited to 'ipaplatform')
-rw-r--r-- | ipaplatform/fedora/paths.py | 5 | ||||
-rw-r--r-- | ipaplatform/fedora/services.py | 197 | ||||
-rw-r--r-- | ipaplatform/fedora/tasks.py | 366 | ||||
-rw-r--r-- | ipaplatform/redhat/__init__.py | 22 | ||||
-rw-r--r-- | ipaplatform/redhat/authconfig.py (renamed from ipaplatform/fedora/authconfig.py) | 2 | ||||
-rw-r--r-- | ipaplatform/redhat/paths.py | 33 | ||||
-rw-r--r-- | ipaplatform/redhat/services.py | 237 | ||||
-rw-r--r-- | ipaplatform/redhat/tasks.py | 394 | ||||
-rw-r--r-- | ipaplatform/setup.py.in | 3 |
9 files changed, 707 insertions, 552 deletions
diff --git a/ipaplatform/fedora/paths.py b/ipaplatform/fedora/paths.py index af3e47d82..49a904f2f 100644 --- a/ipaplatform/fedora/paths.py +++ b/ipaplatform/fedora/paths.py @@ -23,10 +23,11 @@ in Fedora-based systems. ''' # Fallback to default path definitions -from ipaplatform.base.paths import BasePathNamespace +from ipaplatform.redhat.paths import RedHatPathNamespace -class FedoraPathNamespace(BasePathNamespace): +class FedoraPathNamespace(RedHatPathNamespace): pass + paths = FedoraPathNamespace() diff --git a/ipaplatform/fedora/services.py b/ipaplatform/fedora/services.py index d98c2d6d7..33becc219 100644 --- a/ipaplatform/fedora/services.py +++ b/ipaplatform/fedora/services.py @@ -22,213 +22,40 @@ Contains Fedora-specific service class implementations. """ -import os -import time - -from ipaplatform.tasks import tasks -from ipaplatform.base import services as base_services - -from ipapython import ipautil, dogtag -from ipapython.ipa_log_manager import root_logger -from ipalib import api -from ipaplatform.paths import paths +from ipaplatform.redhat import services as redhat_services # Mappings from service names as FreeIPA code references to these services # to their actual systemd service names +fedora_system_units = redhat_services.redhat_system_units -# For beginning just remap names to add .service -# As more services will migrate to systemd, unit names will deviate and -# mapping will be kept in this dictionary -system_units = dict((x, "%s.service" % x) - for x in base_services.wellknownservices) - -system_units['rpcgssd'] = 'nfs-secure.service' -system_units['rpcidmapd'] = 'nfs-idmap.service' - -# Rewrite dirsrv and pki-tomcatd services as they support instances via separate -# service generator. To make this working, one needs to have both foo@.servic -# and foo.target -- the latter is used when request should be coming for -# all instances (like stop). systemd, unfortunately, does not allow one -# to request action for all service instances at once if only foo@.service -# unit is available. To add more, if any of those services need to be -# started/stopped automagically, one needs to manually create symlinks in -# /etc/systemd/system/foo.target.wants/ (look into systemd.py's enable() -# code). - -system_units['dirsrv'] = 'dirsrv@.service' -# Our directory server instance for PKI is dirsrv@PKI-IPA.service -system_units['pkids'] = 'dirsrv@PKI-IPA.service' -# Old style PKI instance -system_units['pki-cad'] = 'pki-cad@pki-ca.service' -system_units['pki_cad'] = system_units['pki-cad'] -# Our PKI instance is pki-tomcatd@pki-tomcat.service -system_units['pki-tomcatd'] = 'pki-tomcatd@pki-tomcat.service' -system_units['pki_tomcatd'] = system_units['pki-tomcatd'] -system_units['ipa-otpd'] = 'ipa-otpd.socket' # Service that sets domainname on Fedora is called fedora-domainname.service -system_units['domainname'] = 'fedora-domainname.service' +fedora_system_units['domainname'] = 'fedora-domainname.service' # Service classes that implement Fedora-specific behaviour -class FedoraService(base_services.SystemdService): - def __init__(self, service_name): - systemd_name = service_name - if service_name in system_units: - systemd_name = system_units[service_name] - else: - if '.' not in service_name: - # if service_name does not have a dot, it is not foo.service - # and not a foo.target. Thus, not correct service name for - # systemd, default to foo.service style then - systemd_name = "%s.service" % (service_name) - super(FedoraService, self).__init__(service_name, systemd_name) - - -class FedoraDirectoryService(FedoraService): - - def tune_nofile_platform(self, num=8192, fstore=None): - """ - Increase the number of files descriptors available to directory server - from the default 1024 to 8192. This will allow to support a greater - number of clients out of the box. - - This is a part of the implementation that is systemd-specific. - - Returns False if the setting of the nofile limit needs to be skipped. - """ - - if os.path.exists(paths.SYSCONFIG_DIRSRV_SYSTEMD): - # We need to enable LimitNOFILE=8192 in the dirsrv@.service - # Since 389-ds-base-1.2.10-0.8.a7 the configuration of the - # service parameters is performed via - # /etc/sysconfig/dirsrv.systemd file which is imported by systemd - # into dirsrv@.service unit - - replacevars = {'LimitNOFILE': str(num)} - ipautil.inifile_replace_variables(paths.SYSCONFIG_DIRSRV_SYSTEMD, - 'service', - replacevars=replacevars) - tasks.restore_context(paths.SYSCONFIG_DIRSRV_SYSTEMD) - ipautil.run(["/bin/systemctl", "--system", "daemon-reload"], - raiseonerr=False) - - return True - - def restart(self, instance_name="", capture_output=True, wait=True): - # We need to explicitly enable instances to install proper symlinks as - # dirsrv.target.wants/ dependencies. Standard systemd service class does it - # on enable() method call. Unfortunately, ipa-server-install does not do - # explicit dirsrv.enable() because the service startup is handled by ipactl. - # - # If we wouldn't do this, our instances will not be started as systemd would - # not have any clue about instances (PKI-IPA and the domain we serve) - # at all. Thus, hook into dirsrv.restart(). - - if instance_name: - elements = self.systemd_name.split("@") - - srv_etc = os.path.join(paths.ETC_SYSTEMD_SYSTEM_DIR, - self.systemd_name) - srv_tgt = os.path.join(paths.ETC_SYSTEMD_SYSTEM_DIR, - self.SYSTEMD_SRV_TARGET % (elements[0])) - srv_lnk = os.path.join(srv_tgt, - self.service_instance(instance_name)) - - if not os.path.exists(srv_etc): - self.enable(instance_name) - elif not os.path.samefile(srv_etc, srv_lnk): - os.unlink(srv_lnk) - os.symlink(srv_etc, srv_lnk) - - super(FedoraDirectoryService, self).restart(instance_name, - capture_output=capture_output, wait=wait) - - -class FedoraIPAService(FedoraService): - # Enforce restart of IPA services when we do enable it - # This gets around the fact that after ipa-server-install systemd thinks - # ipa.service is not yet started but all services were actually started - # already. - def enable(self, instance_name=""): - super(FedoraIPAService, self).enable(instance_name) - self.restart(instance_name) - - -class FedoraSSHService(FedoraService): - def get_config_dir(self, instance_name=""): - return '/etc/ssh' - - -class FedoraCAService(FedoraService): - def wait_until_running(self): - # We must not wait for the httpd proxy if httpd is not set up yet. - # Unfortunately, knownservices.httpd.is_installed() can return - # false positives, so check for existence of our configuration file. - # TODO: Use a cleaner solution - use_proxy = True - if not (os.path.exists('/etc/httpd/conf.d/ipa.conf') and - os.path.exists(paths.HTTPD_IPA_PKI_PROXY_CONF)): - root_logger.debug( - 'The httpd proxy is not installed, wait on local port') - use_proxy = False - root_logger.debug('Waiting until the CA is running') - timeout = float(api.env.startup_timeout) - op_timeout = time.time() + timeout - while time.time() < op_timeout: - try: - status = dogtag.ca_status(use_proxy=use_proxy) - except Exception: - status = 'check interrupted' - root_logger.debug('The CA status is: %s' % status) - if status == 'running': - break - root_logger.debug('Waiting for CA to start...') - time.sleep(1) - else: - raise RuntimeError('CA did not start in %ss' % timeout) - - def start(self, instance_name="", capture_output=True, wait=True): - super(FedoraCAService, self).start( - instance_name, capture_output=capture_output, wait=wait) - if wait: - self.wait_until_running() - - def restart(self, instance_name="", capture_output=True, wait=True): - super(FedoraCAService, self).restart( - instance_name, capture_output=capture_output, wait=wait) - if wait: - self.wait_until_running() +class FedoraService(redhat_services.RedHatService): + system_units = fedora_system_units # Function that constructs proper Fedora-specific server classes for services # of specified name def fedora_service_class_factory(name): - if name == 'dirsrv': - return FedoraDirectoryService(name) - if name == 'ipa': - return FedoraIPAService(name) - if name == 'sshd': - return FedoraSSHService(name) - if name in ('pki-cad', 'pki_cad', 'pki-tomcatd', 'pki_tomcatd'): - return FedoraCAService(name) - return FedoraService(name) + if name == 'domainname': + return FedoraService(name) + return redhat_services.redhat_service_class_factory(name) # Magicdict containing FedoraService instances. -class FedoraServices(base_services.KnownServices): - def __init__(self): - services = dict() - for s in base_services.wellknownservices: - services[s] = fedora_service_class_factory(s) - # Call base class constructor. This will lock services to read-only - super(FedoraServices, self).__init__(services) +class FedoraServices(redhat_services.RedHatServices): + def service_class_factory(self, name): + return fedora_service_class_factory(name) # Objects below are expected to be exported by platform module -from ipaplatform.base.services import timedate_services +from ipaplatform.redhat.services import timedate_services service = fedora_service_class_factory knownservices = FedoraServices() diff --git a/ipaplatform/fedora/tasks.py b/ipaplatform/fedora/tasks.py index 279fbc07f..903bb9211 100644 --- a/ipaplatform/fedora/tasks.py +++ b/ipaplatform/fedora/tasks.py @@ -23,371 +23,11 @@ This module contains default Fedora-specific implementations of system tasks. ''' -import os -import stat -import socket -import sys -import urllib -import base64 +from ipaplatform.redhat.tasks import RedHatTaskNamespace -from subprocess import CalledProcessError -from nss.error import NSPRError -from pyasn1.error import PyAsn1Error -from ipapython.ipa_log_manager import root_logger, log_mgr -from ipapython import ipautil -import ipapython.errors - -from ipalib import x509 # FIXME: do not import from ipalib - -from ipaplatform.paths import paths -from ipaplatform.fedora.authconfig import FedoraAuthConfig -from ipaplatform.base.tasks import BaseTaskNamespace - - -log = log_mgr.get_logger(__name__) - - -def selinux_enabled(): - """ - Check if SELinux is enabled. - """ - if os.path.exists(paths.SELINUXENABLED): - try: - ipautil.run([paths.SELINUXENABLED]) - return True - except ipautil.CalledProcessError: - # selinuxenabled returns 1 if not enabled - return False - else: - # No selinuxenabled, no SELinux - return False - - -class FedoraTaskNamespace(BaseTaskNamespace): - - def restore_context(self, filepath, restorecon=paths.SBIN_RESTORECON): - """ - restore security context on the file path - SELinux equivalent is /path/to/restorecon <filepath> - restorecon's return values are not reliable so we have to - ignore them (BZ #739604). - - ipautil.run() will do the logging. - """ - - if not selinux_enabled(): - return - - if (os.path.exists(restorecon)): - ipautil.run([restorecon, filepath], raiseonerr=False) - - def check_selinux_status(self, restorecon=paths.RESTORECON): - """ - We don't have a specific package requirement for policycoreutils - which provides restorecon. This is because we don't require - SELinux on client installs. However if SELinux is enabled then - this package is required. - - This function returns nothing but may raise a Runtime exception - if SELinux is enabled but restorecon is not available. - """ - if not selinux_enabled(): - return - - if not os.path.exists(restorecon): - raise RuntimeError('SELinux is enabled but %s does not exist.\n' - 'Install the policycoreutils package and start ' - 'the installation again.' % restorecon) - - def restore_pre_ipa_client_configuration(self, fstore, statestore, - was_sssd_installed, - was_sssd_configured): - - auth_config = FedoraAuthConfig() - if statestore.has_state('authconfig'): - # disable only those configurations that we enabled during install - for conf in ('ldap', 'krb5', 'sssd', 'sssdauth', 'mkhomedir'): - cnf = statestore.restore_state('authconfig', conf) - # Do not disable sssd, as this can cause issues with its later - # uses. Remove it from statestore however, so that it becomes - # empty at the end of uninstall process. - if cnf and conf != 'sssd': - auth_config.disable(conf) - else: - # There was no authconfig status store - # It means the code was upgraded after original install - # Fall back to old logic - auth_config.disable("ldap") - auth_config.disable("krb5") - if not(was_sssd_installed and was_sssd_configured): - # Only disable sssdauth. Disabling sssd would cause issues - # with its later uses. - auth_config.disable("sssdauth") - auth_config.disable("mkhomedir") - - auth_config.execute() - - def set_nisdomain(self, nisdomain): - # Let authconfig setup the permanent configuration - auth_config = FedoraAuthConfig() - auth_config.add_parameter("nisdomain", nisdomain) - auth_config.execute() - - def modify_nsswitch_pam_stack(self, sssd, mkhomedir, statestore): - auth_config = FedoraAuthConfig() - - if sssd: - statestore.backup_state('authconfig', 'sssd', True) - statestore.backup_state('authconfig', 'sssdauth', True) - auth_config.enable("sssd") - auth_config.enable("sssdauth") - else: - statestore.backup_state('authconfig', 'ldap', True) - auth_config.enable("ldap") - auth_config.enable("forcelegacy") - - if mkhomedir: - statestore.backup_state('authconfig', 'mkhomedir', True) - auth_config.enable("mkhomedir") - - auth_config.execute() - - def modify_pam_to_use_krb5(self, statestore): - auth_config = FedoraAuthConfig() - statestore.backup_state('authconfig', 'krb5', True) - auth_config.enable("krb5") - auth_config.add_option("nostart") - auth_config.execute() - - def insert_ca_certs_into_systemwide_ca_store(self, ca_certs): - new_cacert_path = os.path.join(paths.SYSTEMWIDE_CA_STORE, 'ipa-ca.crt') - - if os.path.exists(new_cacert_path): - try: - os.remove(new_cacert_path) - except OSError, e: - root_logger.error( - "Could not remove %s: %s", new_cacert_path, e) - return False - - new_cacert_path = paths.IPA_P11_KIT - - try: - f = open(new_cacert_path, 'w') - except IOError, e: - root_logger.info("Failed to open %s: %s" % (new_cacert_path, e)) - return False - - f.write("# This file was created by IPA. Do not edit.\n" - "\n") - - has_eku = set() - for cert, nickname, trusted, ext_key_usage in ca_certs: - try: - subject = x509.get_der_subject(cert, x509.DER) - issuer = x509.get_der_issuer(cert, x509.DER) - serial_number = x509.get_der_serial_number(cert, x509.DER) - public_key_info = x509.get_der_public_key_info(cert, x509.DER) - except (NSPRError, PyAsn1Error), e: - root_logger.warning( - "Failed to decode certificate \"%s\": %s", nickname, e) - continue - - label = urllib.quote(nickname) - subject = urllib.quote(subject) - issuer = urllib.quote(issuer) - serial_number = urllib.quote(serial_number) - public_key_info = urllib.quote(public_key_info) - - cert = base64.b64encode(cert) - cert = x509.make_pem(cert) - - obj = ("[p11-kit-object-v1]\n" - "class: certificate\n" - "certificate-type: x-509\n" - "certificate-category: authority\n" - "label: \"%(label)s\"\n" - "subject: \"%(subject)s\"\n" - "issuer: \"%(issuer)s\"\n" - "serial-number: \"%(serial_number)s\"\n" - "x-public-key-info: \"%(public_key_info)s\"\n" % - dict(label=label, - subject=subject, - issuer=issuer, - serial_number=serial_number, - public_key_info=public_key_info)) - if trusted is True: - obj += "trusted: true\n" - elif trusted is False: - obj += "x-distrusted: true\n" - obj += "%s\n\n" % cert - f.write(obj) - - if ext_key_usage is not None and public_key_info not in has_eku: - if not ext_key_usage: - ext_key_usage = {x509.EKU_PLACEHOLDER} - try: - ext_key_usage = x509.encode_ext_key_usage(ext_key_usage) - except PyAsn1Error, e: - root_logger.warning( - "Failed to encode extended key usage for \"%s\": %s", - nickname, e) - continue - value = urllib.quote(ext_key_usage) - obj = ("[p11-kit-object-v1]\n" - "class: x-certificate-extension\n" - "label: \"ExtendedKeyUsage for %(label)s\"\n" - "x-public-key-info: \"%(public_key_info)s\"\n" - "object-id: 2.5.29.37\n" - "value: \"%(value)s\"\n\n" % - dict(label=label, - public_key_info=public_key_info, - value=value)) - f.write(obj) - has_eku.add(public_key_info) - - f.close() - - # Add the CA to the systemwide CA trust database - try: - ipautil.run([paths.UPDATE_CA_TRUST]) - except CalledProcessError, e: - root_logger.info("Failed to add CA to the systemwide " - "CA trust database: %s" % str(e)) - else: - root_logger.info('Added the CA to the systemwide CA trust ' - 'database.') - return True - - return False - - def remove_ca_certs_from_systemwide_ca_store(self): - ipa_ca_crt = os.path.join(paths.SYSTEMWIDE_CA_STORE, 'ipa-ca.crt') - update = False - - # Remove CA cert from systemwide store - for new_cacert_path in (paths.IPA_P11_KIT, ipa_ca_crt): - if not os.path.exists(new_cacert_path): - continue - try: - os.remove(new_cacert_path) - except OSError, e: - root_logger.error( - "Could not remove %s: %s", new_cacert_path, e) - else: - update = True - - if update: - try: - ipautil.run([paths.UPDATE_CA_TRUST]) - except CalledProcessError, e: - root_logger.error( - "Could not update systemwide CA trust database: %s", e) - return False - else: - root_logger.info("Systemwide CA database updated.") - return True - - return False - - def backup_and_replace_hostname(self, fstore, statestore, hostname): - old_hostname = socket.gethostname() - try: - ipautil.run([paths.BIN_HOSTNAME, hostname]) - except ipautil.CalledProcessError, e: - print >>sys.stderr, ("Failed to set this machine hostname to " - "%s (%s)." % (hostname, str(e))) - - filepath = paths.ETC_HOSTNAME - if os.path.exists(filepath): - # read old hostname - with open(filepath, 'r') as f: - for line in f.readlines(): - line = line.strip() - if not line or line.startswith('#'): - # skip comment or empty line - continue - old_hostname = line - break - fstore.backup_file(filepath) - - with open(filepath, 'w') as f: - f.write("%s\n" % hostname) - os.chmod(filepath, - stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) - os.chown(filepath, 0, 0) - self.restore_context(filepath) - - # store old hostname - statestore.backup_state('network', 'hostname', old_hostname) - - def restore_network_configuration(self, fstore, statestore): - old_filepath = paths.SYSCONFIG_NETWORK - old_hostname = statestore.get_state('network', 'hostname') - hostname_was_configured = False - - if fstore.has_file(old_filepath): - # This is Fedora >=18 instance that was upgraded from previous - # Fedora version which held network configuration - # in /etc/sysconfig/network - old_filepath_restore = paths.SYSCONFIG_NETWORK_IPABKP - fstore.restore_file(old_filepath, old_filepath_restore) - print "Deprecated configuration file '%s' was restored to '%s'" \ - % (old_filepath, old_filepath_restore) - hostname_was_configured = True - - filepath = paths.ETC_HOSTNAME - if fstore.has_file(filepath): - fstore.restore_file(filepath) - hostname_was_configured = True - - if not hostname_was_configured and old_hostname: - # hostname was not configured before but was set by IPA. Delete - # /etc/hostname to restore previous configuration - try: - os.remove(filepath) - except OSError: - pass - - def set_selinux_booleans(self, required_settings, backup_func=None): - def get_setsebool_args(changes): - args = [paths.SETSEBOOL, "-P"] - args.extend(["%s=%s" % update for update in changes.iteritems()]) - - return args - - if not selinux_enabled(): - return False - - updated_vars = {} - failed_vars = {} - for setting, state in required_settings.iteritems(): - try: - (stdout, stderr, rc) = ipautil.run([paths.GETSEBOOL, setting]) - original_state = stdout.split()[2] - if backup_func is not None: - backup_func(setting, original_state) - - if original_state != state: - updated_vars[setting] = state - except ipautil.CalledProcessError, e: - log.error("Cannot get SELinux boolean '%s': %s", setting, e) - failed_vars[setting] = state - - if updated_vars: - args = get_setsebool_args(updated_vars) - try: - ipautil.run(args) - except ipautil.CalledProcessError: - failed_vars.update(updated_vars) - - if failed_vars: - raise ipapython.errors.SetseboolError( - failed=failed_vars, - command=' '.join(get_setsebool_args(failed_vars))) - - return True +class FedoraTaskNamespace(RedHatTaskNamespace): + pass tasks = FedoraTaskNamespace() diff --git a/ipaplatform/redhat/__init__.py b/ipaplatform/redhat/__init__.py new file mode 100644 index 000000000..b6925a7a7 --- /dev/null +++ b/ipaplatform/redhat/__init__.py @@ -0,0 +1,22 @@ +# Authors: +# Tomas Babej <tbabej@redhat.com> +# +# Copyright (C) 2014 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +''' +This module contains Red Hat OS family specific platform files. +''' diff --git a/ipaplatform/fedora/authconfig.py b/ipaplatform/redhat/authconfig.py index 524d76929..901eb5163 100644 --- a/ipaplatform/fedora/authconfig.py +++ b/ipaplatform/redhat/authconfig.py @@ -21,7 +21,7 @@ from ipapython import ipautil -class FedoraAuthConfig(object): +class RedHatAuthConfig(object): """ AuthConfig class implements system-independent interface to configure system authentication resources. In Red Hat systems this is done with diff --git a/ipaplatform/redhat/paths.py b/ipaplatform/redhat/paths.py new file mode 100644 index 000000000..6d7e76dc5 --- /dev/null +++ b/ipaplatform/redhat/paths.py @@ -0,0 +1,33 @@ +# Authors: +# Tomas Babej <tbabej@redhat.com> +# +# Copyright (C) 2014 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +''' +This Red Hat OS family base platform module exports default filesystem paths as +common in Red Hat OS family-based systems. +''' + +# Fallback to default path definitions +from ipaplatform.base.paths import BasePathNamespace + + +class RedHatPathNamespace(BasePathNamespace): + pass + + +paths = RedHatPathNamespace() diff --git a/ipaplatform/redhat/services.py b/ipaplatform/redhat/services.py new file mode 100644 index 000000000..76e123ebe --- /dev/null +++ b/ipaplatform/redhat/services.py @@ -0,0 +1,237 @@ +# Author: Alexander Bokovoy <abokovoy@redhat.com> +# Tomas Babej <tbabej@redhat.com> +# +# Copyright (C) 2011-2014 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +""" +Contains Red Hat OS family-specific service class implementations. +""" + +import os +import time + +from ipaplatform.tasks import tasks +from ipaplatform.base import services as base_services + +from ipapython import ipautil, dogtag +from ipapython.ipa_log_manager import root_logger +from ipalib import api +from ipaplatform.paths import paths + +# Mappings from service names as FreeIPA code references to these services +# to their actual systemd service names + +# For beginning just remap names to add .service +# As more services will migrate to systemd, unit names will deviate and +# mapping will be kept in this dictionary +redhat_system_units = dict((x, "%s.service" % x) + for x in base_services.wellknownservices) + +redhat_system_units['rpcgssd'] = 'nfs-secure.service' +redhat_system_units['rpcidmapd'] = 'nfs-idmap.service' + +# Rewrite dirsrv and pki-tomcatd services as they support instances via separate +# service generator. To make this working, one needs to have both foo@.servic +# and foo.target -- the latter is used when request should be coming for +# all instances (like stop). systemd, unfortunately, does not allow one +# to request action for all service instances at once if only foo@.service +# unit is available. To add more, if any of those services need to be +# started/stopped automagically, one needs to manually create symlinks in +# /etc/systemd/system/foo.target.wants/ (look into systemd.py's enable() +# code). + +redhat_system_units['dirsrv'] = 'dirsrv@.service' +# Our directory server instance for PKI is dirsrv@PKI-IPA.service +redhat_system_units['pkids'] = 'dirsrv@PKI-IPA.service' +# Old style PKI instance +redhat_system_units['pki-cad'] = 'pki-cad@pki-ca.service' +redhat_system_units['pki_cad'] = redhat_system_units['pki-cad'] +# Our PKI instance is pki-tomcatd@pki-tomcat.service +redhat_system_units['pki-tomcatd'] = 'pki-tomcatd@pki-tomcat.service' +redhat_system_units['pki_tomcatd'] = redhat_system_units['pki-tomcatd'] +redhat_system_units['ipa-otpd'] = 'ipa-otpd.socket' + + +# Service classes that implement Red Hat OS family-specific behaviour + +class RedHatService(base_services.SystemdService): + system_units = redhat_system_units + + def __init__(self, service_name): + systemd_name = service_name + if service_name in self.system_units: + systemd_name = self.system_units[service_name] + else: + if '.' not in service_name: + # if service_name does not have a dot, it is not foo.service + # and not a foo.target. Thus, not correct service name for + # systemd, default to foo.service style then + systemd_name = "%s.service" % (service_name) + super(RedHatService, self).__init__(service_name, systemd_name) + + +class RedHatDirectoryService(RedHatService): + + def tune_nofile_platform(self, num=8192, fstore=None): + """ + Increase the number of files descriptors available to directory server + from the default 1024 to 8192. This will allow to support a greater + number of clients out of the box. + + This is a part of the implementation that is systemd-specific. + + Returns False if the setting of the nofile limit needs to be skipped. + """ + + if os.path.exists(paths.SYSCONFIG_DIRSRV_SYSTEMD): + # We need to enable LimitNOFILE=8192 in the dirsrv@.service + # Since 389-ds-base-1.2.10-0.8.a7 the configuration of the + # service parameters is performed via + # /etc/sysconfig/dirsrv.systemd file which is imported by systemd + # into dirsrv@.service unit + + replacevars = {'LimitNOFILE': str(num)} + ipautil.inifile_replace_variables(paths.SYSCONFIG_DIRSRV_SYSTEMD, + 'service', + replacevars=replacevars) + tasks.restore_context(paths.SYSCONFIG_DIRSRV_SYSTEMD) + ipautil.run(["/bin/systemctl", "--system", "daemon-reload"], + raiseonerr=False) + + return True + + def restart(self, instance_name="", capture_output=True, wait=True): + # We need to explicitly enable instances to install proper symlinks as + # dirsrv.target.wants/ dependencies. Standard systemd service class does it + # on enable() method call. Unfortunately, ipa-server-install does not do + # explicit dirsrv.enable() because the service startup is handled by ipactl. + # + # If we wouldn't do this, our instances will not be started as systemd would + # not have any clue about instances (PKI-IPA and the domain we serve) + # at all. Thus, hook into dirsrv.restart(). + + if instance_name: + elements = self.systemd_name.split("@") + + srv_etc = os.path.join(paths.ETC_SYSTEMD_SYSTEM_DIR, + self.systemd_name) + srv_tgt = os.path.join(paths.ETC_SYSTEMD_SYSTEM_DIR, + self.SYSTEMD_SRV_TARGET % (elements[0])) + srv_lnk = os.path.join(srv_tgt, + self.service_instance(instance_name)) + + if not os.path.exists(srv_etc): + self.enable(instance_name) + elif not os.path.samefile(srv_etc, srv_lnk): + os.unlink(srv_lnk) + os.symlink(srv_etc, srv_lnk) + + super(RedHatDirectoryService, self).restart(instance_name, + capture_output=capture_output, wait=wait) + + +class RedHatIPAService(RedHatService): + # Enforce restart of IPA services when we do enable it + # This gets around the fact that after ipa-server-install systemd thinks + # ipa.service is not yet started but all services were actually started + # already. + def enable(self, instance_name=""): + super(RedHatIPAService, self).enable(instance_name) + self.restart(instance_name) + + +class RedHatSSHService(RedHatService): + def get_config_dir(self, instance_name=""): + return '/etc/ssh' + + +class RedHatCAService(RedHatService): + def wait_until_running(self): + # We must not wait for the httpd proxy if httpd is not set up yet. + # Unfortunately, knownservices.httpd.is_installed() can return + # false positives, so check for existence of our configuration file. + # TODO: Use a cleaner solution + use_proxy = True + if not (os.path.exists('/etc/httpd/conf.d/ipa.conf') and + os.path.exists(paths.HTTPD_IPA_PKI_PROXY_CONF)): + root_logger.debug( + 'The httpd proxy is not installed, wait on local port') + use_proxy = False + root_logger.debug('Waiting until the CA is running') + timeout = float(api.env.startup_timeout) + op_timeout = time.time() + timeout + while time.time() < op_timeout: + try: + status = dogtag.ca_status(use_proxy=use_proxy) + except Exception: + status = 'check interrupted' + root_logger.debug('The CA status is: %s' % status) + if status == 'running': + break + root_logger.debug('Waiting for CA to start...') + time.sleep(1) + else: + raise RuntimeError('CA did not start in %ss' % timeout) + + def start(self, instance_name="", capture_output=True, wait=True): + super(RedHatCAService, self).start( + instance_name, capture_output=capture_output, wait=wait) + if wait: + self.wait_until_running() + + def restart(self, instance_name="", capture_output=True, wait=True): + super(RedHatCAService, self).restart( + instance_name, capture_output=capture_output, wait=wait) + if wait: + self.wait_until_running() + + +# Function that constructs proper Red Hat OS family-specific server classes for +# services of specified name + +def redhat_service_class_factory(name): + if name == 'dirsrv': + return RedHatDirectoryService(name) + if name == 'ipa': + return RedHatIPAService(name) + if name == 'sshd': + return RedHatSSHService(name) + if name in ('pki-cad', 'pki_cad', 'pki-tomcatd', 'pki_tomcatd'): + return RedHatCAService(name) + return RedHatService(name) + + +# Magicdict containing RedHatService instances. + +class RedHatServices(base_services.KnownServices): + def service_class_factory(self, name): + return redhat_service_class_factory(name) + + def __init__(self): + services = dict() + for s in base_services.wellknownservices: + services[s] = self.service_class_factory(s) + # Call base class constructor. This will lock services to read-only + super(RedHatServices, self).__init__(services) + + +# Objects below are expected to be exported by platform module + +from ipaplatform.base.services import timedate_services +service = redhat_service_class_factory +knownservices = RedHatServices() diff --git a/ipaplatform/redhat/tasks.py b/ipaplatform/redhat/tasks.py new file mode 100644 index 000000000..30033b274 --- /dev/null +++ b/ipaplatform/redhat/tasks.py @@ -0,0 +1,394 @@ +# Authors: Simo Sorce <ssorce@redhat.com> +# Alexander Bokovoy <abokovoy@redhat.com> +# Martin Kosek <mkosek@redhat.com> +# Tomas Babej <tbabej@redhat.com> +# +# Copyright (C) 2007-2014 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +''' +This module contains default Red Hat OS family-specific implementations of +system tasks. +''' + +import os +import stat +import socket +import sys +import urllib +import base64 + +from subprocess import CalledProcessError +from nss.error import NSPRError +from pyasn1.error import PyAsn1Error + +from ipapython.ipa_log_manager import root_logger, log_mgr +from ipapython import ipautil +import ipapython.errors + +from ipalib import x509 # FIXME: do not import from ipalib + +from ipaplatform.paths import paths +from ipaplatform.redhat.authconfig import RedHatAuthConfig +from ipaplatform.base.tasks import BaseTaskNamespace + + +log = log_mgr.get_logger(__name__) + + +def selinux_enabled(): + """ + Check if SELinux is enabled. + """ + if os.path.exists(paths.SELINUXENABLED): + try: + ipautil.run([paths.SELINUXENABLED]) + return True + except ipautil.CalledProcessError: + # selinuxenabled returns 1 if not enabled + return False + else: + # No selinuxenabled, no SELinux + return False + + +class RedHatTaskNamespace(BaseTaskNamespace): + + def restore_context(self, filepath, restorecon=paths.SBIN_RESTORECON): + """ + restore security context on the file path + SELinux equivalent is /path/to/restorecon <filepath> + restorecon's return values are not reliable so we have to + ignore them (BZ #739604). + + ipautil.run() will do the logging. + """ + + if not selinux_enabled(): + return + + if (os.path.exists(restorecon)): + ipautil.run([restorecon, filepath], raiseonerr=False) + + def check_selinux_status(self, restorecon=paths.RESTORECON): + """ + We don't have a specific package requirement for policycoreutils + which provides restorecon. This is because we don't require + SELinux on client installs. However if SELinux is enabled then + this package is required. + + This function returns nothing but may raise a Runtime exception + if SELinux is enabled but restorecon is not available. + """ + if not selinux_enabled(): + return + + if not os.path.exists(restorecon): + raise RuntimeError('SELinux is enabled but %s does not exist.\n' + 'Install the policycoreutils package and start ' + 'the installation again.' % restorecon) + + def restore_pre_ipa_client_configuration(self, fstore, statestore, + was_sssd_installed, + was_sssd_configured): + + auth_config = RedHatAuthConfig() + if statestore.has_state('authconfig'): + # disable only those configurations that we enabled during install + for conf in ('ldap', 'krb5', 'sssd', 'sssdauth', 'mkhomedir'): + cnf = statestore.restore_state('authconfig', conf) + # Do not disable sssd, as this can cause issues with its later + # uses. Remove it from statestore however, so that it becomes + # empty at the end of uninstall process. + if cnf and conf != 'sssd': + auth_config.disable(conf) + else: + # There was no authconfig status store + # It means the code was upgraded after original install + # Fall back to old logic + auth_config.disable("ldap") + auth_config.disable("krb5") + if not(was_sssd_installed and was_sssd_configured): + # Only disable sssdauth. Disabling sssd would cause issues + # with its later uses. + auth_config.disable("sssdauth") + auth_config.disable("mkhomedir") + + auth_config.execute() + + def set_nisdomain(self, nisdomain): + # Let authconfig setup the permanent configuration + auth_config = RedHatAuthConfig() + auth_config.add_parameter("nisdomain", nisdomain) + auth_config.execute() + + def modify_nsswitch_pam_stack(self, sssd, mkhomedir, statestore): + auth_config = RedHatAuthConfig() + + if sssd: + statestore.backup_state('authconfig', 'sssd', True) + statestore.backup_state('authconfig', 'sssdauth', True) + auth_config.enable("sssd") + auth_config.enable("sssdauth") + else: + statestore.backup_state('authconfig', 'ldap', True) + auth_config.enable("ldap") + auth_config.enable("forcelegacy") + + if mkhomedir: + statestore.backup_state('authconfig', 'mkhomedir', True) + auth_config.enable("mkhomedir") + + auth_config.execute() + + def modify_pam_to_use_krb5(self, statestore): + auth_config = RedHatAuthConfig() + statestore.backup_state('authconfig', 'krb5', True) + auth_config.enable("krb5") + auth_config.add_option("nostart") + auth_config.execute() + + def insert_ca_certs_into_systemwide_ca_store(self, ca_certs): + new_cacert_path = os.path.join(paths.SYSTEMWIDE_CA_STORE, 'ipa-ca.crt') + + if os.path.exists(new_cacert_path): + try: + os.remove(new_cacert_path) + except OSError, e: + root_logger.error( + "Could not remove %s: %s", new_cacert_path, e) + return False + + new_cacert_path = paths.IPA_P11_KIT + + try: + f = open(new_cacert_path, 'w') + except IOError, e: + root_logger.info("Failed to open %s: %s" % (new_cacert_path, e)) + return False + + f.write("# This file was created by IPA. Do not edit.\n" + "\n") + + has_eku = set() + for cert, nickname, trusted, ext_key_usage in ca_certs: + try: + subject = x509.get_der_subject(cert, x509.DER) + issuer = x509.get_der_issuer(cert, x509.DER) + serial_number = x509.get_der_serial_number(cert, x509.DER) + public_key_info = x509.get_der_public_key_info(cert, x509.DER) + except (NSPRError, PyAsn1Error), e: + root_logger.warning( + "Failed to decode certificate \"%s\": %s", nickname, e) + continue + + label = urllib.quote(nickname) + subject = urllib.quote(subject) + issuer = urllib.quote(issuer) + serial_number = urllib.quote(serial_number) + public_key_info = urllib.quote(public_key_info) + + cert = base64.b64encode(cert) + cert = x509.make_pem(cert) + + obj = ("[p11-kit-object-v1]\n" + "class: certificate\n" + "certificate-type: x-509\n" + "certificate-category: authority\n" + "label: \"%(label)s\"\n" + "subject: \"%(subject)s\"\n" + "issuer: \"%(issuer)s\"\n" + "serial-number: \"%(serial_number)s\"\n" + "x-public-key-info: \"%(public_key_info)s\"\n" % + dict(label=label, + subject=subject, + issuer=issuer, + serial_number=serial_number, + public_key_info=public_key_info)) + if trusted is True: + obj += "trusted: true\n" + elif trusted is False: + obj += "x-distrusted: true\n" + obj += "%s\n\n" % cert + f.write(obj) + + if ext_key_usage is not None and public_key_info not in has_eku: + if not ext_key_usage: + ext_key_usage = {x509.EKU_PLACEHOLDER} + try: + ext_key_usage = x509.encode_ext_key_usage(ext_key_usage) + except PyAsn1Error, e: + root_logger.warning( + "Failed to encode extended key usage for \"%s\": %s", + nickname, e) + continue + value = urllib.quote(ext_key_usage) + obj = ("[p11-kit-object-v1]\n" + "class: x-certificate-extension\n" + "label: \"ExtendedKeyUsage for %(label)s\"\n" + "x-public-key-info: \"%(public_key_info)s\"\n" + "object-id: 2.5.29.37\n" + "value: \"%(value)s\"\n\n" % + dict(label=label, + public_key_info=public_key_info, + value=value)) + f.write(obj) + has_eku.add(public_key_info) + + f.close() + + # Add the CA to the systemwide CA trust database + try: + ipautil.run([paths.UPDATE_CA_TRUST]) + except CalledProcessError, e: + root_logger.info("Failed to add CA to the systemwide " + "CA trust database: %s" % str(e)) + else: + root_logger.info('Added the CA to the systemwide CA trust ' + 'database.') + return True + + return False + + def remove_ca_certs_from_systemwide_ca_store(self): + ipa_ca_crt = os.path.join(paths.SYSTEMWIDE_CA_STORE, 'ipa-ca.crt') + update = False + + # Remove CA cert from systemwide store + for new_cacert_path in (paths.IPA_P11_KIT, ipa_ca_crt): + if not os.path.exists(new_cacert_path): + continue + try: + os.remove(new_cacert_path) + except OSError, e: + root_logger.error( + "Could not remove %s: %s", new_cacert_path, e) + else: + update = True + + if update: + try: + ipautil.run([paths.UPDATE_CA_TRUST]) + except CalledProcessError, e: + root_logger.error( + "Could not update systemwide CA trust database: %s", e) + return False + else: + root_logger.info("Systemwide CA database updated.") + return True + + return False + + def backup_and_replace_hostname(self, fstore, statestore, hostname): + old_hostname = socket.gethostname() + try: + ipautil.run([paths.BIN_HOSTNAME, hostname]) + except ipautil.CalledProcessError, e: + print >>sys.stderr, ("Failed to set this machine hostname to " + "%s (%s)." % (hostname, str(e))) + + filepath = paths.ETC_HOSTNAME + if os.path.exists(filepath): + # read old hostname + with open(filepath, 'r') as f: + for line in f.readlines(): + line = line.strip() + if not line or line.startswith('#'): + # skip comment or empty line + continue + old_hostname = line + break + fstore.backup_file(filepath) + + with open(filepath, 'w') as f: + f.write("%s\n" % hostname) + os.chmod(filepath, + stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) + os.chown(filepath, 0, 0) + self.restore_context(filepath) + + # store old hostname + statestore.backup_state('network', 'hostname', old_hostname) + + def restore_network_configuration(self, fstore, statestore): + old_filepath = paths.SYSCONFIG_NETWORK + old_hostname = statestore.get_state('network', 'hostname') + hostname_was_configured = False + + if fstore.has_file(old_filepath): + # This is Fedora >=18 instance that was upgraded from previous + # Fedora version which held network configuration + # in /etc/sysconfig/network + old_filepath_restore = paths.SYSCONFIG_NETWORK_IPABKP + fstore.restore_file(old_filepath, old_filepath_restore) + print "Deprecated configuration file '%s' was restored to '%s'" \ + % (old_filepath, old_filepath_restore) + hostname_was_configured = True + + filepath = paths.ETC_HOSTNAME + if fstore.has_file(filepath): + fstore.restore_file(filepath) + hostname_was_configured = True + + if not hostname_was_configured and old_hostname: + # hostname was not configured before but was set by IPA. Delete + # /etc/hostname to restore previous configuration + try: + os.remove(filepath) + except OSError: + pass + + def set_selinux_booleans(self, required_settings, backup_func=None): + def get_setsebool_args(changes): + args = [paths.SETSEBOOL, "-P"] + args.extend(["%s=%s" % update for update in changes.iteritems()]) + + return args + + if not selinux_enabled(): + return False + + updated_vars = {} + failed_vars = {} + for setting, state in required_settings.iteritems(): + try: + (stdout, stderr, rc) = ipautil.run([paths.GETSEBOOL, setting]) + original_state = stdout.split()[2] + if backup_func is not None: + backup_func(setting, original_state) + + if original_state != state: + updated_vars[setting] = state + except ipautil.CalledProcessError, e: + log.error("Cannot get SELinux boolean '%s': %s", setting, e) + failed_vars[setting] = state + + if updated_vars: + args = get_setsebool_args(updated_vars) + try: + ipautil.run(args) + except ipautil.CalledProcessError: + failed_vars.update(updated_vars) + + if failed_vars: + raise ipapython.errors.SetseboolError( + failed=failed_vars, + command=' '.join(get_setsebool_args(failed_vars))) + + return True + + +tasks = RedHatTaskNamespace() diff --git a/ipaplatform/setup.py.in b/ipaplatform/setup.py.in index 762e84455..0ba5822f9 100644 --- a/ipaplatform/setup.py.in +++ b/ipaplatform/setup.py.in @@ -67,7 +67,8 @@ def setup_package(): package_dir = {'ipaplatform': ''}, packages = ["ipaplatform", "ipaplatform.base", - "ipaplatform.fedora"], + "ipaplatform.fedora", + "ipaplatform.redhat"], ) finally: del sys.path[0] |