Import Upstream version 4.12.4
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user