diff options
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | freeipa.spec.in | 5 | ||||
-rw-r--r-- | ipapython/Makefile | 2 | ||||
-rw-r--r-- | ipapython/ipautil.py | 47 | ||||
-rw-r--r-- | ipapython/platform/__init__.py | 23 | ||||
-rw-r--r-- | ipapython/platform/base.py | 150 | ||||
-rw-r--r-- | ipapython/platform/redhat.py | 176 | ||||
-rw-r--r-- | ipapython/services.py.in | 48 | ||||
-rw-r--r-- | ipapython/setup.py.in | 2 | ||||
-rw-r--r-- | ipapython/sysrestore.py | 5 | ||||
-rw-r--r-- | ipaserver/install/service.py | 68 |
11 files changed, 436 insertions, 98 deletions
@@ -8,6 +8,8 @@ PRJ_PREFIX=freeipa RPMBUILD ?= $(PWD)/rpmbuild TARGET ?= master +SUPPORTED_PLATFORM=redhat + # After updating the version in VERSION you should run the version-update # target. @@ -109,6 +111,12 @@ version-update: release-update ipa-client/ipa-client.spec.in > ipa-client/ipa-client.spec sed -e s/__VERSION__/$(IPA_VERSION)/ ipa-client/version.m4.in \ > ipa-client/version.m4 + + if [ "$(SUPPORTED_PLATFORM)" != "" ]; then \ + sed -e s/SUPPORTED_PLATFORM/$(SUPPORTED_PLATFORM)/ ipapython/services.py.in \ + > ipapython/services.py; \ + fi + if [ "$(SKIP_API_VERSION_CHECK)" != "yes" ]; then \ ./makeapi --validate; \ fi diff --git a/freeipa.spec.in b/freeipa.spec.in index fc7141cc1..0f358fb4c 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -531,7 +531,9 @@ fi %defattr(-,root,root,-) %doc COPYING README Contributors.txt %dir %{python_sitelib}/ipapython +%dir %{python_sitelib}/ipapython/platform %{python_sitelib}/ipapython/*.py* +%{python_sitelib}/ipapython/platform/*.py* %dir %{python_sitelib}/ipalib %{python_sitelib}/ipalib/* %{python_sitearch}/default_encoding_utf8.so @@ -543,6 +545,9 @@ fi %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/default.conf %changelog +* Tue Sep 13 2011 Alexander Bokovoy <abokovoy@redhat.com> - 2.99.0-5 +- Make sure platform adaptation is packaged in -python sub-package + * Fri Sep 9 2011 Martin Kosek <mkosek@redhat.com> - 2.99.0-4 - Add soft dependency for bind and bind-dyndb-ldap required versions diff --git a/ipapython/Makefile b/ipapython/Makefile index c96d5d9c1..a09ffd1bb 100644 --- a/ipapython/Makefile +++ b/ipapython/Makefile @@ -27,7 +27,7 @@ clean: done distclean: clean - rm -f setup.py ipa-python.spec version.py + rm -f setup.py ipa-python.spec version.py services.py @for subdir in $(SUBDIRS); do \ (cd $$subdir && $(MAKE) $@) || exit 1; \ done diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py index 97284b384..72cf400f9 100644 --- a/ipapython/ipautil.py +++ b/ipapython/ipautil.py @@ -1,6 +1,6 @@ # Authors: Simo Sorce <ssorce@redhat.com> # -# Copyright (C) 2007 Red Hat +# Copyright (C) 2007-2011 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or modify @@ -1053,51 +1053,6 @@ def get_gsserror(e): return (major, minor) -def service_stop(service_name, instance_name="", capture_output=True): - run(["/sbin/service", service_name, "stop", instance_name], - capture_output=capture_output) - -def service_start(service_name, instance_name="", capture_output=True): - run(["/sbin/service", service_name, "start", instance_name], - capture_output=capture_output) - -def service_restart(service_name, instance_name="", capture_output=True): - run(["/sbin/service", service_name, "restart", instance_name], - capture_output=capture_output) - -def service_is_running(service_name, instance_name=""): - ret = True - try: - run(["/sbin/service", service_name, "status", instance_name]) - except CalledProcessError: - ret = False - return ret - -def service_is_installed(service_name): - installed = True - try: - run(["/sbin/service", service_name, "status"]) - except CalledProcessError, e: - if e.returncode == 1: - # service is not installed or there is other serious issue - installed = False - return installed - -def service_is_enabled(service_name): - (stdout, stderr, returncode) = run(["/sbin/chkconfig", service_name], raiseonerr=False) - return (returncode == 0) - -def chkconfig_on(service_name): - run(["/sbin/chkconfig", service_name, "on"]) - -def chkconfig_off(service_name): - run(["/sbin/chkconfig", service_name, "off"]) - -def chkconfig_add(service_name): - run(["/sbin/chkconfig", "--add", service_name]) - -def chkconfig_del(service_name): - run(["/sbin/chkconfig", "--del", service_name]) def host_port_open(host, port, socket_stream=True, socket_timeout=None): families = (socket.AF_INET, socket.AF_INET6) diff --git a/ipapython/platform/__init__.py b/ipapython/platform/__init__.py new file mode 100644 index 000000000..e0a394b02 --- /dev/null +++ b/ipapython/platform/__init__.py @@ -0,0 +1,23 @@ +# Authors: +# Alexander Bokovoy <abokovoy@redhat.com> +# +# Copyright (C) 2011 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/>. + +""" +Sub-package containing all platform-specific adaptation for ipapython.services. +Should not be used directly. +""" diff --git a/ipapython/platform/base.py b/ipapython/platform/base.py new file mode 100644 index 000000000..f9d409972 --- /dev/null +++ b/ipapython/platform/base.py @@ -0,0 +1,150 @@ +# Authors: Alexander Bokovoy <abokovoy@redhat.com> +# +# Copyright (C) 2011 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/>. + +from ipalib.plugable import MagicDict + +# Canonical names of services as IPA wants to see them. As we need to have *some* naming, +# set them as in Red Hat distributions. Actual implementation should make them available +# through knownservices.<name> and take care of remapping internally, if needed +wellknownservices = ['certmonger', 'dirsrv', 'httpd', 'ipa', 'krb5kdc', 'messagebus', + 'nslcd', 'nscd', 'ntpd', 'portmap', 'rpcbind'] + +class AuthConfig(object): + """ + AuthConfig class implements system-independent interface to configure + system authentication resources. In Red Hat systems this is done with + authconfig(8) utility. + + AuthConfig class is nothing more than a tool to gather configuration options + and execute their processing. These options then converted by an actual implementation + to series of a system calls to appropriate utilities performing real configuration. + + IPA *expects* names of AuthConfig's options to follow authconfig(8) naming scheme! + + Actual implementation should be done in ipapython/platform/<platform>.py by inheriting from + platform.AuthConfig and redefining __build_args() and execute() methods. + + from ipapython.platform import platform + class PlatformAuthConfig(platform.AuthConfig): + def __build_args(): + ... + + def execute(): + ... + + authconfig = PlatformAuthConfig + .... + + See ipapython/platform/redhat.py for a sample implementation that uses authconfig(8) as its backend. + + From IPA code perspective, the authentication configuration should be done with use of ipapython.services.authconfig: + + from ipapython import services as ipaservices + auth_config = ipaservices.authconfig() + auth_config.disable("ldap").\ + disable("krb5").\ + disable("sssd").\ + disable("sssdauth").\ + disable("mkhomedir").\ + add_option("update").\ + enable("nis").\ + add_parameter("nisdomain","foobar") + auth_config.execute() + + If you need to re-use existing AuthConfig instance for multiple runs, make sure to + call 'AuthConfig.reset()' between the runs. + """ + + def __init__(self): + self.parameters = {} + + def enable(self, option): + self.parameters[option] = True + return self + + def disable(self, option): + self.parameters[option] = False + return self + + def add_option(self, option): + self.parameters[option] = None + return self + + def add_parameter(self, option, value): + self.parameters[option] = [value] + return self + + def __build_args(self): + # do nothing + return None + + def execute(self): + # do nothing + return None + + def reset(self): + self.parameters = {} + return self + +class PlatformService(object): + """ + PlatformService abstracts out external process running on the system which is possible + to administer (start, stop, check status, etc). + + """ + + def __init__(self, service_name): + self.service_name = service_name + + def start(self, instance_name="", capture_output=True): + return + + def stop(self, instance_name="", capture_output=True): + return + + def restart(self, instance_name="", capture_output=True): + return + + def is_running(self): + return False + + def is_installed(self): + return False + + def is_enabled(self): + return False + + def enable(self): + return + + def disable(self): + return + + def install(self): + return + + def remove(self): + return + +class KnownServices(MagicDict): + """ + KnownServices is an abstract class factory that should give out instances of well-known + platform services. Actual implementation must create these instances as its own attributes + on first access (or instance creation) and cache them. + """ + diff --git a/ipapython/platform/redhat.py b/ipapython/platform/redhat.py new file mode 100644 index 000000000..6d1d42368 --- /dev/null +++ b/ipapython/platform/redhat.py @@ -0,0 +1,176 @@ +# Authors: Simo Sorce <ssorce@redhat.com> +# Alexander Bokovoy <abokovoy@redhat.com> +# +# Copyright (C) 2007-2011 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/>. +# + +import tempfile +import re +import os +import stat +import sys +from ipapython import ipautil +from ipapython.platform import base + +# All what we allow exporting directly from this module +# Everything else is made available through these symbols when they directly imported into ipapython.services: +# authconfig -- class reference for platform-specific implementation of authconfig(8) +# service -- class reference for platform-specific implementation of a PlatformService class +# knownservices -- factory instance to access named services IPA cares about, names are ipapython.services.wellknownservices +# backup_and_replace_hostname -- platform-specific way to set hostname and make it persistent over reboots +# restore_context -- platform-sepcific way to restore security context, if applicable +__all__ = ['authconfig', 'service', 'knownservices', 'backup_and_replace_hostname', 'restore_context'] + +class RedHatService(base.PlatformService): + def stop(self, instance_name="", capture_output=True): + ipautil.run(["/sbin/service", self.service_name, "stop", instance_name], capture_output=capture_output) + + def start(self, instance_name="", capture_output=True): + ipautil.run(["/sbin/service", self.service_name, "start", instance_name], capture_output=capture_output) + + def restart(self, instance_name="", capture_output=True): + ipautil.run(["/sbin/service", self.service_name, "restart", instance_name], capture_output=capture_output) + + def is_running(self, instance_name=""): + ret = True + try: + (sout,serr,rcode) = ipautil.run(["/sbin/service", self.service_name, "status", instance_name]) + if sout.find("is stopped") >= 0: + ret = False + except ipautil.CalledProcessError: + ret = False + return ret + + def is_installed(self): + installed = True + try: + ipautil.run(["/sbin/service", self.service_name, "status"]) + except ipautil.CalledProcessError, e: + if e.returncode == 1: + # service is not installed or there is other serious issue + installed = False + return installed + + def is_enabled(self): + (stdout, stderr, returncode) = ipautil.run(["/sbin/chkconfig", self.service_name],raiseonerr=False) + return (returncode == 0) + + def enable(self): + ipautil.run(["/sbin/chkconfig", self.service_name, "on"]) + + def disable(self): + ipautil.run(["/sbin/chkconfig", self.service_name, "off"]) + + def install(self): + ipautil.run(["/sbin/chkconfig", "--add", self.service_name]) + + def remove(self): + ipautil.run(["/sbin/chkconfig", "--del", self.service_name]) + +class RedHatAuthConfig(base.AuthConfig): + """ + AuthConfig class implements system-independent interface to configure + system authentication resources. In Red Hat-produced systems this is done with + authconfig(8) utility. + """ + def __build_args(self): + args = [] + for (option, value) in self.parameters.items(): + if type(value) is bool: + if value: + args.append("--enable%s" % (option)) + else: + args.append("--disable%s" % (option)) + elif type(value) in (tuple, list): + args.append("--%s" % (option)) + args.append("%s" % (value[0])) + elif value is None: + args.append("--%s" % (option)) + else: + args.append("--%s%s" % (option,value)) + return args + + def execute(self): + args = self.__build_args() + ipautil.run(["/usr/sbin/authconfig"]+args) + +class RedHatServices(base.KnownServices): + def __init__(self): + services = dict() + for s in base.wellknownservices: + services[s] = RedHatService(s) + # Call base class constructor. This will lock services to read-only + super(RedHatServices, self).__init__(services) + +authconfig = RedHatAuthConfig +service = RedHatService +knownservices = RedHatServices() + +def restore_context(filepath): + """ + restore security context on the file path + SE Linux equivalent is /sbin/restorecon <filepath> + """ + ipautil.run(["/sbin/restorecon", filepath]) + + +def backup_and_replace_hostname(fstore, statestore, hostname): + network_filename = "/etc/sysconfig/network" + # Backup original /etc/sysconfig/network + fstore.backup_file(network_filename) + hostname_pattern = re.compile(''' +(^ + \s* + (?P<option> [^\#;]+?) + (\s*=\s*) + (?P<value> .+?)? + (\s*((\#|;).*)?)? +$)''', re.VERBOSE) + temp_filename = None + with tempfile.NamedTemporaryFile(delete=False) as new_config: + temp_filename = new_config.name + with open(network_filename, 'r') as f: + for line in f: + new_line = line + m = hostname_pattern.match(line) + if m: + option, value = m.group('option', 'value') + if option is not None and option == 'HOSTNAME': + if value is not None and hostname != value: + new_line = u"HOSTNAME=%s\n" % (hostname) + statestore.backup_state('network', 'hostname', value) + new_config.write(new_line) + new_config.flush() + # Make sure the resulting file is readable by others before installing it + os.fchmod(new_config.fileno(), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) + os.fchown(new_config.fileno(), 0, 0) + + # At this point new_config is closed but not removed due to 'delete=False' above + # Now, install the temporary file as configuration and ensure old version is available as .orig + # While .orig file is not used during uninstall, it is left there for administrator. + ipautil.install_file(temp_filename, network_filename) + try: + ipautil.run(['/bin/hostname', hostname]) + except ipautil.CalledProcessError, e: + print >>sys.stderr, "Failed to set this machine hostname to %s (%s)." % (hostname, str(e)) + + # For SE Linux environments it is important to reset SE labels to the expected ones + try: + restore_context(network_filename) + except ipautil.CalledProcessError, e: + print >>sys.stderr, "Failed to set permissions for %s (%s)." % (network_filename, str(e)) + diff --git a/ipapython/services.py.in b/ipapython/services.py.in new file mode 100644 index 000000000..60bd8b531 --- /dev/null +++ b/ipapython/services.py.in @@ -0,0 +1,48 @@ +# Authors: Alexander Bokovoy <abokovoy@redhat.com> +# +# Copyright (C) 2011 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/>. + +# authconfig is an entry point to platform-provided AuthConfig implementation +# (instance of ipapython.platform.base.AuthConfig) +authconfig = None + +# knownservices is an entry point to known platform services +# (instance of ipapython.platform.base.KnownServices) +knownservices = None + +# service is a class to instantiate ipapython.platform.base.PlatformService +service = None + +# restore context default implementation that does nothing +def restore_context_default(filepath): + return + +# Restore security context for a path +# If the platform has security features where context is important, implement your own +# version in platform services +restore_context = restore_context_default + +# Default implementation of backup and replace hostname that does nothing +def backup_and_replace_hostname_default(fstore, statestore, hostname): + return + +# Backup and replace system's hostname +# Since many platforms have their own way how to store system's hostname, this method must be +# implemented in platform services +backup_and_replace_hostname = backup_and_replace_hostname_default + +from ipapython.platform.SUPPORTED_PLATFORM import * diff --git a/ipapython/setup.py.in b/ipapython/setup.py.in index d9ee28c55..df1cacf85 100644 --- a/ipapython/setup.py.in +++ b/ipapython/setup.py.in @@ -65,7 +65,7 @@ def setup_package(): classifiers=filter(None, CLASSIFIERS.split('\n')), platforms = ["Linux", "Solaris", "Unix"], package_dir = {'ipapython': ''}, - packages = [ "ipapython" ], + packages = [ "ipapython", "ipapython.platform" ], ) finally: del sys.path[0] diff --git a/ipapython/sysrestore.py b/ipapython/sysrestore.py index 1025449c2..9b0e39fcb 100644 --- a/ipapython/sysrestore.py +++ b/ipapython/sysrestore.py @@ -32,6 +32,7 @@ import random import string from ipapython import ipautil +from ipapython import services as ipaservices SYSRESTORE_PATH = "/tmp" SYSRESTORE_INDEXFILE = "sysrestore.index" @@ -165,7 +166,7 @@ class FileStore: os.chown(path, int(uid), int(gid)) os.chmod(path, int(mode)) - ipautil.run(["/sbin/restorecon", path]) + ipaservices.restore_context(path) del self.files[filename] self.save() @@ -196,7 +197,7 @@ class FileStore: os.chown(path, int(uid), int(gid)) os.chmod(path, int(mode)) - ipautil.run(["/sbin/restorecon", path]) + ipaservices.restore_context(path) #force file to be deleted self.files = {} diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py index 2f80749ad..a1c94dfaa 100644 --- a/ipaserver/install/service.py +++ b/ipaserver/install/service.py @@ -22,6 +22,7 @@ import os, socket import tempfile from ipapython import sysrestore from ipapython import ipautil +from ipapython import services as ipaservices from ipalib import errors import ldap from ipaserver import ipaldap @@ -40,36 +41,6 @@ SERVICE_LIST = { 'CA':('pki-cad', 50) } -def stop(service_name, instance_name="", capture_output=True): - ipautil.service_stop(service_name, instance_name, capture_output) - -def start(service_name, instance_name="", capture_output=True): - ipautil.service_start(service_name, instance_name, capture_output) - -def restart(service_name, instance_name="", capture_output=True): - ipautil.service_restart(service_name, instance_name, capture_output) - -def is_running(service_name, instance_name=""): - return ipautil.service_is_running(service_name, instance_name) - -def is_installed(service_name): - return ipautil.service_is_installed(service_name) - -def chkconfig_on(service_name): - ipautil.chkconfig_on(service_name) - -def chkconfig_off(service_name): - ipautil.chkconfig_on(service_name) - -def chkconfig_add(service_name): - ipautil.chkconfig_on(service_name) - -def chkconfig_del(service_name): - ipautil.chkconfig_on(service_name) - -def is_enabled(service_name): - return ipautil.service_is_enabled(service_name) - def print_msg(message, output_fd=sys.stdout): logging.debug(message) output_fd.write(message) @@ -79,6 +50,7 @@ def print_msg(message, output_fd=sys.stdout): class Service(object): def __init__(self, service_name, sstore=None, dm_password=None): self.service_name = service_name + self.service = ipaservices.service(service_name) self.steps = [] self.output_fd = sys.stdout self.dm_password = dm_password @@ -213,31 +185,31 @@ class Service(object): self.output_fd = fd def stop(self, instance_name="", capture_output=True): - stop(self.service_name, instance_name, capture_output=capture_output) + self.service.stop(instance_name, capture_output=capture_output) def start(self, instance_name="", capture_output=True): - start(self.service_name, instance_name, capture_output=capture_output) + self.service.start(instance_name, capture_output=capture_output) def restart(self, instance_name="", capture_output=True): - restart(self.service_name, instance_name, capture_output=capture_output) + self.service.restart(instance_name, capture_output=capture_output) def is_running(self): - return is_running(self.service_name) + return self.service.is_running() - def chkconfig_add(self): - chkconfig_add(self.service_name) + def install(self): + self.service.install() - def chkconfig_del(self): - chkconfig_del(self.service_name) + def remove(self): + self.service.remove() - def chkconfig_on(self): - chkconfig_on(self.service_name) + def enable(self): + self.service.enable() - def chkconfig_off(self): - chkconfig_off(self.service_name) + def disable(self): + self.service.disable() def is_enabled(self): - return is_enabled(self.service_name) + return self.service.is_enabled() def backup_state(self, key, value): self.sstore.backup_state(self.service_name, key, value) @@ -300,7 +272,7 @@ class Service(object): return conn def ldap_enable(self, name, fqdn, dm_password, ldap_suffix): - self.chkconfig_off() + self.disable() conn = self.__get_conn(fqdn, dm_password) entry_name = "cn=%s,cn=%s,%s,%s" % (name, fqdn, @@ -336,10 +308,10 @@ class SimpleServiceInstance(Service): self.restart() def __enable(self): - self.chkconfig_add() + self.enable() self.backup_state("enabled", self.is_enabled()) if self.gensvc_name == None: - self.chkconfig_on() + self.enable() else: self.ldap_enable(self.gensvc_name, self.fqdn, self.dm_password, self.suffix) @@ -354,5 +326,5 @@ class SimpleServiceInstance(Service): if not running is None and not running: self.stop() if not enabled is None and not enabled: - self.chkconfig_off() - self.chkconfig_del() + self.disable() + self.remove() |