Imported Upstream version 4.6.2
This commit is contained in:
22
ipaplatform/redhat/__init__.py
Normal file
22
ipaplatform/redhat/__init__.py
Normal file
@@ -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.
|
||||
'''
|
||||
116
ipaplatform/redhat/authconfig.py
Normal file
116
ipaplatform/redhat/authconfig.py
Normal file
@@ -0,0 +1,116 @@
|
||||
# Authors: Simo Sorce <ssorce@redhat.com>
|
||||
# Alexander Bokovoy <abokovoy@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/>.
|
||||
|
||||
from ipaplatform.paths import paths
|
||||
from ipapython import ipautil
|
||||
from ipapython.admintool import ScriptError
|
||||
import os
|
||||
|
||||
FILES_TO_NOT_BACKUP = ['passwd', 'group', 'shadow', 'gshadow']
|
||||
|
||||
|
||||
class RedHatAuthConfig(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.
|
||||
|
||||
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 reset(self):
|
||||
self.parameters = {}
|
||||
return self
|
||||
|
||||
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, update=True):
|
||||
if update:
|
||||
self.add_option("update")
|
||||
|
||||
args = self.build_args()
|
||||
try:
|
||||
ipautil.run([paths.AUTHCONFIG] + args)
|
||||
except ipautil.CalledProcessError:
|
||||
raise ScriptError("Failed to execute authconfig command")
|
||||
|
||||
def backup(self, path):
|
||||
try:
|
||||
ipautil.run([paths.AUTHCONFIG, "--savebackup", path])
|
||||
except ipautil.CalledProcessError:
|
||||
raise ScriptError("Failed to execute authconfig command")
|
||||
|
||||
# do not backup these files since we don't want to mess with
|
||||
# users/groups during restore. Authconfig doesn't seem to mind about
|
||||
# having them deleted from backup dir
|
||||
files_to_remove = [os.path.join(path, f) for f in FILES_TO_NOT_BACKUP]
|
||||
for filename in files_to_remove:
|
||||
try:
|
||||
os.remove(filename)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def restore(self, path):
|
||||
try:
|
||||
ipautil.run([paths.AUTHCONFIG, "--restorebackup", path])
|
||||
except ipautil.CalledProcessError:
|
||||
raise ScriptError("Failed to execute authconfig command")
|
||||
17
ipaplatform/redhat/constants.py
Normal file
17
ipaplatform/redhat/constants.py
Normal file
@@ -0,0 +1,17 @@
|
||||
#
|
||||
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
'''
|
||||
This Red Hat OS family base platform module exports default platform
|
||||
related constants for the Red Hat OS family-based systems.
|
||||
'''
|
||||
|
||||
# Fallback to default path definitions
|
||||
from ipaplatform.base.constants import BaseConstantsNamespace
|
||||
|
||||
|
||||
class RedHatConstantsNamespace(BaseConstantsNamespace):
|
||||
pass
|
||||
|
||||
constants = RedHatConstantsNamespace()
|
||||
40
ipaplatform/redhat/paths.py
Normal file
40
ipaplatform/redhat/paths.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# 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.
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
# Fallback to default path definitions
|
||||
from ipaplatform.base.paths import BasePathNamespace
|
||||
|
||||
|
||||
class RedHatPathNamespace(BasePathNamespace):
|
||||
# https://docs.python.org/2/library/platform.html#cross-platform
|
||||
if sys.maxsize > 2**32:
|
||||
LIBSOFTHSM2_SO = BasePathNamespace.LIBSOFTHSM2_SO_64
|
||||
PAM_KRB5_SO = BasePathNamespace.PAM_KRB5_SO_64
|
||||
BIND_LDAP_SO = BasePathNamespace.BIND_LDAP_SO_64
|
||||
AUTHCONFIG = '/usr/sbin/authconfig'
|
||||
|
||||
|
||||
paths = RedHatPathNamespace()
|
||||
249
ipaplatform/redhat/services.py
Normal file
249
ipaplatform/redhat/services.py
Normal file
@@ -0,0 +1,249 @@
|
||||
# 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 logging
|
||||
import os
|
||||
import time
|
||||
import contextlib
|
||||
|
||||
from ipaplatform.base import services as base_services
|
||||
|
||||
from ipapython import ipautil, dogtag
|
||||
from ipaplatform.paths import paths
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 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 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'
|
||||
redhat_system_units['ipa-dnskeysyncd'] = 'ipa-dnskeysyncd.service'
|
||||
redhat_system_units['named-regular'] = 'named.service'
|
||||
redhat_system_units['named-pkcs11'] = 'named-pkcs11.service'
|
||||
redhat_system_units['named'] = redhat_system_units['named-pkcs11']
|
||||
redhat_system_units['ods-enforcerd'] = 'ods-enforcerd.service'
|
||||
redhat_system_units['ods_enforcerd'] = redhat_system_units['ods-enforcerd']
|
||||
redhat_system_units['ods-signerd'] = 'ods-signerd.service'
|
||||
redhat_system_units['ods_signerd'] = redhat_system_units['ods-signerd']
|
||||
redhat_system_units['gssproxy'] = 'gssproxy.service'
|
||||
|
||||
|
||||
# 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, api=None):
|
||||
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, api)
|
||||
|
||||
|
||||
class RedHatDirectoryService(RedHatService):
|
||||
|
||||
def is_installed(self, instance_name):
|
||||
file_path = "{}/{}-{}".format(paths.ETC_DIRSRV, "slapd", instance_name)
|
||||
return os.path.exists(file_path)
|
||||
|
||||
def restart(self, instance_name="", capture_output=True, wait=True,
|
||||
ldapi=False):
|
||||
# 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)
|
||||
|
||||
with self.__wait(instance_name, wait, ldapi) as wait:
|
||||
super(RedHatDirectoryService, self).restart(
|
||||
instance_name, capture_output=capture_output, wait=wait)
|
||||
|
||||
def start(self, instance_name="", capture_output=True, wait=True,
|
||||
ldapi=False):
|
||||
with self.__wait(instance_name, wait, ldapi) as wait:
|
||||
super(RedHatDirectoryService, self).start(
|
||||
instance_name, capture_output=capture_output, wait=wait)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def __wait(self, instance_name, wait, ldapi):
|
||||
if ldapi:
|
||||
instance_name = self.service_instance(instance_name)
|
||||
if instance_name.endswith('.service'):
|
||||
instance_name = instance_name[:-8]
|
||||
if instance_name.startswith('dirsrv'):
|
||||
# this is intentional, return the empty string if the instance
|
||||
# name is 'dirsrv'
|
||||
instance_name = instance_name[7:]
|
||||
if not instance_name:
|
||||
ldapi = False
|
||||
if ldapi:
|
||||
yield False
|
||||
socket_name = paths.SLAPD_INSTANCE_SOCKET_TEMPLATE % instance_name
|
||||
ipautil.wait_for_open_socket(socket_name,
|
||||
self.api.env.startup_timeout)
|
||||
else:
|
||||
yield 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 RedHatCAService(RedHatService):
|
||||
def wait_until_running(self):
|
||||
logger.debug('Waiting until the CA is running')
|
||||
timeout = float(self.api.env.startup_timeout)
|
||||
op_timeout = time.time() + timeout
|
||||
while time.time() < op_timeout:
|
||||
try:
|
||||
# check status of CA instance on this host, not remote ca_host
|
||||
status = dogtag.ca_status(self.api.env.host)
|
||||
except Exception as e:
|
||||
status = 'check interrupted due to error: %s' % e
|
||||
logger.debug('The CA status is: %s', status)
|
||||
if status == 'running':
|
||||
break
|
||||
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()
|
||||
|
||||
def is_running(self, instance_name="", wait=True):
|
||||
if instance_name:
|
||||
return super(RedHatCAService, self).is_running(instance_name)
|
||||
try:
|
||||
status = dogtag.ca_status()
|
||||
if status == 'running':
|
||||
return True
|
||||
elif status == 'starting' and wait:
|
||||
# Exception is raised if status is 'starting' even after wait
|
||||
self.wait_until_running()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug(
|
||||
'Failed to check CA status: %s', e
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
# Function that constructs proper Red Hat OS family-specific server classes for
|
||||
# services of specified name
|
||||
|
||||
def redhat_service_class_factory(name, api=None):
|
||||
if name == 'dirsrv':
|
||||
return RedHatDirectoryService(name, api)
|
||||
if name == 'ipa':
|
||||
return RedHatIPAService(name, api)
|
||||
if name in ('pki-tomcatd', 'pki_tomcatd'):
|
||||
return RedHatCAService(name, api)
|
||||
return RedHatService(name, api)
|
||||
|
||||
|
||||
# Magicdict containing RedHatService instances.
|
||||
|
||||
class RedHatServices(base_services.KnownServices):
|
||||
def __init__(self):
|
||||
# pylint: disable=ipa-forbidden-import
|
||||
import ipalib # FixMe: break import cycle
|
||||
# pylint: enable=ipa-forbidden-import
|
||||
services = dict()
|
||||
for s in base_services.wellknownservices:
|
||||
services[s] = self.service_class_factory(s, ipalib.api)
|
||||
# Call base class constructor. This will lock services to read-only
|
||||
super(RedHatServices, self).__init__(services)
|
||||
|
||||
def service_class_factory(self, name, api=None):
|
||||
return redhat_service_class_factory(name, api)
|
||||
|
||||
# Objects below are expected to be exported by platform module
|
||||
|
||||
timedate_services = base_services.timedate_services
|
||||
service = redhat_service_class_factory
|
||||
knownservices = RedHatServices()
|
||||
524
ipaplatform/redhat/tasks.py
Normal file
524
ipaplatform/redhat/tasks.py
Normal file
@@ -0,0 +1,524 @@
|
||||
# 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.
|
||||
'''
|
||||
from __future__ import print_function
|
||||
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import traceback
|
||||
import errno
|
||||
|
||||
from ctypes.util import find_library
|
||||
from functools import total_ordering
|
||||
from subprocess import CalledProcessError
|
||||
|
||||
from cffi import FFI
|
||||
from pyasn1.error import PyAsn1Error
|
||||
from six.moves import urllib
|
||||
|
||||
from ipapython import ipautil
|
||||
import ipapython.errors
|
||||
|
||||
from ipaplatform.constants import constants
|
||||
from ipaplatform.paths import paths
|
||||
from ipaplatform.redhat.authconfig import RedHatAuthConfig
|
||||
from ipaplatform.base.tasks import BaseTaskNamespace
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_ffi = FFI()
|
||||
_ffi.cdef("""
|
||||
int rpmvercmp (const char *a, const char *b);
|
||||
""")
|
||||
|
||||
# use ctypes loader to get correct librpm.so library version according to
|
||||
# https://cffi.readthedocs.org/en/latest/overview.html#id8
|
||||
_librpm = _ffi.dlopen(find_library("rpm"))
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
@total_ordering
|
||||
class IPAVersion(object):
|
||||
|
||||
def __init__(self, version):
|
||||
self._version = version
|
||||
self._bytes = version.encode('utf-8')
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self._version
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, IPAVersion):
|
||||
return NotImplemented
|
||||
return _librpm.rpmvercmp(self._bytes, other._bytes) == 0
|
||||
|
||||
def __lt__(self, other):
|
||||
if not isinstance(other, IPAVersion):
|
||||
return NotImplemented
|
||||
return _librpm.rpmvercmp(self._bytes, other._bytes) < 0
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._version)
|
||||
|
||||
|
||||
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 check_ipv6_stack_enabled(self):
|
||||
"""Checks whether IPv6 kernel module is loaded.
|
||||
|
||||
Function checks if /proc/net/if_inet6 is present. If IPv6 stack is
|
||||
enabled, it exists and contains the interfaces configuration.
|
||||
|
||||
:raises: RuntimeError when IPv6 stack is disabled
|
||||
"""
|
||||
if not os.path.exists(paths.IF_INET6):
|
||||
raise RuntimeError(
|
||||
"IPv6 stack has to be enabled in the kernel and some "
|
||||
"interface has to have ::1 address assigned. Typically "
|
||||
"this is 'lo' interface. If you do not wish to use IPv6 "
|
||||
"globally, disable it on the specific interfaces in "
|
||||
"sysctl.conf except 'lo' interface.")
|
||||
|
||||
try:
|
||||
localhost6 = ipautil.CheckedIPAddress('::1', allow_loopback=True)
|
||||
if localhost6.get_matching_interface() is None:
|
||||
raise ValueError("no interface for ::1 address found")
|
||||
except ValueError:
|
||||
raise RuntimeError(
|
||||
"IPv6 stack is enabled in the kernel but there is no "
|
||||
"interface that has ::1 address assigned. Add ::1 address "
|
||||
"resolution to 'lo' interface. You might need to enable IPv6 "
|
||||
"on the interface 'lo' in sysctl.conf.")
|
||||
|
||||
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 backup_auth_configuration(self, path):
|
||||
auth_config = RedHatAuthConfig()
|
||||
auth_config.backup(path)
|
||||
|
||||
def restore_auth_configuration(self, path):
|
||||
auth_config = RedHatAuthConfig()
|
||||
auth_config.restore(path)
|
||||
|
||||
def reload_systemwide_ca_store(self):
|
||||
try:
|
||||
ipautil.run([paths.UPDATE_CA_TRUST])
|
||||
except CalledProcessError as e:
|
||||
logger.error(
|
||||
"Could not update systemwide CA trust database: %s", e)
|
||||
return False
|
||||
else:
|
||||
logger.info("Systemwide CA database updated.")
|
||||
return True
|
||||
|
||||
def insert_ca_certs_into_systemwide_ca_store(self, ca_certs):
|
||||
# pylint: disable=ipa-forbidden-import
|
||||
from ipalib import x509 # FixMe: break import cycle
|
||||
from ipalib.errors import CertificateError
|
||||
# pylint: enable=ipa-forbidden-import
|
||||
|
||||
new_cacert_path = paths.SYSTEMWIDE_IPA_CA_CRT
|
||||
|
||||
if os.path.exists(new_cacert_path):
|
||||
try:
|
||||
os.remove(new_cacert_path)
|
||||
except OSError as e:
|
||||
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 as e:
|
||||
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 = cert.subject_bytes
|
||||
issuer = cert.issuer_bytes
|
||||
serial_number = cert.serial_number_bytes
|
||||
public_key_info = cert.public_key_info_bytes
|
||||
except (PyAsn1Error, ValueError, CertificateError) as e:
|
||||
logger.warning(
|
||||
"Failed to decode certificate \"%s\": %s", nickname, e)
|
||||
continue
|
||||
|
||||
label = urllib.parse.quote(nickname)
|
||||
subject = urllib.parse.quote(subject)
|
||||
issuer = urllib.parse.quote(issuer)
|
||||
serial_number = urllib.parse.quote(serial_number)
|
||||
public_key_info = urllib.parse.quote(public_key_info)
|
||||
|
||||
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 += "{pem}\n\n".format(
|
||||
pem=cert.public_bytes(x509.Encoding.PEM).decode('ascii'))
|
||||
f.write(obj)
|
||||
|
||||
if (cert.extended_key_usage is not None and
|
||||
public_key_info not in has_eku):
|
||||
try:
|
||||
ext_key_usage = cert.extended_key_usage_bytes
|
||||
except PyAsn1Error as e:
|
||||
logger.warning(
|
||||
"Failed to encode extended key usage for \"%s\": %s",
|
||||
nickname, e)
|
||||
continue
|
||||
value = urllib.parse.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
|
||||
if not self.reload_systemwide_ca_store():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def remove_ca_certs_from_systemwide_ca_store(self):
|
||||
result = True
|
||||
update = False
|
||||
|
||||
# Remove CA cert from systemwide store
|
||||
for new_cacert_path in (paths.IPA_P11_KIT,
|
||||
paths.SYSTEMWIDE_IPA_CA_CRT):
|
||||
if not os.path.exists(new_cacert_path):
|
||||
continue
|
||||
try:
|
||||
os.remove(new_cacert_path)
|
||||
except OSError as e:
|
||||
logger.error(
|
||||
"Could not remove %s: %s", new_cacert_path, e)
|
||||
result = False
|
||||
else:
|
||||
update = True
|
||||
|
||||
if update:
|
||||
if not self.reload_systemwide_ca_store():
|
||||
return False
|
||||
|
||||
return result
|
||||
|
||||
def backup_hostname(self, fstore, statestore):
|
||||
filepath = paths.ETC_HOSTNAME
|
||||
if os.path.exists(filepath):
|
||||
fstore.backup_file(filepath)
|
||||
|
||||
# store old hostname
|
||||
old_hostname = socket.gethostname()
|
||||
statestore.backup_state('network', 'hostname', old_hostname)
|
||||
|
||||
def restore_hostname(self, fstore, statestore):
|
||||
old_hostname = statestore.get_state('network', 'hostname')
|
||||
|
||||
if old_hostname is not None:
|
||||
try:
|
||||
self.set_hostname(old_hostname)
|
||||
except ipautil.CalledProcessError as e:
|
||||
logger.debug("%s", traceback.format_exc())
|
||||
logger.error(
|
||||
"Failed to restore this machine hostname to %s (%s).",
|
||||
old_hostname, e
|
||||
)
|
||||
|
||||
filepath = paths.ETC_HOSTNAME
|
||||
if fstore.has_file(filepath):
|
||||
fstore.restore_file(filepath)
|
||||
|
||||
|
||||
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.items()])
|
||||
|
||||
return args
|
||||
|
||||
if not selinux_enabled():
|
||||
return False
|
||||
|
||||
updated_vars = {}
|
||||
failed_vars = {}
|
||||
for setting, state in required_settings.items():
|
||||
if state is None:
|
||||
continue
|
||||
try:
|
||||
result = ipautil.run(
|
||||
[paths.GETSEBOOL, setting],
|
||||
capture_output=True
|
||||
)
|
||||
original_state = result.output.split()[2]
|
||||
if backup_func is not None:
|
||||
backup_func(setting, original_state)
|
||||
|
||||
if original_state != state:
|
||||
updated_vars[setting] = state
|
||||
except ipautil.CalledProcessError as e:
|
||||
logger.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
|
||||
|
||||
def parse_ipa_version(self, version):
|
||||
"""
|
||||
:param version: textual version
|
||||
:return: object implementing proper __cmp__ method for version compare
|
||||
"""
|
||||
return IPAVersion(version)
|
||||
|
||||
def configure_httpd_service_ipa_conf(self):
|
||||
"""Create systemd config for httpd service to work with IPA
|
||||
"""
|
||||
if not os.path.exists(paths.SYSTEMD_SYSTEM_HTTPD_D_DIR):
|
||||
os.mkdir(paths.SYSTEMD_SYSTEM_HTTPD_D_DIR, 0o755)
|
||||
|
||||
ipautil.copy_template_file(
|
||||
os.path.join(paths.USR_SHARE_IPA_DIR, 'ipa-httpd.conf.template'),
|
||||
paths.SYSTEMD_SYSTEM_HTTPD_IPA_CONF,
|
||||
dict(
|
||||
KDCPROXY_CONFIG=paths.KDCPROXY_CONFIG,
|
||||
IPA_HTTPD_KDCPROXY=paths.IPA_HTTPD_KDCPROXY,
|
||||
KRB5CC_HTTPD=paths.KRB5CC_HTTPD,
|
||||
)
|
||||
)
|
||||
|
||||
os.chmod(paths.SYSTEMD_SYSTEM_HTTPD_IPA_CONF, 0o644)
|
||||
self.restore_context(paths.SYSTEMD_SYSTEM_HTTPD_IPA_CONF)
|
||||
|
||||
ipautil.run([paths.SYSTEMCTL, "--system", "daemon-reload"],
|
||||
raiseonerr=False)
|
||||
|
||||
def configure_http_gssproxy_conf(self, ipaapi_user):
|
||||
ipautil.copy_template_file(
|
||||
os.path.join(paths.USR_SHARE_IPA_DIR, 'gssproxy.conf.template'),
|
||||
paths.GSSPROXY_CONF,
|
||||
dict(
|
||||
HTTP_KEYTAB=paths.HTTP_KEYTAB,
|
||||
HTTP_CCACHE=paths.HTTP_CCACHE,
|
||||
HTTPD_USER=constants.HTTPD_USER,
|
||||
IPAAPI_USER=ipaapi_user,
|
||||
)
|
||||
)
|
||||
|
||||
os.chmod(paths.GSSPROXY_CONF, 0o600)
|
||||
self.restore_context(paths.GSSPROXY_CONF)
|
||||
|
||||
def remove_httpd_service_ipa_conf(self):
|
||||
"""Remove systemd config for httpd service of IPA"""
|
||||
try:
|
||||
os.unlink(paths.SYSTEMD_SYSTEM_HTTPD_IPA_CONF)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
logger.debug(
|
||||
'Trying to remove %s but file does not exist',
|
||||
paths.SYSTEMD_SYSTEM_HTTPD_IPA_CONF
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
'Error removing %s: %s',
|
||||
paths.SYSTEMD_SYSTEM_HTTPD_IPA_CONF, e
|
||||
)
|
||||
return
|
||||
|
||||
ipautil.run([paths.SYSTEMCTL, "--system", "daemon-reload"],
|
||||
raiseonerr=False)
|
||||
|
||||
def set_hostname(self, hostname):
|
||||
ipautil.run([paths.BIN_HOSTNAMECTL, 'set-hostname', hostname])
|
||||
|
||||
def is_fips_enabled(self):
|
||||
"""
|
||||
Checks whether this host is FIPS-enabled.
|
||||
|
||||
Returns a boolean indicating if the host is FIPS-enabled, i.e. if the
|
||||
file /proc/sys/crypto/fips_enabled contains a non-0 value. Otherwise,
|
||||
or if the file /proc/sys/crypto/fips_enabled does not exist,
|
||||
the function returns False.
|
||||
"""
|
||||
try:
|
||||
with open(paths.PROC_FIPS_ENABLED, 'r') as f:
|
||||
if f.read().strip() != '0':
|
||||
return True
|
||||
except IOError:
|
||||
# Consider that the host is not fips-enabled if the file does not
|
||||
# exist
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
tasks = RedHatTaskNamespace()
|
||||
Reference in New Issue
Block a user