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

@@ -60,7 +60,6 @@ class ServerCertificateInstallInterface(service.ServiceInstallInterface):
description = "SSL certificate"
dirsrv_cert_files = knob(
# pylint: disable=invalid-sequence-index
typing.List[str], None,
description=("File containing the Directory Server SSL certificate "
"and private key"),
@@ -71,7 +70,6 @@ class ServerCertificateInstallInterface(service.ServiceInstallInterface):
dirsrv_cert_files = prepare_only(dirsrv_cert_files)
http_cert_files = knob(
# pylint: disable=invalid-sequence-index
typing.List[str], None,
description=("File containing the Apache Server SSL certificate and "
"private key"),
@@ -82,7 +80,6 @@ class ServerCertificateInstallInterface(service.ServiceInstallInterface):
http_cert_files = prepare_only(http_cert_files)
pkinit_cert_files = knob(
# pylint: disable=invalid-sequence-index
typing.List[str], None,
description=("File containing the Kerberos KDC SSL certificate and "
"private key"),
@@ -141,8 +138,50 @@ class ServerCertificateInstallInterface(service.ServiceInstallInterface):
pkinit_cert_name = prepare_only(pkinit_cert_name)
@group
class ServerHSMInstallInterface(service.ServiceInstallInterface):
description = "HSM"
token_name = knob(
str, None,
description=(
"The PKCS#11 token name if using an HSM to store and generate "
"private keys."
),
cli_metavar='NAME',
)
token_name = master_install_only(token_name)
token_library_path = knob(
str, None,
description=(
"The full path to the PKCS#11 shared library needed to"
"access an HSM device."
),
cli_metavar='NAME',
)
token_library_path = prepare_only(token_library_path)
token_password = knob(
str, None,
sensitive=True,
description=("The PKCS#11 token password for the HSM."),
cli_metavar='NAME',
)
token_password = prepare_only(token_password)
token_password_file = knob(
str, None,
description=("The full path to a file containing the password to "
"the PKCS#11 token password."),
cli_metavar='NAME',
)
token_password_file = prepare_only(token_password_file)
@group
class ServerInstallInterface(ServerCertificateInstallInterface,
ServerHSMInstallInterface,
client.ClientInstallInterface,
ca.CAInstallInterface,
kra.KRAInstallInterface,
@@ -171,7 +210,6 @@ class ServerInstallInterface(ServerCertificateInstallInterface,
domain_name = client.ClientInstallInterface.domain_name
domain_name = extend_knob(
domain_name,
# pylint: disable=no-member
cli_names=list(domain_name.cli_names) + ['-n'],
)
@@ -432,11 +470,6 @@ class ServerInstallInterface(ServerCertificateInstallInterface,
"You cannot specify an --enable-compat option without the "
"--setup-adtrust option")
if self.netbios_name:
raise RuntimeError(
"You cannot specify a --netbios-name option without the "
"--setup-adtrust option")
if self.no_msdcs:
raise RuntimeError(
"You cannot specify a --no-msdcs option without the "

View File

@@ -31,7 +31,7 @@ from ipaplatform import services
from ipaplatform.paths import paths
from ipaplatform.tasks import tasks
from ipalib import api, errors, x509
from ipalib.constants import DOMAIN_LEVEL_0
from ipalib.constants import DOMAIN_LEVEL_0, FQDN
from ipalib.facts import is_ipa_configured, is_ipa_client_configured
from ipalib.util import (
validate_domain_name,
@@ -42,9 +42,9 @@ from ipaserver.install import (
adtrust, adtrustinstance, bindinstance, ca, dns, dsinstance,
httpinstance, installutils, kra, krbinstance,
otpdinstance, custodiainstance, replication, service,
sysupgrade)
sysupgrade, cainstance)
from ipaserver.install.installutils import (
BadHostError, get_fqdn, get_server_ip_address,
BadHostError, get_server_ip_address,
load_pkcs12, read_password, verify_fqdn, update_hosts_file,
validate_mask)
@@ -76,7 +76,7 @@ def validate_dm_password(password):
# Actual behavior of setup-ds.pl is that it does not accept white
# space characters in password when called interactively but does when
# provided such password in INF file. But it ignores leading and trailing
# white spaces in INF file.
# whitespaces in INF file.
# Disallow leading/trailing whaitespaces
if password.strip() != password:
@@ -132,7 +132,7 @@ def read_cache(dm_password):
fname,
dm_password,
top_dir)
except Exception as e:
except Exception:
shutil.rmtree(top_dir)
raise Exception("Decryption of answer cache in %s failed, please "
"check your password." % paths.ROOT_IPA_CACHE)
@@ -178,18 +178,24 @@ def write_cache(options):
shutil.rmtree(top_dir)
def read_host_name(host_default, no_host_dns=False):
def read_host_name(host_default):
"""
Prompt user to input FQDN. Does not verify it.
"""
print("Enter the fully qualified domain name of the computer")
print("on which you're setting up server software. Using the form")
print("<hostname>.<domainname>")
print("Example: master.example.com.")
print("Example: master.example.com")
print("")
print("")
if host_default == "":
host_default = "master.example.com"
host_name = user_input("Server host name", host_default, allow_empty=False)
print("")
verify_fqdn(host_name, no_host_dns)
if host_name.endswith('.'):
host_name = host_name[:-1]
return host_name
@@ -315,6 +321,19 @@ def remove_master_from_managed_topology(api_instance, options):
logger.warning("Failed to delete master: %s", e)
def cleanup_dogtag_server_specific_data():
"""
There are data in Dogtag database related to specific servers.
Some of these data should be left alone, e.g. range assignments.
Some of these data should be cleaned up; that's what this
subroutine does.
"""
# remove ACME user
acme_uid = cainstance.CAInstance.acme_uid(api.env.host)
cainstance.CAInstance.delete_user(acme_uid)
@common_cleanup
def install_check(installer):
options = installer
@@ -408,10 +427,39 @@ def install_check(installer):
if not setup_ca and options.setup_kra:
raise ScriptError(
"--setup-kra cannot be used with CA-less installation")
if setup_ca:
if any(
(
options.token_name is not None,
options.token_library_path is not None,
options.token_password is not None,
options.token_password_file is not None,
)
):
if any(
(
options.token_name is None,
options.token_library_path is None)
):
raise ScriptError(
"Both token name and library path are required."
)
else:
if any(
(
options.token_name is not None,
options.token_library_path is not None,
options.token_password is not None,
options.token_password_file is not None,
)
):
raise ScriptError(
"HSM token options are not valid with CA-less installs."
)
print("======================================="
"=======================================")
print("This program will set up the FreeIPA Server.")
print("This program will set up the IPA Server.")
print("Version {}".format(version.VERSION))
print("")
print("This includes:")
@@ -427,6 +475,7 @@ def install_check(installer):
print(" * Configure KRA (dogtag) for secret management")
if options.setup_dns:
print(" * Configure DNS (bind)")
print(" * Configure SID generation")
if options.setup_adtrust:
print(" * Configure Samba (smb) and winbind for managing AD trusts")
if not options.no_pkinit:
@@ -477,14 +526,15 @@ def install_check(installer):
if options.host_name:
host_default = options.host_name
else:
host_default = get_fqdn()
host_default = FQDN
if installer.interactive and not options.host_name:
host_name = read_host_name(host_default)
else:
host_name = host_default
try:
if not installer.interactive or options.host_name:
verify_fqdn(host_default, options.no_host_dns)
host_name = host_default
else:
host_name = read_host_name(host_default, options.no_host_dns)
verify_fqdn(host_name, options.no_host_dns)
except BadHostError as e:
raise ScriptError(e)
@@ -504,6 +554,9 @@ def install_check(installer):
domain_name = domain_name.lower()
if host_name.lower() == domain_name:
raise ScriptError("hostname cannot be the same as the domain name")
if not options.realm_name:
realm_name = read_realm_name(domain_name, not installer.interactive)
logger.debug("read realm_name: %s\n", realm_name)
@@ -603,6 +656,22 @@ def install_check(installer):
else:
admin_password = options.admin_password
if all(
(
options.token_password is None,
options.token_password_file is None,
options.token_name is not None
)
):
if options.unattended:
raise ScriptError("HSM token password required")
token_password = read_password(
f"HSM token '{options.token_name}'" , confirm=False)
if token_password is None:
raise ScriptError("HSM token password required")
else:
options.token_password = token_password
# Configuration for ipalib, we will bootstrap and finalize later, after
# we are sure we have the configuration file ready.
cfg = dict(
@@ -686,8 +755,9 @@ def install_check(installer):
logger.debug('Starting Directory Server')
services.knownservices.dirsrv.start(instance_name)
if options.setup_adtrust:
adtrust.install_check(False, options, api)
# Always call adtrust.install_check
# if --setup-adtrust is not specified, only the SID part is executed
adtrust.install_check(False, options, api)
# installer needs to update hosts file when DNS subsystem will be
# installed or custom addresses are used
@@ -949,8 +1019,9 @@ def install(installer):
if options.setup_dns:
dns.install(False, False, options)
if options.setup_adtrust:
adtrust.install(False, options, fstore, api)
# Always call adtrust installer to configure SID generation
# if --setup-adtrust is not specified, only the SID part is executed
adtrust.install(False, options, fstore, api)
# Set the admin user kerberos password
ds.change_admin_password(admin_password)
@@ -971,6 +1042,8 @@ def install(installer):
args.append("--no-sshd")
if options.mkhomedir:
args.append("--mkhomedir")
if options.subid:
args.append("--subid")
start = time.time()
run(args, redirect_output=True)
dur = time.time() - start
@@ -1032,7 +1105,7 @@ def install(installer):
"enabling chronyd.")
print("")
if setup_ca:
if setup_ca and not options.token_name:
print(("Be sure to back up the CA certificates stored in " +
paths.CACERT_P12))
print("These files are required to create replicas. The password for "
@@ -1081,6 +1154,9 @@ def uninstall_check(installer):
"uninstall procedure?", False):
raise ScriptError("Aborting uninstall operation.")
kra.uninstall_check(options)
ca.uninstall_check(options)
try:
api.Backend.ldap2.connect(autobind=True)
@@ -1102,7 +1178,9 @@ def uninstall_check(installer):
else:
dns.uninstall_check(options)
ca.uninstall_check(options)
ca.uninstall_crl_check(options)
cleanup_dogtag_server_specific_data()
if domain_level == DOMAIN_LEVEL_0:
rm = replication.ReplicationManager(
@@ -1146,6 +1224,13 @@ def uninstall(installer):
rv = 0
# Uninstall the KRA prior to shutting the services down so it
# can un-register with the CA.
kra.uninstall()
# Uninstall the CA priori to shutting the services down so it
# can unregister from the security domain
ca.uninstall()
print("Shutting down all IPA services")
try:
services.knownservices.ipa.stop()
@@ -1158,10 +1243,6 @@ def uninstall(installer):
restore_time_sync(sstore, fstore)
kra.uninstall()
ca.uninstall()
dns.uninstall()
httpinstance.HTTPInstance(fstore).uninstall()
@@ -1247,6 +1328,9 @@ def uninstall(installer):
logger.warning("Failed to remove file %s: %s",
paths.IPA_RENEWAL_LOCK, e)
ipautil.remove_file(paths.SVC_LIST_FILE)
ipautil.rmtree('/root/.cache/ipa')
print("Removing IPA client configuration")
try:
result = run([paths.IPA_CLIENT_INSTALL, "--on-master",

View File

@@ -9,6 +9,7 @@ import logging
import dns.exception as dnsexception
import dns.name as dnsname
import itertools
import os
import shutil
import socket
@@ -22,8 +23,8 @@ import six
from ipaclient.install.client import check_ldap_conf, sssd_enable_ifp
import ipaclient.install.timeconf
from ipalib.install import certstore, sysrestore
from ipalib.install.kinit import kinit_keytab
from ipalib.install import sysrestore
from ipalib.kinit import kinit_keytab
from ipapython import ipaldap, ipautil
from ipapython.dn import DN
from ipapython.dnsutil import DNSResolver
@@ -32,14 +33,17 @@ from ipapython.ipachangeconf import IPAChangeConf
from ipaplatform import services
from ipaplatform.tasks import tasks
from ipaplatform.paths import paths
from ipalib import api, constants, create_api, errors, rpc, x509
from ipalib import api, constants, create_api, errors, rpc
from ipalib.config import Env
from ipalib.facts import is_ipa_configured, is_ipa_client_configured
from ipalib.util import no_matching_interface_for_ip_address_warning
from ipaclient.install.client import configure_krb5_conf, purge_host_keytab
from ipaserver.install.dogtaginstance import INTERNAL_TOKEN
from ipaserver.install import (
adtrust, bindinstance, ca, dns, dsinstance, httpinstance,
installutils, kra, krbinstance, otpdinstance, custodiainstance, service)
adtrust, bindinstance, ca, cainstance, dns, dsinstance, httpinstance,
installutils, kra, krainstance, krbinstance, otpdinstance,
custodiainstance, service,)
from ipaserver.install import certs
from ipaserver.install.installutils import (
ReplicaConfig, load_pkcs12, validate_mask)
from ipaserver.install.replication import (
@@ -132,24 +136,6 @@ def install_krb(config, setup_pkinit=False, pkcs12_info=None, fstore=None):
return krb
def install_ca_cert(ldap, base_dn, realm, cafile, destfile=paths.IPA_CA_CRT):
try:
try:
certs = certstore.get_ca_certs(ldap, base_dn, realm, False)
except errors.NotFound:
try:
shutil.copy(cafile, destfile)
except shutil.Error:
# cafile == IPA_CA_CRT
pass
else:
certs = [c[0] for c in certs if c[2] is not False]
x509.write_certificate_list(certs, destfile, mode=0o644)
except Exception as e:
raise ScriptError("error copying files: " + str(e))
return destfile
def install_http(config, auto_redirect, ca_is_configured, ca_file,
pkcs12_info=None, fstore=None):
# if we have a pkcs12 file, create the cert db from
@@ -623,7 +609,7 @@ def check_domain_level_is_supported(current):
above_upper_bound = current > constants.MAX_DOMAIN_LEVEL
if under_lower_bound or above_upper_bound:
message = ("This version of FreeIPA does not support "
message = ("This version of IPA does not support "
"the Domain Level which is currently set for "
"this domain. The Domain Level needs to be "
"raised before installing a replica with "
@@ -720,6 +706,8 @@ def ensure_enrolled(installer):
args.append("--no-sshd")
if installer.mkhomedir:
args.append("--mkhomedir")
if installer.subid:
args.append("--subid")
if installer.force_join:
args.append("--force-join")
if installer.no_ntp:
@@ -770,6 +758,101 @@ def promotion_check_ipa_domain(master_ldap_conn, basedn):
))
def promotion_check_host_principal_auth_ind(conn, hostdn):
entry = conn.get_entry(hostdn, ['krbprincipalauthind'])
if 'krbprincipalauthind' in entry:
raise RuntimeError(
"Client cannot be promoted to a replica if the host principal "
"has an authentication indicator set."
)
def clean_up_hsm_nicknames(api):
"""Ensure that all of the nicknames on the token are visible on
the NSS softoken.
"""
# Hardcode the token names. NSS tooling does not provide a
# public way to determine it other than scraping modutil
# output.
if tasks.is_fips_enabled():
dbname = 'NSS FIPS 140-2 Certificate DB'
else:
dbname = 'NSS Certificate DB'
api.Backend.ldap2.connect()
(token_name, _unused) = ca.lookup_hsm_configuration(api)
api.Backend.ldap2.disconnect()
if not token_name:
return
cai = cainstance.CAInstance(api.env.realm, host_name=api.env.host)
dogtag_reqs = cai.tracking_reqs.items()
kra = krainstance.KRAInstance(api.env.realm)
if kra.is_installed():
dogtag_reqs = itertools.chain(dogtag_reqs,
kra.tracking_reqs.items())
try:
tmpdir = tempfile.mkdtemp(prefix="tmp-")
pwd_file = os.path.join(tmpdir, "pwd_file")
with open(pwd_file, "w") as pwd:
with open(paths.PKI_TOMCAT_PASSWORD_CONF, 'r') as fd:
for line in fd:
(token, pin) = line.split('=', 1)
if token.startswith('hardware-'):
token = token.replace('hardware-', '')
pwd.write(f'{token}:{pin}')
elif token == INTERNAL_TOKEN:
pwd.write(f'{dbname}:{pin}')
pwd.flush()
db = certs.CertDB(api.env.realm,
nssdir=paths.PKI_TOMCAT_ALIAS_DIR,
pwd_file=pwd_file)
for (nickname, _unused) in dogtag_reqs:
try:
if nickname in (
'caSigningCert cert-pki-ca',
'Server-Cert cert-pki-ca'
):
continue
if nickname in (
'auditSigningCert cert-pki-ca',
'auditSigningCert cert-pki-kra',
):
trust = ',,P'
else:
trust = ',,'
db.run_certutil(['-M',
'-n', f"{token_name}:{nickname}",
'-t', trust])
except CalledProcessError as e:
logger.debug("Modifying trust on %s failed: %s",
nickname, e)
if db.has_nickname('Directory Server CA certificate'):
db.run_certutil(['--rename',
'-n', 'Directory Server CA certificate',
'--new-n', 'caSigningCert cert-pki-ca'],
raiseonerr=False)
finally:
shutil.rmtree(tmpdir)
def remote_connection(config):
logger.debug("Creating LDAP connection to %s", config.master_host_name)
ldapuri = 'ldaps://%s' % ipautil.format_netloc(config.master_host_name)
xmlrpc_uri = 'https://{}/ipa/xml'.format(
ipautil.format_netloc(config.master_host_name))
remote_api = create_api(mode=None)
remote_api.bootstrap(in_server=True,
context='installer',
confdir=paths.ETC_IPA,
ldap_uri=ldapuri,
xmlrpc_uri=xmlrpc_uri)
remote_api.finalize()
return remote_api
@common_cleanup
@preserve_enrollment_state
def promote_check(installer):
@@ -786,7 +869,8 @@ def promote_check(installer):
raise ScriptError("--setup-ca and --*-cert-file options are "
"mutually exclusive")
if not is_ipa_client_configured(on_master=True):
ipa_client_installed = is_ipa_client_configured(on_master=True)
if not ipa_client_installed:
# One-step replica installation
if options.password and options.admin_password:
raise ScriptError("--password and --admin-password options are "
@@ -814,14 +898,12 @@ def promote_check(installer):
env._bootstrap(context='installer', confdir=paths.ETC_IPA, log=None)
env._finalize_core(**dict(constants.DEFAULT_CONFIG))
# pylint: disable=no-member
xmlrpc_uri = 'https://{}/ipa/xml'.format(ipautil.format_netloc(env.host))
api.bootstrap(in_server=True,
context='installer',
confdir=paths.ETC_IPA,
ldap_uri=ipaldap.realm_to_ldapi_uri(env.realm),
xmlrpc_uri=xmlrpc_uri)
# pylint: enable=no-member
api.finalize()
config = ReplicaConfig()
@@ -921,26 +1003,23 @@ def promote_check(installer):
installutils.verify_fqdn(config.master_host_name, options.no_host_dns,
local_hostname=not container_environment)
if config.host_name.lower() == config.domain_name.lower():
raise ScriptError("hostname cannot be the same as the domain name")
ccache = os.environ['KRB5CCNAME']
kinit_keytab('host/{env.host}@{env.realm}'.format(env=api.env),
paths.KRB5_KEYTAB,
ccache)
cafile = paths.IPA_CA_CRT
if not os.path.isfile(cafile):
raise RuntimeError("CA cert file is not available! Please reinstall"
"the client and try again.")
if ipa_client_installed:
# host was already an IPA client, refresh client cert stores to
# ensure we have up to date CA certs.
try:
ipautil.run([paths.IPA_CERTUPDATE])
except ipautil.CalledProcessError:
raise RuntimeError("ipa-certupdate failed to refresh certs.")
ldapuri = 'ldaps://%s' % ipautil.format_netloc(config.master_host_name)
xmlrpc_uri = 'https://{}/ipa/xml'.format(
ipautil.format_netloc(config.master_host_name))
remote_api = create_api(mode=None)
remote_api.bootstrap(in_server=True,
context='installer',
confdir=paths.ETC_IPA,
ldap_uri=ldapuri,
xmlrpc_uri=xmlrpc_uri)
remote_api.finalize()
remote_api = remote_connection(config)
installer._remote_api = remote_api
with rpc_client(remote_api) as client:
@@ -956,6 +1035,10 @@ def promote_check(installer):
config.master_host_name, None)
promotion_check_ipa_domain(conn, remote_api.env.basedn)
hostdn = DN(('fqdn', api.env.host),
api.env.container_host,
api.env.basedn)
promotion_check_host_principal_auth_ind(conn, hostdn)
# Make sure that domain fulfills minimal domain level
# requirement
@@ -981,7 +1064,7 @@ def promote_check(installer):
raise errors.ACIError(info="Not authorized")
if installer._ccache is None:
del os.environ['KRB5CCNAME']
os.environ.pop('KRB5CCNAME', None)
else:
os.environ['KRB5CCNAME'] = installer._ccache
@@ -1012,8 +1095,8 @@ def promote_check(installer):
if replman.get_replication_agreement(config.host_name):
msg = ("A replication agreement for this host already exists. "
"It needs to be removed.\n"
"Run this command:\n"
" %% ipa-replica-manage del {host} --force"
"Run this command on any working server:\n"
" %% ipa server-del {host} --force"
.format(host=config.host_name))
raise ScriptError(msg, rval=3)
@@ -1066,8 +1149,16 @@ def promote_check(installer):
'CA', conn, preferred_cas
)
if ca_host is not None:
if options.setup_ca and config.master_host_name != ca_host:
conn.disconnect()
del remote_api
config.master_host_name = ca_host
remote_api = remote_connection(config)
installer._remote_api = remote_api
conn = remote_api.Backend.ldap2
conn.connect(ccache=installer._ccache)
config.ca_host_name = ca_host
ca_enabled = True
ca_enabled = True # There is a CA somewhere in the topology
if options.dirsrv_cert_files:
logger.error("Certificates could not be provided when "
"CA is present on some master.")
@@ -1105,8 +1196,18 @@ def promote_check(installer):
'KRA', conn, preferred_kras
)
if kra_host is not None:
if options.setup_kra and config.master_host_name != kra_host:
conn.disconnect()
del remote_api
config.master_host_name = kra_host
remote_api = remote_connection(config)
installer._remote_api = remote_api
conn = remote_api.Backend.ldap2
conn.connect(ccache=installer._ccache)
config.kra_host_name = kra_host
kra_enabled = True
if options.setup_kra: # only reset ca_host if KRA is requested
config.ca_host_name = kra_host
kra_enabled = True # There is a KRA somewhere in the topology
if options.setup_kra and options.server and \
kra_host != options.server:
# Installer was provided with a specific master
@@ -1145,8 +1246,9 @@ def promote_check(installer):
# check addresses here, dns module is doing own check
no_matching_interface_for_ip_address_warning(config.ips)
if options.setup_adtrust:
adtrust.install_check(False, options, remote_api)
# Always call adtrust.install_check
# if --setup-adtrust is not specified, only the SID part is executed
adtrust.install_check(False, options, remote_api)
except errors.ACIError:
logger.debug("%s", traceback.format_exc())
@@ -1170,7 +1272,7 @@ def promote_check(installer):
if add_to_ipaservers:
# use user's credentials when the server host is not ipaservers
if installer._ccache is None:
del os.environ['KRB5CCNAME']
os.environ.pop('KRB5CCNAME', None)
else:
os.environ['KRB5CCNAME'] = installer._ccache
@@ -1179,14 +1281,14 @@ def promote_check(installer):
config.master_host_name, config.host_name, config.realm_name,
options.setup_ca, 389,
options.admin_password, principal=options.principal,
ca_cert_file=cafile)
ca_cert_file=paths.IPA_CA_CRT)
finally:
if add_to_ipaservers:
os.environ['KRB5CCNAME'] = ccache
installer._ca_enabled = ca_enabled
installer._kra_enabled = kra_enabled
installer._ca_file = cafile
installer._ca_file = paths.IPA_CA_CRT
installer._fstore = fstore
installer._sstore = sstore
installer._config = config
@@ -1207,7 +1309,6 @@ def install(installer):
fstore = installer._fstore
sstore = installer._sstore
config = installer._config
cafile = installer._ca_file
dirsrv_pkcs12_info = installer._dirsrv_pkcs12_info
http_pkcs12_info = installer._http_pkcs12_info
pkinit_pkcs12_info = installer._pkinit_pkcs12_info
@@ -1222,6 +1323,24 @@ def install(installer):
if tasks.configure_pkcs11_modules(fstore):
print("Disabled p11-kit-proxy")
_hostname, _sep, host_domain = config.host_name.partition('.')
fstore.backup_file(paths.KRB5_CONF)
# Write a new krb5.conf in case any values changed finding the
# right server to configure against (for CA, KRA).
logger.debug("Installing against server %s", config.master_host_name)
configure_krb5_conf(
cli_realm=api.env.realm,
cli_domain=api.env.domain,
cli_server=[config.master_host_name],
cli_kdc=[config.master_host_name],
dnsok=False,
filename=paths.KRB5_CONF,
client_domain=host_domain,
client_hostname=config.host_name,
configure_sssd=False
)
if installer._add_to_ipaservers:
try:
conn.connect(ccache=installer._ccache)
@@ -1241,18 +1360,10 @@ def install(installer):
try:
conn.connect(ccache=ccache)
# Update and istall updated CA file
cafile = install_ca_cert(conn, api.env.basedn, api.env.realm, cafile)
install_ca_cert(conn, api.env.basedn, api.env.realm, cafile,
destfile=paths.KDC_CA_BUNDLE_PEM)
install_ca_cert(conn, api.env.basedn, api.env.realm, cafile,
destfile=paths.CA_BUNDLE_PEM)
# Configure dirsrv
ds = install_replica_ds(config, options, ca_enabled,
remote_api,
ca_file=cafile,
ca_file=paths.IPA_CA_CRT,
pkcs12_info=dirsrv_pkcs12_info,
fstore=fstore)
@@ -1303,7 +1414,7 @@ def install(installer):
auto_redirect=not options.no_ui_redirect,
pkcs12_info=http_pkcs12_info,
ca_is_configured=ca_enabled,
ca_file=cafile,
ca_file=paths.IPA_CA_CRT,
fstore=fstore)
# Need to point back to ourself after the cert for HTTP is obtained
@@ -1313,10 +1424,10 @@ def install(installer):
otpd.create_instance('OTPD', config.host_name,
ipautil.realm_to_suffix(config.realm_name))
if kra_enabled:
if options.setup_kra and kra_enabled:
# A KRA peer always provides a CA, too.
mode = custodiainstance.CustodiaModes.KRA_PEER
elif ca_enabled:
elif options.setup_ca and ca_enabled:
mode = custodiainstance.CustodiaModes.CA_PEER
else:
mode = custodiainstance.CustodiaModes.MASTER_PEER
@@ -1328,6 +1439,8 @@ def install(installer):
options.domain_name = config.domain_name
options.host_name = config.host_name
options.dm_password = config.dirman_password
# Always call ca.install() if there is a CA in the topology
# to ensure the RA agent is present.
ca.install(False, config, options, custodia=custodia)
# configure PKINIT now that all required services are in place
@@ -1340,6 +1453,7 @@ def install(installer):
ds.finalize_replica_config()
if kra_enabled:
# The KRA installer checks for itself the status of setup_kra
kra.install(api, config, options, custodia=custodia)
service.print_msg("Restarting the KDC")
@@ -1352,8 +1466,9 @@ def install(installer):
if options.setup_dns:
dns.install(False, True, options, api)
if options.setup_adtrust:
adtrust.install(False, options, fstore, api)
# Always call adtrust.install
# if --setup-adtrust is not specified, only the SID part is executed
adtrust.install(False, options, fstore, api)
if options.hidden_replica:
# Set services to hidden
@@ -1388,6 +1503,9 @@ def install(installer):
'''.format(ca_servers[0]))
print(msg, file=sys.stderr)
if options.setup_ca:
clean_up_hsm_nicknames(api)
def init(installer):
installer.unattended = not installer.interactive

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)