diff options
author | Alexander Bokovoy <abokovoy@redhat.com> | 2011-09-13 00:01:23 +0300 |
---|---|---|
committer | Martin Kosek <mkosek@redhat.com> | 2011-09-13 11:25:58 +0200 |
commit | 136220265324111f77e3eec4b162a5df80623d07 (patch) | |
tree | b0f662fff54e79f0d2553e3865d3f911ce7327c1 /ipapython | |
parent | 50a836b44cbeb4b6e31d71522cd7240da1cd7b6f (diff) | |
download | freeipa-136220265324111f77e3eec4b162a5df80623d07.tar.gz freeipa-136220265324111f77e3eec4b162a5df80623d07.tar.xz freeipa-136220265324111f77e3eec4b162a5df80623d07.zip |
Introduce platform-specific adaptation for services used by FreeIPA.
Refactor FreeIPA code to allow abstracting all calls to external processes and
dependencies on modification of system-wide configuration. A platform provider
would give its own implementation of those methods and FreeIPA would use it
based on what's built in packaging process.
https://fedorahosted.org/freeipa/ticket/1605
Diffstat (limited to 'ipapython')
-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 |
8 files changed, 403 insertions, 50 deletions
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 = {} |