Import Upstream version 4.12.4

This commit is contained in:
geos_one
2025-08-12 22:28:56 +02:00
parent 03a8170b15
commit 9181ee2487
1629 changed files with 874094 additions and 554378 deletions

View File

@@ -20,13 +20,11 @@
from __future__ import absolute_import, print_function
import logging
import optparse # pylint: disable=deprecated-module
import os
import shutil
import sys
import tempfile
import time
import pwd
import ldif
import itertools
@@ -35,13 +33,13 @@ import six
from ipaclient.install.client import update_ipa_nssdb
from ipalib import api, errors
from ipalib.constants import FQDN
from ipapython import version, ipautil
from ipapython import version, ipautil, config
from ipapython.ipautil import run, user_input
from ipapython import admintool, certdb
from ipapython.dn import DN
from ipaserver.install.replication import (wait_for_task, ReplicationManager,
get_cs_replication_manager)
from ipaserver.install import installutils
from ipaserver.install import installutils, ldapupdate
from ipaserver.install import dsinstance, httpinstance, cainstance, krbinstance
from ipaserver.masters import get_masters
from ipapython import ipaldap
@@ -51,6 +49,8 @@ from ipaplatform.tasks import tasks
from ipaplatform import services
from ipaplatform.paths import paths
from lib389.cli_ctl.dblib import run_dbscan
try:
from ipaserver.install import adtrustinstance
except ImportError:
@@ -66,6 +66,29 @@ else:
logger = logging.getLogger(__name__)
backends = [] # global to save running dbscan multiple times
def get_backends(db_dir):
"""Retrieve the set of backends directly from the current database"""
global backends
if backends:
return backends
output = run_dbscan(['-L', db_dir])
output = output.replace(db_dir + '/', '')
output = output.split('\n')
for line in output:
if '/' not in line:
continue
backends.append(line.split('/')[0].strip().lower())
backends = set(backends)
if 'changelog' in backends:
backends.remove('changelog')
return backends
def recursive_chown(path, uid, gid):
'''
@@ -162,11 +185,11 @@ class Restore(admintool.AdminTool):
super(Restore, cls).add_options(parser, debug_option=True)
parser.add_option(
"-p", "--password", dest="password",
"-p", "--password", dest="password", sensitive=True,
help="Directory Manager password")
parser.add_option(
"--gpg-keyring", dest="gpg_keyring",
help=optparse.SUPPRESS_HELP)
help=config.SUPPRESS_HELP)
parser.add_option(
"--data", dest="data_only", action="store_true",
default=False, help="Restore only the data")
@@ -236,6 +259,15 @@ class Restore(admintool.AdminTool):
raise admintool.ScriptError(
"Directory Manager password required")
def enable_server(self):
"""Make sure the current server is marked as enabled"""
if not api.Backend.ldap2.isconnected():
api.Backend.ldap2.connect()
try:
api.Command.server_state(api.env.host, state='enabled')
except errors.EmptyModlist:
pass
def run(self):
options = self.options
@@ -287,8 +319,9 @@ class Restore(admintool.AdminTool):
if options.backend:
for instance in self.instances:
db_dir = (paths.SLAPD_INSTANCE_DB_DIR_TEMPLATE %
(instance, options.backend))
if os.path.exists(db_dir):
(instance, ""))
backends = get_backends(db_dir)
if options.backend.lower() in backends:
break
else:
raise admintool.ScriptError(
@@ -296,15 +329,20 @@ class Restore(admintool.AdminTool):
self.backends = [options.backend]
missing_backends = []
for instance, backend in itertools.product(self.instances,
self.backends):
db_dir = (paths.SLAPD_INSTANCE_DB_DIR_TEMPLATE %
(instance, backend))
if os.path.exists(db_dir):
break
else:
(instance, ""))
backends = get_backends(db_dir)
if backend.lower() not in backends:
missing_backends.append(backend)
if missing_backends:
raise admintool.ScriptError(
"Cannot restore a data backup into an empty system")
"Cannot restore a data backup into an empty system. "
"Missing backend(s) %s" % ', '.join(missing_backends)
)
logger.info("Performing %s restore from %s backup",
restore_type, self.backup_type)
@@ -346,18 +384,14 @@ class Restore(admintool.AdminTool):
)
)
pent = pwd.getpwnam(constants.DS_USER)
# Temporary directory for decrypting files before restoring
self.top_dir = tempfile.mkdtemp("ipa")
os.chown(self.top_dir, pent.pw_uid, pent.pw_gid)
constants.DS_USER.chown(self.top_dir)
os.chmod(self.top_dir, 0o750)
self.dir = os.path.join(self.top_dir, "ipa")
os.mkdir(self.dir)
os.chmod(self.dir, 0o750)
os.chown(self.dir, pent.pw_uid, pent.pw_gid)
cwd = os.getcwd()
constants.DS_USER.chown(self.dir)
logger.info("Temporary setting umask to 022")
old_umask = os.umask(0o022)
@@ -377,6 +411,10 @@ class Restore(admintool.AdminTool):
ldiffile = os.path.join(self.dir, '%s-%s.ldif' % database)
if os.path.exists(ldiffile):
databases.append(database)
else:
logger.warning(
"LDIF file '%s-%s.ldif' not found in backup",
instance, backend)
if options.instance:
for instance, backend in databases:
@@ -472,6 +510,12 @@ class Restore(admintool.AdminTool):
oddjobd.enable()
oddjobd.start()
http.remove_httpd_ccaches()
# update autobind configuration in case uid/gid have changed
ld = ldapupdate.LDAPUpdate(api=api)
autobind_update = os.path.join(
paths.UPDATES_DIR, "49-autobind-services.update"
)
ld.update([autobind_update])
# have the daemons pick up their restored configs
tasks.systemd_daemon_reload()
# Restart IPA a final time.
@@ -482,11 +526,8 @@ class Restore(admintool.AdminTool):
result = run([paths.IPACTL, 'restart'], raiseonerr=False)
if result.returncode != 0:
logger.error('Restarting IPA failed: %s', result.error_log)
self.enable_server()
finally:
try:
os.chdir(cwd)
except Exception as e:
logger.error('Cannot change directory to %s: %s', cwd, e)
shutil.rmtree(self.top_dir)
logger.info("Restoring umask to %s", old_umask)
os.umask(old_umask)
@@ -596,10 +637,9 @@ class Restore(admintool.AdminTool):
srcldiffile = os.path.join(self.dir, ldifname)
if not os.path.exists(ldifdir):
pent = pwd.getpwnam(constants.DS_USER)
os.mkdir(ldifdir)
os.chmod(ldifdir, 0o770)
os.chown(ldifdir, pent.pw_uid, pent.pw_gid)
constants.DS_USER.chown(ldifdir)
ipautil.backup_file(ldiffile)
with open(ldiffile, 'w') as out_file:
@@ -609,8 +649,7 @@ class Restore(admintool.AdminTool):
ldif_parser.parse()
# Make sure the modified ldiffile is owned by DS_USER
pent = pwd.getpwnam(constants.DS_USER)
os.chown(ldiffile, pent.pw_uid, pent.pw_gid)
constants.DS_USER.chown(ldiffile)
if online:
conn = self.get_connection()
@@ -637,10 +676,10 @@ class Restore(admintool.AdminTool):
template_dir = paths.VAR_LOG_DIRSRV_INSTANCE_TEMPLATE % instance
try:
os.makedirs(template_dir)
except OSError as e:
except OSError:
pass
os.chown(template_dir, pent.pw_uid, pent.pw_gid)
constants.DS_USER.chown(template_dir)
os.chmod(template_dir, 0o770)
# Restore SELinux context of template_dir
@@ -708,7 +747,6 @@ class Restore(admintool.AdminTool):
if result.returncode != 0:
logger.critical("bak2db failed: %s", result.error_log)
def restore_default_conf(self):
'''
Restore paths.IPA_DEFAULT_CONF to temporary directory.
@@ -716,21 +754,18 @@ class Restore(admintool.AdminTool):
Primary purpose of this method is to get configuration for api
finalization when restoring ipa after uninstall.
'''
cwd = os.getcwd()
os.chdir(self.dir)
args = ['tar',
'--xattrs',
'--selinux',
'-xzf',
os.path.join(self.dir, 'files.tar'),
paths.IPA_DEFAULT_CONF[1:],
]
]
result = run(args, raiseonerr=False, cwd=self.dir)
result = run(args, raiseonerr=False)
if result.returncode != 0:
logger.critical('Restoring %s failed: %s',
paths.IPA_DEFAULT_CONF, result.error_log)
os.chdir(cwd)
def remove_old_files(self):
"""
@@ -770,25 +805,20 @@ class Restore(admintool.AdminTool):
databases.
'''
logger.info("Restoring files")
cwd = os.getcwd()
os.chdir('/')
args = ['tar',
'--xattrs',
'--selinux',
'-xzf',
os.path.join(self.dir, 'files.tar')
]
]
if nologs:
args.append('--exclude')
args.append('var/log')
result = run(args, raiseonerr=False)
result = run(args, cwd='/', raiseonerr=False)
if result.returncode != 0:
logger.critical('Restoring files failed: %s', result.error_log)
os.chdir(cwd)
def read_header(self):
'''
Read the backup file header that contains the meta data about
@@ -802,11 +832,9 @@ class Restore(admintool.AdminTool):
self.backup_host = config.get('ipa', 'host')
self.backup_ipa_version = config.get('ipa', 'ipa_version')
self.backup_version = config.get('ipa', 'version')
# pylint: disable=no-member
# we can assume that returned object is string and it has .split()
# method
self.backup_services = config.get('ipa', 'services').split(',')
# pylint: enable=no-member
def extract_backup(self):
'''
@@ -831,20 +859,19 @@ class Restore(admintool.AdminTool):
logger.info('Decrypting %s', filename)
filename = decrypt_file(self.dir, filename)
os.chdir(self.dir)
args = ['tar',
'--xattrs',
'--selinux',
'-xzf',
filename,
'.'
]
run(args)
]
run(args, cwd=self.dir)
pent = pwd.getpwnam(constants.DS_USER)
os.chown(self.top_dir, pent.pw_uid, pent.pw_gid)
recursive_chown(self.dir, pent.pw_uid, pent.pw_gid)
constants.DS_USER.chown(self.top_dir)
recursive_chown(
self.dir, constants.DS_USER.uid, constants.DS_USER.pgid
)
if encrypt:
# We can remove the decoded tarball
@@ -868,7 +895,7 @@ class Restore(admintool.AdminTool):
paths.TOMCAT_SIGNEDAUDIT_DIR]
try:
pent = pwd.getpwnam(constants.PKI_USER)
pent = constants.PKI_USER.entity
except KeyError:
logger.debug("No %s user exists, skipping CA directory creation",
constants.PKI_USER)