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

@@ -34,6 +34,7 @@ import tempfile
from ipalib import api
from ipalib.constants import CA_DBUS_TIMEOUT
from ipapython.dn import DN
from ipapython.ipautil import Sleeper
from ipaplatform.paths import paths
from ipaplatform import services
@@ -350,7 +351,8 @@ def request_and_wait_for_cert(
certpath, subject, principal, nickname=None, passwd_fname=None,
dns=None, ca='IPA', profile=None,
pre_command=None, post_command=None, storage='NSSDB', perms=None,
resubmit_timeout=0, stop_tracking_on_error=False):
resubmit_timeout=0, stop_tracking_on_error=False,
nss_user=None):
"""Request certificate, wait and possibly resubmit failing requests
Submit a cert request to certmonger and wait until the request has
@@ -365,13 +367,17 @@ def request_and_wait_for_cert(
"""
req_id = request_cert(
certpath, subject, principal, nickname, passwd_fname, dns, ca,
profile, pre_command, post_command, storage, perms
profile, pre_command, post_command, storage, perms, nss_user
)
# Don't wait longer than resubmit timeout if it is configured
certmonger_timeout = api.env.certmonger_wait_timeout
if resubmit_timeout and resubmit_timeout < certmonger_timeout:
certmonger_timeout = resubmit_timeout
deadline = time.time() + resubmit_timeout
while True: # until success, timeout, or error
try:
state = wait_for_request(req_id, api.env.http_timeout)
state = wait_for_request(req_id, certmonger_timeout)
except RuntimeError as e:
logger.debug("wait_for_request raised %s", e)
state = 'TIMEOUT'
@@ -414,7 +420,8 @@ def request_and_wait_for_cert(
def request_cert(
certpath, subject, principal, nickname=None, passwd_fname=None,
dns=None, ca='IPA', profile=None,
pre_command=None, post_command=None, storage='NSSDB', perms=None):
pre_command=None, post_command=None, storage='NSSDB', perms=None,
nss_user=None):
"""
Execute certmonger to request a server certificate.
@@ -445,12 +452,16 @@ def request_cert(
request_parameters["KEY_NICKNAME"] = nickname
if principal:
request_parameters['PRINCIPAL'] = [principal]
if dns is not None and len(dns) > 0:
if dns:
if not isinstance(dns, (list, tuple)):
raise TypeError(dns)
request_parameters['DNS'] = dns
if passwd_fname:
request_parameters['KEY_PIN_FILE'] = passwd_fname
if profile:
request_parameters['ca-profile'] = profile
if nss_user:
request_parameters['nss-user'] = nss_user
certmonger_cmd_template = paths.CERTMONGER_COMMAND_TEMPLATE
if pre_command:
@@ -482,7 +493,7 @@ def request_cert(
def start_tracking(
certpath, ca='IPA', nickname=None, pin=None, pinfile=None,
pre_command=None, post_command=None, profile=None, storage="NSSDB",
token_name=None, dns=None):
token_name=None, dns=None, nss_user=None):
"""
Tell certmonger to track the given certificate in either a file or an NSS
database. The certificate access can be protected by a password_file.
@@ -519,6 +530,8 @@ def start_tracking(
Hardware token name for HSM support
:param dns:
List of DNS names
:param nss_user:
login of the private key owner
:returns: certificate tracking nickname.
"""
if storage == 'FILE':
@@ -565,6 +578,8 @@ def start_tracking(
params['cert-token'] = token_name
if dns is not None and len(dns) > 0:
params['DNS'] = dns
if nss_user:
params['nss-user'] = nss_user
result = cm.obj_if.add_request(params)
try:
@@ -746,6 +761,8 @@ def get_pin(token="internal"):
The caller is expected to handle any exceptions raised.
"""
if token and token != 'internal':
token = 'hardware-' + token
with open(paths.PKI_TOMCAT_PASSWORD_CONF, 'r') as f:
for line in f:
(tok, pin) = line.split('=', 1)
@@ -772,14 +789,20 @@ def check_state(dirs):
def wait_for_request(request_id, timeout=120):
for _i in range(0, timeout, 5):
state = get_request_value(request_id, 'status')
logger.debug("certmonger request is in state %r", state)
if state in ('CA_REJECTED', 'CA_UNREACHABLE', 'CA_UNCONFIGURED',
'NEED_GUIDANCE', 'NEED_CA', 'MONITORING'):
sleep = Sleeper(
sleep=0.5, # getcert.c:waitfor() uses 125ms
timeout=timeout,
raises=RuntimeError("request timed out")
)
last_state = None
while True:
state = str(get_request_value(request_id, 'status'))
if state != last_state:
logger.debug("certmonger request is in state %r", state)
if state in {'CA_REJECTED', 'CA_UNREACHABLE', 'CA_UNCONFIGURED',
'NEED_GUIDANCE', 'NEED_CA', 'MONITORING'}:
break
time.sleep(5)
else:
raise RuntimeError("request timed out")
last_state = state
sleep()
return state

View File

@@ -21,7 +21,6 @@ class HostNameInstallInterface(service.ServiceInstallInterface):
"""
ip_addresses = knob(
# pylint: disable=invalid-sequence-index
typing.List[CheckedIPAddress], None,
description="Specify IP address that should be added to DNS. This "
"option can be used multiple times",

View File

@@ -1,127 +1,20 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
# Copyright (C) 2024 FreeIPA Contributors see COPYING for license
#
from __future__ import absolute_import
# code was moved to ipalib.kinit. This module is now an alias
__all__ = (
"validate_principal",
"kinit_keytab",
"kinit_password",
"kinit_armor",
"kinit_pkinit",
)
import logging
import os
import time
import gssapi
from ipaplatform.paths import paths
from ipapython.ipautil import run
logger = logging.getLogger(__name__)
# Cannot contact any KDC for requested realm
KRB5_KDC_UNREACH = 2529639068
# A service is not available that s required to process the request
KRB5KDC_ERR_SVC_UNAVAILABLE = 2529638941
def kinit_keytab(principal, keytab, ccache_name, config=None, attempts=1):
"""
Given a ccache_path, keytab file and a principal kinit as that user.
The optional parameter 'attempts' specifies how many times the credential
initialization should be attempted in case of non-responsive KDC.
"""
errors_to_retry = {KRB5KDC_ERR_SVC_UNAVAILABLE,
KRB5_KDC_UNREACH}
logger.debug("Initializing principal %s using keytab %s",
principal, keytab)
logger.debug("using ccache %s", ccache_name)
for attempt in range(1, attempts + 1):
old_config = os.environ.get('KRB5_CONFIG')
if config is not None:
os.environ['KRB5_CONFIG'] = config
else:
os.environ.pop('KRB5_CONFIG', None)
try:
name = gssapi.Name(principal, gssapi.NameType.kerberos_principal)
store = {'ccache': ccache_name,
'client_keytab': keytab}
cred = gssapi.Credentials(name=name, store=store, usage='initiate')
logger.debug("Attempt %d/%d: success", attempt, attempts)
return cred
except gssapi.exceptions.GSSError as e:
if e.min_code not in errors_to_retry: # pylint: disable=no-member
raise
logger.debug("Attempt %d/%d: failed: %s", attempt, attempts, e)
if attempt == attempts:
logger.debug("Maximum number of attempts (%d) reached",
attempts)
raise
logger.debug("Waiting 5 seconds before next retry")
time.sleep(5)
finally:
if old_config is not None:
os.environ['KRB5_CONFIG'] = old_config
else:
os.environ.pop('KRB5_CONFIG', None)
def kinit_password(principal, password, ccache_name, config=None,
armor_ccache_name=None, canonicalize=False,
enterprise=False, lifetime=None):
"""
perform interactive kinit as principal using password. If using FAST for
web-based authentication, use armor_ccache_path to specify http service
ccache.
"""
logger.debug("Initializing principal %s using password", principal)
args = [paths.KINIT, principal, '-c', ccache_name]
if armor_ccache_name is not None:
logger.debug("Using armor ccache %s for FAST webauth",
armor_ccache_name)
args.extend(['-T', armor_ccache_name])
if lifetime:
args.extend(['-l', lifetime])
if canonicalize:
logger.debug("Requesting principal canonicalization")
args.append('-C')
if enterprise:
logger.debug("Using enterprise principal")
args.append('-E')
env = {'LC_ALL': 'C'}
if config is not None:
env['KRB5_CONFIG'] = config
# this workaround enables us to capture stderr and put it
# into the raised exception in case of unsuccessful authentication
result = run(args, stdin=password, env=env, raiseonerr=False,
capture_error=True)
if result.returncode:
raise RuntimeError(result.error_output)
def kinit_armor(ccache_name, pkinit_anchors=None):
"""
perform anonymous pkinit to obtain anonymous ticket to be used as armor
for FAST.
:param ccache_name: location of the armor ccache
:param pkinit_anchor: if not None, the location of PKINIT anchor file to
use. Otherwise the value from Kerberos client library configuration is
used
:raises: CalledProcessError if the anonymous PKINIT fails
"""
logger.debug("Initializing anonymous ccache")
env = {'LC_ALL': 'C'}
args = [paths.KINIT, '-n', '-c', ccache_name]
if pkinit_anchors is not None:
for pkinit_anchor in pkinit_anchors:
args.extend(['-X', 'X509_anchors=FILE:{}'.format(pkinit_anchor)])
# this workaround enables us to capture stderr and put it
# into the raised exception in case of unsuccessful authentication
run(args, env=env, raiseonerr=True, capture_error=True)
from ..kinit import (
validate_principal,
kinit_keytab,
kinit_password,
kinit_armor,
kinit_pkinit,
)

View File

@@ -115,7 +115,6 @@ class ServiceInstallInterface(common.Installable,
validate_domain_name(value)
servers = knob(
# pylint: disable=invalid-sequence-index
typing.List[str], None,
description="FQDN of IPA server",
cli_names='--server',
@@ -143,7 +142,6 @@ class ServiceInstallInterface(common.Installable,
)
ca_cert_files = knob(
# pylint: disable=invalid-sequence-index
typing.List[str], None,
description="load the CA certificate from this file",
cli_names='--ca-cert-file',