freeipa/ipaplatform/redhat/authconfig.py
Florence Blanc-Renaud 571b6b81c3 Installer: add --subid option to select the sssd profile with-subid
Add the --subid option to client, server and replica installers.
This option allows to configure authselect with the sssd
profile + with-subid feature, in order to have SSSD setup as
a datasource for subid in /etc/nsswitch.conf.

The default behavior remains unchanged: without the option,
/etc/nsswitch.conf keeps the line subid: files

Fixes: https://pagure.io/freeipa/issue/9159
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
2022-05-25 08:11:39 +03:00

380 lines
13 KiB
Python

# 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 __future__ import absolute_import
import logging
import six
import abc
from ipaplatform.paths import paths
from ipapython import ipautil
from ipapython.admintool import ScriptError
import os
import time
FILES_TO_NOT_BACKUP = ['passwd', 'group', 'shadow', 'gshadow']
logger = logging.getLogger(__name__)
def get_auth_tool():
return RedHatAuthSelect()
@six.add_metaclass(abc.ABCMeta)
class RedHatAuthToolBase:
@abc.abstractmethod
def configure(self, sssd, mkhomedir, statestore, sudo=True):
pass
@abc.abstractmethod
def unconfigure(self, fstore, statestore,
was_sssd_installed,
was_sssd_configured):
pass
@abc.abstractmethod
def backup(self, path):
"""
Backup the system authentication resources configuration
:param path: directory where the backup will be stored
"""
@abc.abstractmethod
def restore(self, path):
"""
Restore the system authentication resources configuration from a backup
:param path: directory where the backup is stored
"""
@abc.abstractmethod
def set_nisdomain(self, nisdomain):
pass
class RedHatAuthSelect(RedHatAuthToolBase):
def _get_authselect_current_output(self):
try:
current = ipautil.run(
[paths.AUTHSELECT, "current", "--raw"])
except ipautil.CalledProcessError:
logger.debug("Current configuration not managed by authselect")
return None
return current.raw_output.decode()
def _parse_authselect_output(self, output_text=None):
"""
Parses the output_text to extract the profile and options.
When no text is provided, runs the 'authselect profile' command to
generate the text to be parsed.
"""
if output_text is None:
output_text = self._get_authselect_current_output()
if output_text is None:
return None
output_text = output_text.strip()
if not output_text:
return None
output_items = output_text.split(' ')
profile = output_items[0]
features = output_items[1:]
return profile, features
def configure(self, sssd, mkhomedir, statestore, sudo=True,
subid=False):
# In the statestore, the following keys are used for the
# 'authselect' module:
# Old method:
# profile: name of the profile configured pre-installation
# features_list: list of features configured pre-installation
# mkhomedir: True if installation was called with --mkhomedir
# profile and features_list are used when reverting to the
# pre-install state
# New method:
# backup: name of the authselect backup
backup_name = "pre_ipaclient_{}".format(time.strftime("%Y%m%d%H%M%S"))
statestore.backup_state('authselect', 'backup', backup_name)
cmd = [paths.AUTHSELECT, "select", "sssd"]
if mkhomedir:
cmd.append("with-mkhomedir")
statestore.backup_state('authselect', 'mkhomedir', True)
if sudo:
cmd.append("with-sudo")
if subid:
cmd.append("with-subid")
cmd.append("--force")
cmd.append("--backup={}".format(backup_name))
ipautil.run(cmd)
def unconfigure(
self, fstore, statestore, was_sssd_installed, was_sssd_configured
):
# If the installation failed before doing the authselect part
# nothing to do here
complete = statestore.get_state('installation', 'complete')
if complete is not None and not complete and \
not statestore.has_state('authselect'):
return
if not statestore.has_state('authselect'):
logger.warning(
"WARNING: Unable to revert to the pre-installation state "
"('authconfig' tool has been deprecated in favor of "
"'authselect'). The default sssd profile will be used "
"instead.")
# Build the equivalent command line that will be displayed
# to the user
# This is a copy-paste of unconfigure code, except that it
# creates the command line but does not actually call it
authconfig = RedHatAuthConfig()
authconfig.prepare_unconfigure(
fstore, statestore, was_sssd_installed, was_sssd_configured)
args = authconfig.build_args()
logger.warning(
"The authconfig arguments would have been: authconfig %s",
" ".join(args))
profile = 'sssd'
features = []
else:
profile = statestore.restore_state('authselect', 'profile')
if not profile:
profile = 'sssd'
features_state = statestore.restore_state(
'authselect', 'features_list'
)
statestore.delete_state('authselect', 'mkhomedir')
# https://pagure.io/freeipa/issue/8054
if fstore.has_file(paths.NSSWITCH_CONF):
logger.info("Restoring user-nsswitch.conf")
fstore.restore_file(paths.NSSWITCH_CONF)
# only non-empty features, https://pagure.io/freeipa/issue/7776
if features_state is not None:
features = [
f.strip() for f in features_state.split(' ') if f.strip()
]
else:
features = []
backup = statestore.restore_state('authselect', 'backup')
if backup:
cmd = [paths.AUTHSELECT, "backup-restore", backup]
ipautil.run(cmd)
else:
cmd = [paths.AUTHSELECT, "select", profile]
cmd.extend(features)
cmd.append("--force")
ipautil.run(cmd)
def backup(self, path):
current = self._get_authselect_current_output()
if current is None:
return
if not os.path.exists(path):
os.makedirs(path)
with open(os.path.join(path, "authselect.backup"), 'w') as f:
f.write(current)
def restore(self, path):
with open(os.path.join(path, "authselect.backup"), "r") as f:
cfg = self._parse_authselect_output(f.read())
if cfg:
profile = cfg[0]
cmd = [paths.AUTHSELECT, "select", profile]
cmd.extend(cfg[1])
cmd.append("--force")
ipautil.run(cmd)
def set_nisdomain(self, nisdomain):
try:
with open(paths.SYSCONF_NETWORK, 'r') as f:
content = [
line for line in f
if not line.strip().upper().startswith('NISDOMAIN')
]
except IOError:
content = []
content.append("NISDOMAIN={}\n".format(nisdomain))
with open(paths.SYSCONF_NETWORK, 'w') as f:
f.writelines(content)
# RedHatAuthConfig concrete class definition to be removed later
# when agreed on exact path to migrate to authselect
class RedHatAuthConfig(RedHatAuthToolBase):
"""
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 configure(self, sssd, mkhomedir, statestore, sudo=True):
if sssd:
statestore.backup_state('authconfig', 'sssd', True)
statestore.backup_state('authconfig', 'sssdauth', True)
self.enable("sssd")
self.enable("sssdauth")
else:
statestore.backup_state('authconfig', 'ldap', True)
self.enable("ldap")
self.enable("forcelegacy")
statestore.backup_state('authconfig', 'krb5', True)
self.enable("krb5")
self.add_option("nostart")
if mkhomedir:
statestore.backup_state('authconfig', 'mkhomedir', True)
self.enable("mkhomedir")
self.execute()
self.reset()
def prepare_unconfigure(self, fstore, statestore,
was_sssd_installed,
was_sssd_configured):
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':
self.disable(conf)
else:
# There was no authconfig status store
# It means the code was upgraded after original install
# Fall back to old logic
self.disable("ldap")
self.disable("krb5")
if not(was_sssd_installed and was_sssd_configured):
# Only disable sssdauth. Disabling sssd would cause issues
# with its later uses.
self.disable("sssdauth")
self.disable("mkhomedir")
def unconfigure(self, fstore, statestore,
was_sssd_installed,
was_sssd_configured):
self.prepare_unconfigure(
fstore, statestore, was_sssd_installed, was_sssd_configured)
self.execute()
self.reset()
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")
def set_nisdomain(self, nisdomain):
# Let authconfig setup the permanent configuration
self.reset()
self.add_parameter("nisdomain", nisdomain)
self.execute()