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,12 +11,14 @@ from __future__ import print_function, absolute_import
import enum
import logging
import os.path
import pki.util
import six
from ipalib.constants import IPA_CA_CN
from ipalib.install import certstore
from ipalib.install.service import enroll_only, master_install_only, replica_install_only
from ipaplatform.constants import constants
from ipaserver.install import sysupgrade
from ipapython.install import typing
from ipapython.install.core import group, knob, extend_knob
@@ -69,6 +71,26 @@ def subject_validator(valid_attrs, value):
raise ValueError("invalid DN: %s" % e)
def random_serial_numbers_version(enabled):
"""Return True if PKI supports RSNv3
The caller is responsible for raising the exception.
"""
if not enabled:
return None, None
pki_version = pki.util.Version(pki.specification_version())
return pki_version >= pki.util.Version("11.2.0"), pki_version
def random_serial_numbers_validator(enabled):
val, pki_version = random_serial_numbers_version(enabled)
if val is False:
raise ValueError(
"Random Serial Numbers are not supported in PKI version %s"
% pki_version
)
def lookup_ca_subject(api, subject_base):
dn = DN(('cn', IPA_CA_CN), api.env.container_ca, api.env.basedn)
try:
@@ -88,6 +110,189 @@ def lookup_ca_subject(api, subject_base):
return str(ca_subject)
def lookup_random_serial_number_version(api):
"""
Retrieve the random serial number version number from the
remote server.
If the value is > 0 then RSN was enabled. Return the raw
value for future-proofing in case version-specific decisions
need to be made.
Returns 0 if RSN is not enabled or otherwise not available.
"""
dn = DN(('cn', IPA_CA_CN), api.env.container_ca, api.env.basedn)
version = 0
try:
# we do not use api.Command.ca_show because it attempts to
# talk to the CA (to read certificate / chain), but the RA
# backend may be unavailable (ipa-replica-install) or unusable
# due to RA Agent cert not yet created (ipa-ca-install).
entry = api.Backend.ldap2.get_entry(dn)
# If the attribute doesn't exist then the remote didn't
# enable RSN.
if 'ipacarandomserialnumberversion' in entry:
version = int(entry['ipacarandomserialnumberversion'][0])
except (errors.NotFound, KeyError):
# if the entry doesn't exist then the remote doesn't support
# RSN so there is nothing to do.
pass
return version
def lookup_hsm_configuration(api):
"""
If an HSM was configured on the initial install then return the
token name and PKCS#11 library path from that install.
Returns a tuple of (token_name, token_library_path) or (None, None)
"""
dn = DN(('cn', IPA_CA_CN), api.env.container_ca, api.env.basedn)
token_name = None
token_library_path = None
try:
# we do not use api.Command.ca_show because it attempts to
# talk to the CA (to read certificate / chain), but the RA
# backend may be unavailable (ipa-replica-install) or unusable
# due to RA Agent cert not yet created (ipa-ca-install).
entry = api.Backend.ldap2.get_entry(dn)
# If the attribute doesn't exist then the remote didn't
# enable RSN.
if 'ipacahsmconfiguration' in entry:
val = entry['ipacahsmconfiguration'][0]
(token_name, token_library_path) = val.split(';')
except (errors.NotFound, KeyError):
# if the entry doesn't exist then the remote doesn't support
# HSM so there is nothing to do.
pass
return (token_name, token_library_path)
def hsm_version():
"""Return True if PKI supports working HSM code
The caller is responsible for raising the exception.
"""
pki_version = pki.util.Version(pki.specification_version())
return pki_version >= pki.util.Version("11.5.0"), pki_version
def hsm_validator(token_name, token_library, token_password):
"""Do some basic validation of the HSM information provided.
- The local PKI server supports IPA HSM
- The token library exists
- The token name doesn't have a colon or semi-colon in it
- The token name exists after loading the library
- The token password works
- Super-simple test to see if the SELinux module is loaded
"""
if not token_name:
logger.debug("No token name, assuming not an HSM install")
return
if not token_password:
raise ValueError("No token password provided")
val, pki_version = hsm_version()
if val is False:
raise ValueError(
"HSM is not supported in PKI version %s" % pki_version
)
if ':' in token_name or ';' in token_name:
raise ValueError(
"Colon and semi-colon are not allowed in a token name."
)
if not os.path.exists(token_library):
raise ValueError(
"Token library path '%s' does not exist" % token_library
)
pkiuser = constants.PKI_USER
pkigroup = constants.PKI_GROUP
if 'libsofthsm' in token_library:
import grp
group = grp.getgrnam(constants.ODS_GROUP)
if str(constants.PKI_USER) in group.gr_mem:
pkigroup = constants.ODS_GROUP
with certdb.NSSDatabase() as tempnssdb:
tempnssdb.create_db(user=str(pkiuser), group=str(pkigroup))
# Try adding the token library to the temporary database in
# case it isn't already available. Ignore all errors.
command = [
paths.MODUTIL,
'-dbdir', '{}:{}'.format(tempnssdb.dbtype, tempnssdb.secdir),
'-nocertdb',
'-add', 'test',
'-libfile', token_library,
'-force',
]
# It may fail if p11-kit has already registered the library, that's
# ok.
ipautil.run(command, stdin='\n', cwd=tempnssdb.secdir,
runas=pkiuser, suplementary_groups=[pkigroup],
raiseonerr=False)
command = [
paths.MODUTIL,
'-dbdir', '{}:{}'.format(tempnssdb.dbtype, tempnssdb.secdir),
'-list',
'-force'
]
lines = ipautil.run(
command, cwd=tempnssdb.secdir, capture_output=True,
runas=pkiuser, suplementary_groups=[pkigroup]).output
found = False
token_line = f'token: {token_name}'
for line in lines.split('\n'):
if token_line in line.strip():
found = True
break
if not found:
raise ValueError(
"Token named '%s' was not found. Check permissions"
% token_name
)
pwdfile = ipautil.write_tmp_file(token_password)
os.fchown(pwdfile.fileno(), pkiuser.uid, pkigroup.gid)
args = [
paths.CERTUTIL,
"-d", '{}:{}'.format(tempnssdb.dbtype, tempnssdb.secdir),
"-K",
"-h", token_name,
"-f", pwdfile.name,
]
result = ipautil.run(args, cwd=tempnssdb.secdir,
runas=pkiuser,
suplementary_groups=[pkigroup],
capture_error=True, raiseonerr=False)
if result.returncode != 0 and len(result.error_output):
if 'SEC_ERROR_BAD_PASSWORD' in result.error_output:
raise ValueError('Invalid HSM token password')
else:
raise ValueError(
"Validating HSM password failed: %s" % result.error_output
)
# validate that the appropriate SELinux module is installed
# Only warn in case the expected paths don't match.
if 'nfast' in token_library:
module = 'ipa-nfast'
elif 'luna' in token_library:
module = 'ipa-luna'
else:
module = None
if module:
args = [paths.SEMODULE, "-l"]
result = ipautil.run(args, cwd=tempnssdb.secdir,
capture_output=True, raiseonerr=False)
if module not in result.output:
logger.info('\nWARNING: The associated SELinux module ,%s, '
'for this HSM was not detected.\nVerify '
'that the appropriate subpackage is installed '
'for this HSM\n', module)
def set_subject_base_in_config(subject_base):
entry_attrs = api.Backend.ldap2.get_ipa_config()
entry_attrs['ipacertificatesubjectbase'] = [str(subject_base)]
@@ -116,6 +321,24 @@ def print_ca_configuration(options):
def uninstall_check(options):
"""IPA needs to be running so pkidestroy can unregister CA"""
ca = cainstance.CAInstance(api.env.realm)
if not ca.is_installed():
return
result = ipautil.run([paths.IPACTL, 'status'],
raiseonerr=False)
if result.returncode not in [0, 4]:
try:
logger.info(
"Starting services to unregister CA from security domain")
ipautil.run([paths.IPACTL, 'start'])
except Exception:
logger.info("Re-starting IPA failed, continuing uninstall")
def uninstall_crl_check(options):
"""Check if the host is CRL generation master"""
# Skip the checks if the host is not a CA instance
ca = cainstance.CAInstance(api.env.realm)
@@ -156,6 +379,14 @@ def install_check(standalone, replica_config, options):
if replica_config is None:
options._subject_base = options.subject_base
options._ca_subject = options.ca_subject
options._random_serial_numbers = options.random_serial_numbers
token_name = options.token_name
token_library_path = options.token_library_path
if "setup_ca" in options.__dict__:
setup_ca = options.setup_ca
else:
# We got here through ipa-ca-install
setup_ca = True
else:
# during replica install, this gets invoked before local DS is
# available, so use the remote api.
@@ -165,6 +396,55 @@ def install_check(standalone, replica_config, options):
options._subject_base = str(replica_config.subject_base)
options._ca_subject = lookup_ca_subject(_api, options._subject_base)
options._random_serial_numbers = (
lookup_random_serial_number_version(_api) > 0
)
if options._random_serial_numbers and replica_config.setup_ca:
try:
random_serial_numbers_validator(
options._random_serial_numbers
)
except ValueError as e:
raise ScriptError(str(e))
(token_name, token_library_path) = lookup_hsm_configuration(_api)
# IPA version and dependency checking should prevent this but
# better to be safe and avoid a failed install.
if replica_config.setup_ca and token_name:
if not options.token_library_path:
options.token_library_path = token_library_path
setup_ca = replica_config.setup_ca
if setup_ca and token_name:
if (options.token_password_file and options.token_password):
raise ScriptError(
"token-password and token-password-file are mutually exclusive"
)
if options.token_password_file:
with open(options.token_password_file, "r") as fd:
options.token_password = fd.readline().strip()
if (
not options.token_password_file
and not options.token_password
):
if options.unattended:
raise ScriptError("HSM token password required")
token_password = installutils.read_password(
f"HSM token '{token_name}'", confirm=False
)
if token_password is None:
raise ScriptError("HSM token password required")
else:
options.token_password = token_password
try:
hsm_validator(
token_name, token_library_path,
options.token_password)
except ValueError as e:
raise ScriptError(str(e))
if replica_config is not None and not replica_config.setup_ca:
return
@@ -294,6 +574,7 @@ def install_step_0(standalone, replica_config, options, custodia):
else:
cert_file = None
cert_chain_file = None
token_name = options.token_name
pkcs12_info = None
master_host = None
@@ -302,10 +583,16 @@ def install_step_0(standalone, replica_config, options, custodia):
ra_only = False
promote = False
else:
cafile = os.path.join(replica_config.dir, 'cacert.p12')
custodia.get_ca_keys(
cafile,
replica_config.dirman_password)
_api = api if standalone else options._remote_api
(token_name, _token_library_path) = lookup_hsm_configuration(api)
if not token_name:
cafile = os.path.join(replica_config.dir, 'cacert.p12')
if replica_config.setup_ca:
custodia.get_ca_keys(
cafile,
replica_config.dirman_password)
else:
cafile = None
ca_signing_algorithm = None
ca_type = None
@@ -353,6 +640,10 @@ def install_step_0(standalone, replica_config, options, custodia):
promote=promote,
use_ldaps=use_ldaps,
pki_config_override=options.pki_config_override,
random_serial_numbers=options._random_serial_numbers,
token_name=token_name,
token_library_path=options.token_library_path,
token_password=options.token_password,
)
@@ -435,6 +726,7 @@ def uninstall():
class CASigningAlgorithm(enum.Enum):
SHA1_WITH_RSA = 'SHA1withRSA'
SHA_256_WITH_RSA = 'SHA256withRSA'
SHA_384_WITH_RSA = 'SHA384withRSA'
SHA_512_WITH_RSA = 'SHA512withRSA'
@@ -490,7 +782,6 @@ class CAInstallInterface(dogtag.DogtagInstallInterface,
external_ca_profile = master_install_only(external_ca_profile)
external_cert_files = knob(
# pylint: disable=invalid-sequence-index
typing.List[str], None,
description=("File containing the IPA CA certificate and the external "
"CA certificate chain"),
@@ -545,3 +836,13 @@ class CAInstallInterface(dogtag.DogtagInstallInterface,
)
skip_schema_check = enroll_only(skip_schema_check)
skip_schema_check = replica_install_only(skip_schema_check)
random_serial_numbers = knob(
None,
description="Enable random serial numbers",
)
random_serial_numbers = master_install_only(random_serial_numbers)
@random_serial_numbers.validator
def random_serial_numbers(self, value):
random_serial_numbers_validator(value)