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

@@ -11,14 +11,13 @@ import re
import os
import glob
import shutil
import pwd
import fileinput
import ssl
import stat
import sys
import tempfile
from contextlib import contextmanager
from augeas import Augeas
from pkg_resources import parse_version
from ipalib import api, x509
from ipalib.constants import RENEWAL_CA_NAME, RA_AGENT_PROFILE, IPA_CA_RECORD
@@ -37,6 +36,7 @@ from ipapython import ipautil, version
from ipapython import ipaldap
from ipapython import directivesetter
from ipapython.dn import DN
from ipapython.version import KRB5_BUILD_VERSION
from ipaplatform.constants import constants
from ipaplatform.paths import paths
from ipaserver import servroles
@@ -173,12 +173,17 @@ def find_version(filename):
else:
return -1
def upgrade_file(sub_dict, filename, template, add=False):
def upgrade_file(sub_dict, filename, template, add=False, force=False):
"""
Get the version from the current and template files and update the
installed configuration file if there is a new template.
If add is True then create a new configuration file.
If force is True then the version comparison is skipped. This should
be used judiciously. It does not override add nor will it affect
files that don't exist (version == -1).
"""
old = int(find_version(filename))
new = int(find_version(template))
@@ -200,7 +205,10 @@ def upgrade_file(sub_dict, filename, template, add=False):
"overwritten. A backup of the original will be made.",
filename)
if old < new or (add and old == 0):
if force:
logger.error("Forcing update of template %s", template)
if ((old < new) or (add and old == 0)) or force:
backup_file(filename, new)
update_conf(sub_dict, filename, template)
logger.info("Upgraded %s to version %d", filename, new)
@@ -359,6 +367,21 @@ def upgrade_adtrust_config():
else:
logger.warning("Error updating Samba registry: %s", e)
logger.info("[Change 'server role' from "
"'CLASSIC PRIMARY DOMAIN CONTROLLER' "
"to 'IPA PRIMARY DOMAIN CONTROLLER' in Samba configuration]")
args = [paths.NET, "conf", "setparm", "global",
"server role", "IPA PRIMARY DOMAIN CONTROLLER"]
try:
ipautil.run(args)
except ipautil.CalledProcessError as e:
# Only report an error if return code is not 255
# which indicates that the new server role is not supported
# and we don't need to do anything
if e.returncode != 255:
logger.warning("Error updating Samba registry: %s", e)
def ca_configure_profiles_acl(ca):
logger.info('[Authorizing RA Agent to modify profiles]')
@@ -389,15 +412,34 @@ def ca_enable_ldap_profile_subsystem(ca):
needs_update = False
directive = None
try:
for i in range(15):
i = 0
while True:
# find profile subsystem
directive = "subsystem.{}.id".format(i)
value = directivesetter.get_directive(
paths.CA_CS_CFG_PATH,
directive,
separator='=')
if not value:
logger.error('Unable to find profile subsystem in %s',
paths.CA_CS_CFG_PATH)
return False
if value != 'profile':
i = i + 1
continue
# check profile subsystem class name
directive = "subsystem.{}.class".format(i)
value = directivesetter.get_directive(
paths.CA_CS_CFG_PATH,
directive,
separator='=')
if value == 'com.netscape.cmscore.profile.ProfileSubsystem':
if value != 'com.netscape.cmscore.profile.LDAPProfileSubsystem':
needs_update = True
break
# break after finding profile subsystem
break
except OSError as e:
logger.error('Cannot read CA configuration file "%s": %s',
paths.CA_CS_CFG_PATH, e)
@@ -440,6 +482,16 @@ def ca_ensure_lightweight_cas_container(ca):
return cainstance.ensure_lightweight_cas_container()
def ca_enable_lightweight_ca_monitor(ca):
logger.info('[Enabling LWCA monitor]')
if not ca.is_configured():
logger.info('CA is not configured')
return False
return cainstance.enable_lightweight_ca_monitor()
def ca_add_default_ocsp_uri(ca):
logger.info('[Adding default OCSP URI configuration]')
if not ca.is_configured():
@@ -484,20 +536,6 @@ def ca_disable_publish_cert(ca):
return True # restart needed
def upgrade_ca_audit_cert_validity(ca):
"""
Update the Dogtag audit signing certificate.
Returns True if restart is needed, False otherwise.
"""
logger.info('[Verifying that CA audit signing cert has 2 year validity]')
if ca.is_configured():
return ca.set_audit_renewal()
else:
logger.info('CA is not configured')
return False
def ca_initialize_hsm_state(ca):
"""Initializse HSM state as False / internal token
"""
@@ -509,6 +547,24 @@ def ca_initialize_hsm_state(ca):
ca.set_hsm_state(config)
def dnssec_set_openssl_engine(dnskeysyncd):
"""
Setup OpenSSL engine for BIND
"""
if constants.NAMED_OPENSSL_ENGINE is None:
return False
if sysupgrade.get_upgrade_state('dns', 'openssl_engine'):
return False
logger.info('[Set OpenSSL engine for BIND]')
dnskeysyncd.setup_named_openssl_conf()
dnskeysyncd.setup_named_sysconfig()
dnskeysyncd.setup_ipa_dnskeysyncd_sysconfig()
sysupgrade.set_upgrade_state('dns', 'openssl_engine', True)
return True
def certificate_renewal_update(ca, kra, ds, http):
"""
@@ -626,9 +682,9 @@ def certificate_renewal_update(ca, kra, ds, http):
# Ok, now we need to stop tracking, then we can start tracking them
# again with new configuration:
ca.stop_tracking_certificates(stop_certmonger=False)
ca.stop_tracking_certificates()
if kra.is_installed():
kra.stop_tracking_certificates(stop_certmonger=False)
kra.stop_tracking_certificates()
ds.stop_tracking_certificates(serverid)
http.stop_tracking_certificates()
@@ -670,7 +726,7 @@ def http_certificate_ensure_ipa_ca_dnsname(http):
try:
cert.match_hostname(expect)
except ssl.CertificateError:
except x509.ssl_match_hostname.CertificateError:
if certs.is_ipa_issued_cert(api, cert):
request_id = certmonger.get_request_id(
{'cert-file': paths.HTTPD_CERT_FILE})
@@ -719,8 +775,7 @@ def copy_crl_file(old_path, new_path=None):
os.symlink(realpath, new_path)
else:
shutil.copy2(old_path, new_path)
pent = pwd.getpwnam(constants.PKI_USER)
os.chown(new_path, pent.pw_uid, pent.pw_gid)
constants.PKI_USER.chown(new_path)
tasks.restore_context(new_path)
@@ -899,7 +954,7 @@ def uninstall_dogtag_9(ds, http):
ca = dogtaginstance.DogtagInstance(
api.env.realm, "CA", "certificate server",
nss_db=paths.VAR_LIB_PKI_CA_ALIAS_DIR)
ca.stop_tracking_certificates(False)
ca.stop_tracking_certificates()
if serverid is not None:
# drop the trailing / off the config_dirname so the directory
@@ -1068,8 +1123,7 @@ def update_http_keytab(http):
'Cannot remove file %s (%s). Please remove the file manually.',
paths.OLD_IPA_KEYTAB, e
)
pent = pwd.getpwnam(http.keytab_user)
os.chown(http.keytab, pent.pw_uid, pent.pw_gid)
http.keytab_user.chown(http.keytab)
def ds_enable_sidgen_extdom_plugins(ds):
@@ -1079,11 +1133,24 @@ def ds_enable_sidgen_extdom_plugins(ds):
if sysupgrade.get_upgrade_state('ds', 'enable_ds_sidgen_extdom_plugins'):
logger.debug('sidgen and extdom plugins are enabled already')
return
return False
ds.add_sidgen_plugin(api.env.basedn)
ds.add_extdom_plugin(api.env.basedn)
sysupgrade.set_upgrade_state('ds', 'enable_ds_sidgen_extdom_plugins', True)
return True
def ds_enable_graceperiod_plugin(ds):
"""Graceperiod is a newer DS plugin so needs to be enabled on upgrade"""
if sysupgrade.get_upgrade_state('ds', 'enable_ds_graceperiod_plugin'):
logger.debug('graceperiod is enabled already')
return False
ds.config_graceperiod_module()
sysupgrade.set_upgrade_state('ds', 'enable_ds_graceperiod_plugin', True)
return True
def ca_upgrade_schema(ca):
logger.info('[Upgrading CA schema]')
@@ -1091,9 +1158,24 @@ def ca_upgrade_schema(ca):
logger.info('CA is not configured')
return False
# ACME schema file moved in pki-server-10.9.0-0.3
# ACME database connections were abstrated in pki-acme-10.10.0
for path in [
'/usr/share/pki/acme/conf/database/ds/schema.ldif',
'/usr/share/pki/acme/conf/database/ldap/schema.ldif',
'/usr/share/pki/acme/database/ldap/schema.ldif',
]:
if os.path.exists(path):
acme_schema_ldif = path
break
else:
logger.info('ACME schema is not available')
return False
schema_files=[
'/usr/share/pki/server/conf/schema-certProfile.ldif',
'/usr/share/pki/server/conf/schema-authority.ldif',
acme_schema_ldif,
]
try:
modified = schemaupdate.update_schema(schema_files, ldapi=True)
@@ -1122,6 +1204,16 @@ def add_default_caacl(ca):
sysupgrade.set_upgrade_state('caacl', 'add_default_caacl', True)
def add_agent_to_security_domain_admins():
user_dn = DN(('uid', "ipara"), ('ou', 'People'), ('o', 'ipaca'))
group_dn = DN(('cn', 'Security Domain Administrators'), ('ou', 'groups'),
('o', 'ipaca'))
try:
api.Backend.ldap2.add_entry_to_group(user_dn, group_dn, 'uniqueMember')
except ipalib.errors.AlreadyGroupMember:
pass
def setup_pkinit(krb):
logger.info("[Setup PKINIT]")
@@ -1217,6 +1309,29 @@ def enable_server_snippet():
tasks.restore_context(paths.KRB5_FREEIPA_SERVER)
def setup_kpasswd_server(krb):
logger.info("[Setup kpasswd_server]")
aug = Augeas(
flags=Augeas.NO_LOAD | Augeas.NO_MODL_AUTOLOAD,
loadpath=paths.USR_SHARE_IPA_DIR,
)
try:
aug.transform("IPAKrb5", paths.KRB5_CONF)
aug.load()
kpass_srv_path = "/files{}/realms/{}/kpasswd_server"
kpass_srv_path = kpass_srv_path.format(paths.KRB5_CONF, krb.realm)
if aug.match(kpass_srv_path):
return
aug.set(kpass_srv_path, f"{krb.fqdn}:464")
aug.save()
finally:
aug.close()
def ntpd_cleanup(fqdn, fstore):
sstore = sysrestore.StateFile(paths.SYSRESTORE)
timeconf.restore_forced_timeservices(sstore, 'ntpd')
@@ -1228,7 +1343,7 @@ def ntpd_cleanup(fqdn, fstore):
try:
instance.disable()
instance.stop()
except Exception as e:
except Exception:
logger.debug("Service ntpd was not disabled or stopped")
for ntpd_file in [paths.NTP_CONF, paths.NTP_STEP_TICKERS,
@@ -1395,13 +1510,20 @@ def upgrade_bind(fstore):
logger.info("DNS service is not configured")
return False
# get rid of old upgrade states
bind_switch_service(bind)
# get rid of old states
bind_old_states(bind)
bind_old_upgrade_states()
# only upgrade with drop-in is missing and /etc/resolv.conf is a link to
# resolve1's stub resolver config file.
has_resolved_ipa_conf = os.path.isfile(paths.SYSTEMD_RESOLVED_IPA_CONF)
if not has_resolved_ipa_conf and detect_resolve1_resolv_conf():
ip_addresses = installutils.resolve_ip_addresses_nss(
api.env.host
)
bind.ip_addresses = ip_addresses
bind.setup_resolv_conf()
logger.info("Updated systemd-resolved configuration")
@@ -1412,6 +1534,9 @@ def upgrade_bind(fstore):
else:
bind_started = False
# create or update autobind entry
bind.setup_autobind()
try:
changed = bind.setup_named_conf(backup=True)
if changed:
@@ -1428,6 +1553,38 @@ def upgrade_bind(fstore):
return changed
def bind_switch_service(bind):
"""
Mask either named or named-pkcs11, we need to run only one,
running both can cause unexpected errors.
"""
named_conflict_name = bind.named_conflict.systemd_name
named_conflict_old = sysupgrade.get_upgrade_state('dns', 'conflict_named')
# nothing changed
if named_conflict_old and named_conflict_old == named_conflict_name:
return False
bind.switch_service()
sysupgrade.set_upgrade_state('dns', 'conflict_named', named_conflict_name)
return True
def bind_old_states(bind):
"""Remove old states
"""
# no longer used states
old_states = [
"enabled",
"running",
"named-regular-enabled",
"named-regular-running",
]
for state in old_states:
bind.delete_state(state)
def bind_old_upgrade_states():
"""Remove old upgrade states
"""
@@ -1453,6 +1610,51 @@ def bind_old_upgrade_states():
sysupgrade.remove_upgrade_state("dns", state)
def ca_update_acme_configuration(ca, fqdn):
"""
Re-apply the templates in case anyting has been updated.
"""
logger.info('[Updating ACME configuration]')
if not os.path.isdir(os.path.join(paths.PKI_TOMCAT, 'acme')):
logger.info('ACME is not deployed, skipping')
return
if not os.path.exists(paths.PKI_ACME_ISSUER_CONF):
logger.info('ACME configuration file %s is missing',
paths.PKI_ACME_ISSUER_CONF)
return
password = directivesetter.get_directive(
paths.PKI_ACME_ISSUER_CONF,
'password',
separator='=')
acme_user = ca.acme_uid(fqdn)
sub_dict = dict(
FQDN=fqdn,
USER=acme_user,
PASSWORD=password,
)
for template_name, target in cainstance.ACME_CONFIG_FILES:
upgrade_file(sub_dict, target,
os.path.join(paths.USR_SHARE_IPA_DIR,
template_name))
def set_default_grace_time():
dn = DN(
('cn', 'global_policy'), ('cn', api.env.realm),
('cn', 'kerberos'), api.env.basedn
)
entry = api.Backend.ldap2.get_entry(dn)
for (a,_v) in entry.items():
if a.lower() == 'passwordgracelimit':
return
entry['objectclass'].append('ipapwdpolicy')
entry['passwordgracelimit'] = -1
api.Backend.ldap2.update_entry(entry)
def upgrade_configuration():
"""
Execute configuration upgrade of the IPA services
@@ -1505,6 +1707,7 @@ def upgrade_configuration():
IPA_CCACHES=paths.IPA_CCACHES,
IPA_CUSTODIA_SOCKET=paths.IPA_CUSTODIA_SOCKET,
KDCPROXY_CONFIG=paths.KDCPROXY_CONFIG,
DOMAIN=api.env.domain,
)
subject_base = find_subject_base()
@@ -1548,20 +1751,26 @@ def upgrade_configuration():
os.path.join(paths.USR_SHARE_IPA_DIR,
"ipa-kdc-proxy.conf.template"))
if ca.is_configured():
# Ensure that the drop-in file is present
if not os.path.isfile(paths.SYSTEMD_PKI_TOMCAT_IPA_CONF):
ca.add_ipa_wait()
# Handle upgrade of AJP connector configuration
ca.secure_ajp_connector()
rewrite = ca.secure_ajp_connector()
if ca.ajp_secret:
sub_dict['DOGTAG_AJP_SECRET'] = "secret={}".format(
ca.ajp_secret)
else:
sub_dict['DOGTAG_AJP_SECRET'] = ''
upgrade_file(
sub_dict,
paths.HTTPD_IPA_PKI_PROXY_CONF,
os.path.join(paths.USR_SHARE_IPA_DIR,
"ipa-pki-proxy.conf.template"),
add=True)
# force=True will ensure the secret is updated if it changes
if rewrite:
upgrade_file(
sub_dict,
paths.HTTPD_IPA_PKI_PROXY_CONF,
os.path.join(paths.USR_SHARE_IPA_DIR,
"ipa-pki-proxy.conf.template"),
add=True, force=True)
else:
if os.path.isfile(paths.HTTPD_IPA_PKI_PROXY_CONF):
os.remove(paths.HTTPD_IPA_PKI_PROXY_CONF)
@@ -1585,6 +1794,18 @@ def upgrade_configuration():
else:
logger.info('ephemeralRequest is already enabled')
if tasks.is_fips_enabled():
logger.info('[Ensuring KRA OAEP wrap algo is enabled in FIPS]')
value = directivesetter.get_directive(
paths.KRA_CS_CFG_PATH,
'keyWrap.useOAEP',
separator='=')
if value is None or value.lower() != 'true':
logger.info('Use the OAEP key wrap algo')
kra.enable_oaep_wrap_algo()
else:
logger.info('OAEP key wrap algo is already enabled')
# several upgrade steps require running CA. If CA is configured,
# always run ca.start() because we need to wait until CA is really ready
# by checking status using http
@@ -1636,7 +1857,13 @@ def upgrade_configuration():
ds.realm = api.env.realm
ds.suffix = ipautil.realm_to_suffix(api.env.realm)
ds_enable_sidgen_extdom_plugins(ds)
if any([
ds_enable_sidgen_extdom_plugins(ds),
ds_enable_graceperiod_plugin(ds)
]):
ds.restart(ds.serverid)
set_default_grace_time()
if not http.is_kdcproxy_configured():
logger.info('[Enabling KDC Proxy]')
@@ -1658,12 +1885,12 @@ def upgrade_configuration():
(otpdinstance.OtpdInstance(), 'OTPD'),
)
for service, ldap_name in simple_service_list:
for svc, ldap_name in simple_service_list:
try:
if not service.is_configured():
service.create_instance(ldap_name, fqdn,
ipautil.realm_to_suffix(api.env.realm),
realm=api.env.realm)
if not svc.is_configured():
svc.create_instance(ldap_name, fqdn,
ipautil.realm_to_suffix(api.env.realm),
realm=api.env.realm)
except ipalib.errors.DuplicateEntry:
pass
@@ -1673,6 +1900,10 @@ def upgrade_configuration():
if not dnskeysyncd.is_configured():
dnskeysyncd.create_instance(fqdn, api.env.realm)
dnskeysyncd.start_dnskeysyncd()
else:
if dnssec_set_openssl_engine(dnskeysyncd):
dnskeysyncd.start_dnskeysyncd()
dnskeysyncd.set_dyndb_ldap_workdir_permissions()
cleanup_kdc(fstore)
cleanup_adtrust(fstore)
@@ -1684,15 +1915,18 @@ def upgrade_configuration():
custodia = custodiainstance.CustodiaInstance(api.env.host, api.env.realm)
custodia.upgrade_instance()
# Don't include schema upgrades in restart consideration, see
# https://pagure.io/freeipa/issue/9204
ca_upgrade_schema(ca)
ca_restart = any([
ca_restart,
ca_upgrade_schema(ca),
upgrade_ca_audit_cert_validity(ca),
certificate_renewal_update(ca, kra, ds, http),
ca_enable_pkix(ca),
ca_configure_profiles_acl(ca),
ca_configure_lightweight_ca_acls(ca),
ca_ensure_lightweight_cas_container(ca),
ca_enable_lightweight_ca_monitor(ca),
ca_add_default_ocsp_uri(ca),
ca_disable_publish_cert(ca),
])
@@ -1719,7 +1953,10 @@ def upgrade_configuration():
cainstance.repair_profile_caIPAserviceCert()
ca.setup_lightweight_ca_key_retrieval()
cainstance.ensure_ipa_authority_entry()
ca.setup_acme()
ca_update_acme_configuration(ca, fqdn)
ca_initialize_hsm_state(ca)
add_agent_to_security_domain_admins()
migrate_to_authselect()
add_systemd_user_hbac()
@@ -1750,11 +1987,22 @@ def upgrade_configuration():
setup_spake(krb)
setup_pkinit(krb)
enable_server_snippet()
setup_kpasswd_server(krb)
if KRB5_BUILD_VERSION >= parse_version('1.20'):
krb.pac_tkt_sign_support_enable()
# Must be executed after certificate_renewal_update
# (see function docstring for details)
http_certificate_ensure_ipa_ca_dnsname(http)
# Convert configuredService to either enabledService or hiddenService
# depending on the state of the server role. This is to fix situations
# when deployment has happened before introduction of hidden replicas
# as those services will stay as configuredService and will not get
# started after upgrade, rendering the system non-functioning
service.sync_services_state(fqdn)
if not ds_running:
ds.stop(ds.serverid)