summaryrefslogtreecommitdiffstats
path: root/ipaplatform
diff options
context:
space:
mode:
Diffstat (limited to 'ipaplatform')
-rw-r--r--ipaplatform/fedora/paths.py5
-rw-r--r--ipaplatform/fedora/services.py197
-rw-r--r--ipaplatform/fedora/tasks.py366
-rw-r--r--ipaplatform/redhat/__init__.py22
-rw-r--r--ipaplatform/redhat/authconfig.py (renamed from ipaplatform/fedora/authconfig.py)2
-rw-r--r--ipaplatform/redhat/paths.py33
-rw-r--r--ipaplatform/redhat/services.py237
-rw-r--r--ipaplatform/redhat/tasks.py394
-rw-r--r--ipaplatform/setup.py.in3
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]