diff options
author | Jan Cholasta <jcholast@redhat.com> | 2014-10-03 11:14:56 +0200 |
---|---|---|
committer | Martin Kosek <mkosek@redhat.com> | 2014-10-09 15:37:24 +0200 |
commit | 308d2dd406d5ec935b209c52803ce7de4136fe0b (patch) | |
tree | 3d8b88422e1351de453f2f2a6b255316d302248b /ipaplatform/redhat/tasks.py | |
parent | 57c510dcc7a08d908fd55856a735b8dca6684571 (diff) | |
download | freeipa-308d2dd406d5ec935b209c52803ce7de4136fe0b.tar.gz freeipa-308d2dd406d5ec935b209c52803ce7de4136fe0b.tar.xz freeipa-308d2dd406d5ec935b209c52803ce7de4136fe0b.zip |
Split off generic Red Hat-like platform code from Fedora platform code
https://fedorahosted.org/freeipa/ticket/4562
Reviewed-By: Martin Kosek <mkosek@redhat.com>
Diffstat (limited to 'ipaplatform/redhat/tasks.py')
-rw-r--r-- | ipaplatform/redhat/tasks.py | 394 |
1 files changed, 394 insertions, 0 deletions
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() |