mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Migration from authconfig to authselect
The authconfig tool is deprecated and replaced by authselect. Migrate FreeIPA in order to use the new tool as described in the design page https://www.freeipa.org/page/V4/Authselect_migration Fixes: https://pagure.io/freeipa/issue/7377 Reviewed-By: Alexander Koksharov <akokshar@redhat.com> Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com> Reviewed-By: Christian Heimes <cheimes@redhat.com>
This commit is contained in:
parent
fc371b651e
commit
1df1786b06
@ -597,7 +597,7 @@ Requires: python2-sssdconfig
|
||||
Requires: cyrus-sasl-gssapi%{?_isa}
|
||||
Requires: chrony
|
||||
Requires: krb5-workstation >= %{krb5_version}
|
||||
Requires: authconfig
|
||||
Requires: authselect >= 0.4-2
|
||||
Requires: curl
|
||||
# NIS domain name config: /usr/lib/systemd/system/*-domainname.service
|
||||
Requires: initscripts
|
||||
|
@ -2036,6 +2036,22 @@ def install_check(options):
|
||||
"Invalid hostname, '{}' must not be used.".format(hostname),
|
||||
rval=CLIENT_INSTALL_ERROR)
|
||||
|
||||
# --no-sssd is not supported any more for rhel-based distros
|
||||
if not tasks.is_nosssd_supported() and not options.sssd:
|
||||
raise ScriptError(
|
||||
"Option '--no-sssd' is incompatible with the 'authselect' tool "
|
||||
"provided by this distribution for configuring system "
|
||||
"authentication resources",
|
||||
rval=CLIENT_INSTALL_ERROR)
|
||||
|
||||
# --noac is not supported any more for rhel-based distros
|
||||
if not tasks.is_nosssd_supported() and options.no_ac:
|
||||
raise ScriptError(
|
||||
"Option '--noac' is incompatible with the 'authselect' tool "
|
||||
"provided by this distribution for configuring system "
|
||||
"authentication resources",
|
||||
rval=CLIENT_INSTALL_ERROR)
|
||||
|
||||
# when installing with '--no-sssd' option, check whether nss-ldap is
|
||||
# installed
|
||||
if not options.sssd:
|
||||
@ -2899,9 +2915,11 @@ def _install(options):
|
||||
|
||||
if not options.no_ac:
|
||||
# Modify nsswitch/pam stack
|
||||
tasks.modify_nsswitch_pam_stack(sssd=options.sssd,
|
||||
mkhomedir=options.mkhomedir,
|
||||
statestore=statestore)
|
||||
tasks.modify_nsswitch_pam_stack(
|
||||
sssd=options.sssd,
|
||||
mkhomedir=options.mkhomedir,
|
||||
statestore=statestore
|
||||
)
|
||||
|
||||
logger.info("%s enabled", "SSSD" if options.sssd else "LDAP")
|
||||
|
||||
|
@ -376,6 +376,8 @@ class BasePathNamespace(object):
|
||||
IF_INET6 = '/proc/net/if_inet6'
|
||||
WSGI_PREFIX_DIR = "/run/httpd/wsgi"
|
||||
AUTHCONFIG = None
|
||||
AUTHSELECT = None
|
||||
SYSCONF_NETWORK = None
|
||||
IPA_SERVER_UPGRADE = '/usr/sbin/ipa-server-upgrade'
|
||||
KEYCTL = '/usr/bin/keyctl'
|
||||
GETENT = '/usr/bin/getent'
|
||||
|
@ -135,7 +135,7 @@ class BaseTaskNamespace(object):
|
||||
|
||||
def modify_nsswitch_pam_stack(self, sssd, mkhomedir, statestore):
|
||||
"""
|
||||
If sssd flag is true, configure pam and nsswtich so that SSSD is used
|
||||
If sssd flag is true, configure pam and nsswitch so that SSSD is used
|
||||
for retrieving user information and authentication.
|
||||
|
||||
Otherwise, configure pam and nsswitch to leverage pure LDAP.
|
||||
@ -150,6 +150,13 @@ class BaseTaskNamespace(object):
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def is_nosssd_supported(self):
|
||||
"""
|
||||
Check if the flag --no-sssd is supported for client install.
|
||||
"""
|
||||
|
||||
return True
|
||||
|
||||
def backup_auth_configuration(self, path):
|
||||
"""
|
||||
Create backup of access control configuration.
|
||||
@ -165,6 +172,12 @@ class BaseTaskNamespace(object):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def migrate_auth_configuration(self, statestore):
|
||||
"""
|
||||
Migrate pam stack configuration to authselect.
|
||||
"""
|
||||
return
|
||||
|
||||
def set_selinux_booleans(self, required_settings, backup_func=None):
|
||||
"""Set the specified SELinux booleans
|
||||
|
||||
|
@ -19,6 +19,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
import logging
|
||||
import six
|
||||
import abc
|
||||
import re
|
||||
|
||||
from ipaplatform.paths import paths
|
||||
from ipapython import ipautil
|
||||
@ -27,8 +31,192 @@ import os
|
||||
|
||||
FILES_TO_NOT_BACKUP = ['passwd', 'group', 'shadow', 'gshadow']
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class RedHatAuthConfig(object):
|
||||
|
||||
def get_auth_tool():
|
||||
return RedHatAuthSelect()
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class RedHatAuthToolBase(object):
|
||||
|
||||
@abc.abstractmethod
|
||||
def configure(self, sssd, mkhomedir, statestore):
|
||||
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
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def restore(self, path):
|
||||
"""
|
||||
Restore the system authentication resources configuration from a backup
|
||||
:param path: directory where the backup is stored
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_nisdomain(self, nisdomain):
|
||||
pass
|
||||
|
||||
|
||||
class RedHatAuthSelect(RedHatAuthToolBase):
|
||||
|
||||
def _get_authselect_current_output(self):
|
||||
try:
|
||||
current = ipautil.run(
|
||||
[paths.AUTHSELECT, "current"], env={"LC_ALL": "C.UTF8"})
|
||||
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
|
||||
|
||||
cfg_params = re.findall(
|
||||
r"\s*Profile ID:\s*(\S+)\s*\n\s*Enabled features:\s*(.*)",
|
||||
output_text,
|
||||
re.DOTALL
|
||||
)
|
||||
|
||||
profile = cfg_params[0][0]
|
||||
|
||||
if not profile:
|
||||
return None
|
||||
|
||||
features = re.findall(r"-\s*(\S+)", cfg_params[0][1], re.DOTALL)
|
||||
|
||||
return profile, features
|
||||
|
||||
def configure(self, sssd, mkhomedir, statestore):
|
||||
# In the statestore, the following keys are used for the
|
||||
# 'authselect' module:
|
||||
# profile: name of the profile configured pre-installation
|
||||
# features_list: lsit 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
|
||||
cfg = self._parse_authselect_output()
|
||||
if cfg:
|
||||
statestore.backup_state('authselect', 'profile', cfg[0])
|
||||
statestore.backup_state(
|
||||
'authselect', 'features_list', " ".join(cfg[1]))
|
||||
else:
|
||||
# cfg = None means that the current conf is not managed by
|
||||
# authselect but by authconfig.
|
||||
# As we are using authselect to configure the host,
|
||||
# it will not be possible to revert to a custom authconfig
|
||||
# configuration later (during uninstall)
|
||||
# Best thing to do will be to use sssd profile at this time
|
||||
logger.warning(
|
||||
"WARNING: The configuration pre-client installation is not "
|
||||
"managed by authselect and cannot be backed up. "
|
||||
"Uninstallation may not be able to revert to the original "
|
||||
"state.")
|
||||
|
||||
cmd = [paths.AUTHSELECT, "select", "sssd"]
|
||||
if mkhomedir:
|
||||
cmd.append("with-mkhomedir")
|
||||
statestore.backup_state('authselect', 'mkhomedir', True)
|
||||
cmd.append("--force")
|
||||
|
||||
ipautil.run(cmd)
|
||||
|
||||
def unconfigure(
|
||||
self, fstore, statestore, was_sssd_installed, was_sssd_configured
|
||||
):
|
||||
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')
|
||||
features = statestore.restore_state('authselect', 'features_list')
|
||||
statestore.delete_state('authselect', 'mkhomedir')
|
||||
|
||||
cmd = [paths.AUTHSELECT, "select", profile, features, "--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,
|
||||
" ".join(cfg[1]), "--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
|
||||
@ -95,6 +283,60 @@ class RedHatAuthConfig(object):
|
||||
except ipautil.CalledProcessError:
|
||||
raise ScriptError("Failed to execute authconfig command")
|
||||
|
||||
def configure(self, sssd, mkhomedir, statestore):
|
||||
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])
|
||||
@ -116,3 +358,9 @@ class RedHatAuthConfig(object):
|
||||
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()
|
||||
|
@ -37,6 +37,8 @@ class RedHatPathNamespace(BasePathNamespace):
|
||||
PAM_KRB5_SO = BasePathNamespace.PAM_KRB5_SO_64
|
||||
BIND_LDAP_SO = BasePathNamespace.BIND_LDAP_SO_64
|
||||
AUTHCONFIG = '/usr/sbin/authconfig'
|
||||
AUTHSELECT = '/usr/bin/authselect'
|
||||
SYSCONF_NETWORK = '/etc/sysconfig/network'
|
||||
|
||||
|
||||
paths = RedHatPathNamespace()
|
||||
|
@ -45,7 +45,7 @@ import ipapython.errors
|
||||
|
||||
from ipaplatform.constants import constants
|
||||
from ipaplatform.paths import paths
|
||||
from ipaplatform.redhat.authconfig import RedHatAuthConfig
|
||||
from ipaplatform.redhat.authconfig import get_auth_tool
|
||||
from ipaplatform.base.tasks import BaseTaskNamespace
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -186,70 +186,67 @@ class RedHatTaskNamespace(BaseTaskNamespace):
|
||||
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()
|
||||
auth_config = get_auth_tool()
|
||||
auth_config.unconfigure(
|
||||
fstore, statestore, was_sssd_installed, was_sssd_configured
|
||||
)
|
||||
|
||||
def set_nisdomain(self, nisdomain):
|
||||
# Let authconfig setup the permanent configuration
|
||||
auth_config = RedHatAuthConfig()
|
||||
auth_config.add_parameter("nisdomain", nisdomain)
|
||||
auth_config.execute()
|
||||
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)
|
||||
|
||||
def modify_nsswitch_pam_stack(self, sssd, mkhomedir, statestore):
|
||||
auth_config = RedHatAuthConfig()
|
||||
auth_config = get_auth_tool()
|
||||
auth_config.configure(sssd, mkhomedir, statestore)
|
||||
|
||||
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 is_nosssd_supported(self):
|
||||
# The flag --no-sssd is not supported any more for rhel-based distros
|
||||
return False
|
||||
|
||||
def backup_auth_configuration(self, path):
|
||||
auth_config = RedHatAuthConfig()
|
||||
auth_config = get_auth_tool()
|
||||
auth_config.backup(path)
|
||||
|
||||
def restore_auth_configuration(self, path):
|
||||
auth_config = RedHatAuthConfig()
|
||||
auth_config = get_auth_tool()
|
||||
auth_config.restore(path)
|
||||
|
||||
def migrate_auth_configuration(self, statestore):
|
||||
"""
|
||||
Migrate the pam stack configuration from authconfig to an authselect
|
||||
profile.
|
||||
"""
|
||||
# Check if mkhomedir was enabled during installation
|
||||
mkhomedir = statestore.get_state('authconfig', 'mkhomedir')
|
||||
|
||||
# Force authselect 'sssd' profile
|
||||
authselect_cmd = [paths.AUTHSELECT, "select", "sssd"]
|
||||
if mkhomedir:
|
||||
authselect_cmd.append("with-mkhomedir")
|
||||
authselect_cmd.append("--force")
|
||||
ipautil.run(authselect_cmd)
|
||||
|
||||
# Remove all remaining keys from the authconfig module
|
||||
for conf in ('ldap', 'krb5', 'sssd', 'sssdauth', 'mkhomedir'):
|
||||
statestore.restore_state('authconfig', conf)
|
||||
|
||||
# Create new authselect module in the statestore
|
||||
statestore.backup_state('authselect', 'profile', 'sssd')
|
||||
statestore.backup_state(
|
||||
'authselect', 'features_list', '')
|
||||
statestore.backup_state('authselect', 'mkhomedir', bool(mkhomedir))
|
||||
|
||||
def reload_systemwide_ca_store(self):
|
||||
try:
|
||||
ipautil.run([paths.UPDATE_CA_TRUST])
|
||||
@ -408,7 +405,6 @@ class RedHatTaskNamespace(BaseTaskNamespace):
|
||||
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"]
|
||||
|
@ -1654,6 +1654,21 @@ def update_replica_config(db_suffix):
|
||||
api.Backend.ldap2.update_entry(entry)
|
||||
|
||||
|
||||
def migrate_to_authselect():
|
||||
logger.info('[Migrating to authselect profile]')
|
||||
if sysupgrade.get_upgrade_state('authcfg', 'migrated_to_authselect'):
|
||||
logger.info("Already migrated to authselect profile")
|
||||
return
|
||||
|
||||
statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE)
|
||||
try:
|
||||
tasks.migrate_auth_configuration(statestore)
|
||||
except ipautil.CalledProcessError as e:
|
||||
raise RuntimeError(
|
||||
"Failed to migrate to authselect profile: %s" % e, 1)
|
||||
sysupgrade.set_upgrade_state('authcfg', 'migrated_to_authselect', True)
|
||||
|
||||
|
||||
def upgrade_configuration():
|
||||
"""
|
||||
Execute configuration upgrade of the IPA services
|
||||
@ -1933,6 +1948,7 @@ def upgrade_configuration():
|
||||
ca.setup_lightweight_ca_key_retrieval()
|
||||
cainstance.ensure_ipa_authority_entry()
|
||||
|
||||
migrate_to_authselect()
|
||||
set_sssd_domain_option('ipa_server_mode', 'True')
|
||||
set_sssd_domain_option('ipa_server', api.env.host)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user