From cf84a22228460957f578ac102f02516febe13f92 Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Fri, 13 Dec 2024 13:42:36 +0200 Subject: [PATCH 1/7] Unify use of option parsers Do not use direct optparse references, instead import IPAOptionParser Signed-off-by: Alexander Bokovoy --- install/tools/ipa-adtrust-install.in | 4 +--- install/tools/ipa-managed-entries.in | 3 ++- ipaclient/install/ipa_client_automount.py | 4 ++-- ipaclient/install/ipa_client_samba.py | 4 ++-- ipalib/cli.py | 21 ++++++++++++--------- ipalib/plugable.py | 8 ++++---- ipapython/admintool.py | 3 +-- ipapython/config.py | 18 +++++++++++------- ipapython/install/cli.py | 9 ++++----- ipaserver/install/ipa_acme_manage.py | 6 ++---- ipaserver/install/ipa_backup.py | 5 ++--- ipaserver/install/ipa_cacert_manage.py | 9 ++++----- ipaserver/install/ipa_kra_install.py | 5 ++--- ipaserver/install/ipa_restore.py | 5 ++--- ipaserver/install/ipa_server_certinstall.py | 7 +++---- ipatests/i18n.py | 8 ++++---- makeapi.in | 5 ++--- 17 files changed, 60 insertions(+), 64 deletions(-) diff --git a/install/tools/ipa-adtrust-install.in b/install/tools/ipa-adtrust-install.in index cb2b78e50..e7b0e3692 100644 --- a/install/tools/ipa-adtrust-install.in +++ b/install/tools/ipa-adtrust-install.in @@ -29,8 +29,6 @@ import sys import six -from optparse import SUPPRESS_HELP # pylint: disable=deprecated-module - from ipalib.install import sysrestore from ipaserver.install import adtrust, service from ipaserver.install.installutils import ( @@ -41,7 +39,7 @@ from ipapython.admintool import ScriptError from ipapython import version from ipapython import ipautil from ipalib import api, errors, krb_utils -from ipapython.config import IPAOptionParser +from ipapython.config import IPAOptionParser, SUPPRESS_HELP from ipaplatform.paths import paths from ipapython.ipa_log_manager import standard_logging_setup diff --git a/install/tools/ipa-managed-entries.in b/install/tools/ipa-managed-entries.in index e9be41b7a..e3f121943 100644 --- a/install/tools/ipa-managed-entries.in +++ b/install/tools/ipa-managed-entries.in @@ -39,7 +39,8 @@ logger = logging.getLogger(os.path.basename(__file__)) def parse_options(): usage = "%prog [options] \n" usage += "%prog [options]\n" - parser = OptionParser(usage=usage, formatter=config.IPAFormatter()) + parser = config.IPAOptionParser(usage=usage, + formatter=config.IPAFormatter()) parser.add_option("-d", "--debug", action="store_true", dest="debug", help="Display debugging information about the update(s)") diff --git a/ipaclient/install/ipa_client_automount.py b/ipaclient/install/ipa_client_automount.py index 4439932bd..9f49ff9ed 100644 --- a/ipaclient/install/ipa_client_automount.py +++ b/ipaclient/install/ipa_client_automount.py @@ -34,7 +34,6 @@ import SSSDConfig from six.moves.urllib.parse import urlsplit -from optparse import OptionParser # pylint: disable=deprecated-module from ipapython import ipachangeconf from ipaclient import discovery from ipaclient.install.client import ( @@ -52,6 +51,7 @@ from ipaplatform.tasks import tasks from ipaplatform import services from ipaplatform.paths import paths from ipapython.admintool import ScriptError +from ipapython.config import IPAOptionParser logger = logging.getLogger(os.path.basename(__file__)) @@ -59,7 +59,7 @@ logger = logging.getLogger(os.path.basename(__file__)) def parse_options(): usage = "%prog [options]\n" - parser = OptionParser(usage=usage) + parser = IPAOptionParser(usage=usage) parser.add_option("--server", dest="server", help="FQDN of IPA server") parser.add_option( "--location", diff --git a/ipaclient/install/ipa_client_samba.py b/ipaclient/install/ipa_client_samba.py index 81d670c34..5c33abb4c 100755 --- a/ipaclient/install/ipa_client_samba.py +++ b/ipaclient/install/ipa_client_samba.py @@ -9,7 +9,6 @@ import logging import os import gssapi from urllib.parse import urlsplit -from optparse import OptionParser # pylint: disable=deprecated-module from contextlib import contextmanager from ipaclient import discovery @@ -31,6 +30,7 @@ from ipaplatform.constants import constants from ipaplatform import services from ipapython.admintool import ScriptError from samba import generate_random_password +from ipapython.config import IPAOptionParser logger = logging.getLogger(os.path.basename(__file__)) logger.setLevel(logging.DEBUG) @@ -68,7 +68,7 @@ def use_api_as_principal(principal, keytab): def parse_options(): usage = "%prog [options]\n" - parser = OptionParser(usage=usage) + parser = IPAOptionParser(usage=usage) parser.add_option( "--server", dest="server", diff --git a/ipalib/cli.py b/ipalib/cli.py index d9c2ac165..667b213fd 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -30,7 +30,6 @@ import textwrap import sys import getpass import code -import optparse # pylint: disable=deprecated-module import os import pprint import fcntl @@ -71,6 +70,8 @@ from ipalib.text import _ from ipalib import api from ipapython.dnsutil import DNSName from ipapython.admintool import ScriptError +from ipapython.config import (IPAOptionParser, IPAFormatter, + OptionGroup, make_option) import datetime @@ -1121,7 +1122,8 @@ class Collector: def __todict__(self): return dict(self.__options) -class CLIOptionParserFormatter(optparse.IndentedHelpFormatter): + +class CLIOptionParserFormatter(IPAFormatter): def format_argument(self, name, help_string): result = [] opt_width = self.help_position - self.current_indent - 2 @@ -1141,7 +1143,8 @@ class CLIOptionParserFormatter(optparse.IndentedHelpFormatter): result.append("\n") return "".join(result) -class CLIOptionParser(optparse.OptionParser): + +class CLIOptionParser(IPAOptionParser): """ This OptionParser subclass adds an ability to print positional arguments in CLI help. Custom formatter is used to format the argument @@ -1151,13 +1154,13 @@ class CLIOptionParser(optparse.OptionParser): self._arguments = [] if 'formatter' not in kwargs: kwargs['formatter'] = CLIOptionParserFormatter() - optparse.OptionParser.__init__(self, *args, **kwargs) + IPAOptionParser.__init__(self, *args, **kwargs) def format_option_help(self, formatter=None): """ Prepend argument help to standard OptionParser's option help """ - option_help = optparse.OptionParser.format_option_help(self, formatter) + option_help = IPAOptionParser.format_option_help(self, formatter) if isinstance(formatter, CLIOptionParserFormatter): heading = unicode(_("Positional arguments")) @@ -1272,7 +1275,7 @@ class cli(backend.Executioner): """Get or create an option group for the given name""" option_group = option_groups.get(group_name) if option_group is None: - option_group = optparse.OptionGroup(parser, group_name) + option_group = OptionGroup(parser, group_name) parser.add_option_group(option_group) option_groups[group_name] = option_group return option_group @@ -1298,7 +1301,7 @@ class cli(backend.Executioner): option_names = ['--%s' % cli_name] if option.cli_short_name: option_names.append('-%s' % option.cli_short_name) - opt = optparse.make_option(*option_names, **kw) + opt = make_option(*option_names, **kw) if option.option_group is None: parser.add_option(opt) else: @@ -1312,7 +1315,7 @@ class cli(backend.Executioner): group = _get_option_group(unicode(_('Deprecated options'))) for alias in option.deprecated_cli_aliases: name = '--%s' % alias - group.add_option(optparse.make_option(name, **new_kw)) + group.add_option(make_option(name, **new_kw)) for arg in cmd.args(): name = self.__get_arg_name(arg, format_name=False) @@ -1442,7 +1445,7 @@ class cli(backend.Executioner): ) -class IPAHelpFormatter(optparse.IndentedHelpFormatter): +class IPAHelpFormatter(IPAFormatter): """Formatter suitable for printing IPA command help The default help formatter reflows text to fit the terminal, but it diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 2e2861df0..a87e6e891 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -33,7 +33,6 @@ import sys import threading import os from os import path -import optparse # pylint: disable=deprecated-module import textwrap import collections import importlib @@ -47,6 +46,7 @@ from ipalib.util import classproperty from ipalib.base import ReadOnly, lock, islocked from ipalib.constants import DEFAULT_CONFIG from ipapython import ipa_log_manager, ipautil +from ipapython.config import IPAOptionParser, IPAFormatter from ipapython.ipa_log_manager import ( LOGGING_FORMAT_FILE, LOGGING_FORMAT_STDERR) @@ -526,7 +526,7 @@ class API(ReadOnly): def build_global_parser(self, parser=None, context=None): """ - Add global options to an optparse.OptionParser instance. + Add global options to an IPAOptionParser instance. """ def config_file_callback(option, opt, value, parser): if not os.path.isfile(value): @@ -536,7 +536,7 @@ class API(ReadOnly): parser.values.conf = value if parser is None: - parser = optparse.OptionParser( + parser = IPAOptionParser( add_help_option=False, formatter=IPAHelpFormatter(), usage='%prog [global-options] COMMAND [command-options]', @@ -821,7 +821,7 @@ class API(ReadOnly): return self.__next[plugin] -class IPAHelpFormatter(optparse.IndentedHelpFormatter): +class IPAHelpFormatter(IPAFormatter): def format_epilog(self, epilog): text_width = self.width - self.current_indent indent = " " * self.current_indent diff --git a/ipapython/admintool.py b/ipapython/admintool.py index fdb4400d8..dff9112eb 100644 --- a/ipapython/admintool.py +++ b/ipapython/admintool.py @@ -26,7 +26,6 @@ import logging import sys import os import traceback -from optparse import OptionGroup # pylint: disable=deprecated-module from ipaplatform.osinfo import osinfo from ipapython import version @@ -113,7 +112,7 @@ class AdminTool: :param parser: The parser to add options to :param debug_option: Add a --debug option as an alias to --verbose """ - group = OptionGroup(parser, "Logging and output options") + group = config.OptionGroup(parser, "Logging and output options") group.add_option("-v", "--verbose", dest="verbose", default=False, action="store_true", help="print debugging information") if debug_option: diff --git a/ipapython/config.py b/ipapython/config.py index f53d0f998..7af4dfdeb 100644 --- a/ipapython/config.py +++ b/ipapython/config.py @@ -18,9 +18,9 @@ # from __future__ import absolute_import -# pylint: disable=deprecated-module -from optparse import ( - Option, Values, OptionParser, IndentedHelpFormatter, OptionValueError) +# pylint: disable=deprecated-module, disable=unused-import +from optparse import (Option, Values, OptionGroup, OptionParser, SUPPRESS_HELP, + IndentedHelpFormatter, OptionValueError, make_option) # pylint: enable=deprecated-module from copy import copy from configparser import ConfigParser as SafeConfigParser @@ -113,10 +113,14 @@ class IPAOptionParser(OptionParser): description=None, formatter=None, add_help_option=True, - prog=None): - OptionParser.__init__(self, usage, option_list, option_class, - version, conflict_handler, description, - formatter, add_help_option, prog) + prog=None, + epilog=None): + OptionParser.__init__(self, usage=usage, option_list=option_list, + option_class=option_class, version=version, + conflict_handler=conflict_handler, + description=description, formatter=formatter, + add_help_option=add_help_option, prog=prog, + epilog=epilog) def get_safe_opts(self, opts): """ diff --git a/ipapython/install/cli.py b/ipapython/install/cli.py index ab212be4e..a048b3c7c 100644 --- a/ipapython/install/cli.py +++ b/ipapython/install/cli.py @@ -9,12 +9,11 @@ Command line support. import collections import enum import logging -import optparse # pylint: disable=deprecated-module import signal import six -from ipapython import admintool +from ipapython import admintool, config from ipapython.ipa_log_manager import standard_logging_setup from ipapython.ipautil import (CheckedIPAddress, CheckedIPAddressLoopback, private_ccache) @@ -158,7 +157,7 @@ class ConfigureTool(admintool.AdminTool): try: opt_group = groups[group_cls] except KeyError: - opt_group = groups[group_cls] = optparse.OptionGroup( + opt_group = groups[group_cls] = config.OptionGroup( parser, "{0} options".format(group_cls.description)) parser.add_option_group(opt_group) @@ -232,7 +231,7 @@ class ConfigureTool(admintool.AdminTool): if not hidden: help = knob_cls.description else: - help = optparse.SUPPRESS_HELP + help = config.SUPPRESS_HELP opt_group.add_option( *opt_strs, @@ -256,7 +255,7 @@ class ConfigureTool(admintool.AdminTool): # fake option parser to parse positional arguments # (because optparse does not support positional argument parsing) - fake_option_parser = optparse.OptionParser() + fake_option_parser = config.IPAOptionParser() self.add_options(fake_option_parser, True) fake_option_map = {option.dest: option diff --git a/ipaserver/install/ipa_acme_manage.py b/ipaserver/install/ipa_acme_manage.py index dc2359f49..0decab394 100644 --- a/ipaserver/install/ipa_acme_manage.py +++ b/ipaserver/install/ipa_acme_manage.py @@ -7,14 +7,12 @@ import enum import pki.util import logging -from optparse import OptionGroup # pylint: disable=deprecated-module - from ipalib import api, errors, x509 from ipalib import _ from ipalib.facts import is_ipa_configured from ipaplatform.paths import paths from ipapython.admintool import AdminTool -from ipapython import cookie, dogtag +from ipapython import cookie, dogtag, config from ipapython.ipautil import run from ipapython.certdb import NSSDatabase, EXTERNAL_CA_TRUST_FLAGS from ipaserver.install import cainstance @@ -143,7 +141,7 @@ class IPAACMEManage(AdminTool): @classmethod def add_options(cls, parser): - group = OptionGroup(parser, 'Pruning') + group = config.OptionGroup(parser, 'Pruning') group.add_option( "--enable", dest="enable", action="store_true", default=False, help="Enable certificate pruning") diff --git a/ipaserver/install/ipa_backup.py b/ipaserver/install/ipa_backup.py index 982e5dfc4..b6af63813 100644 --- a/ipaserver/install/ipa_backup.py +++ b/ipaserver/install/ipa_backup.py @@ -20,7 +20,6 @@ from __future__ import absolute_import, print_function import logging -import optparse # pylint: disable=deprecated-module import os import shutil import sys @@ -32,7 +31,7 @@ import six from ipaplatform.paths import paths from ipaplatform import services from ipalib import api, errors -from ipapython import version +from ipapython import version, config from ipapython.ipautil import run, write_tmp_file from ipapython import admintool, certdb from ipapython.dn import DN @@ -245,7 +244,7 @@ class Backup(admintool.AdminTool): parser.add_option( "--gpg-keyring", dest="gpg_keyring", - help=optparse.SUPPRESS_HELP) + help=config.SUPPRESS_HELP) parser.add_option( "--gpg", dest="gpg", action="store_true", default=False, help="Encrypt the backup") diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py index f6ab736fa..048245237 100644 --- a/ipaserver/install/ipa_cacert_manage.py +++ b/ipaserver/install/ipa_cacert_manage.py @@ -22,14 +22,13 @@ from __future__ import print_function, absolute_import import datetime import logging import os -from optparse import OptionGroup # pylint: disable=deprecated-module import gssapi from ipalib.constants import ( RENEWAL_CA_NAME, RENEWAL_REUSE_CA_NAME, RENEWAL_SELFSIGNED_CA_NAME, IPA_CA_CN) from ipalib.install import certmonger, certstore -from ipapython import admintool, ipautil +from ipapython import admintool, ipautil, config from ipapython.certdb import (EMPTY_TRUST_FLAGS, EXTERNAL_CA_TRUST_FLAGS, TrustFlags, @@ -61,7 +60,7 @@ class CACertManage(admintool.AdminTool): "-p", "--password", dest='password', help="Directory Manager password") - renew_group = OptionGroup(parser, "Renew options") + renew_group = config.OptionGroup(parser, "Renew options") renew_group.add_option( "--self-signed", dest='self_signed', action='store_true', @@ -89,7 +88,7 @@ class CACertManage(admintool.AdminTool): "certificate chain") parser.add_option_group(renew_group) - install_group = OptionGroup(parser, "Install options") + install_group = config.OptionGroup(parser, "Install options") install_group.add_option( "-n", "--nickname", dest='nickname', help="Nickname for the certificate") @@ -98,7 +97,7 @@ class CACertManage(admintool.AdminTool): help="Trust flags for the certificate in certutil format") parser.add_option_group(install_group) - delete_group = OptionGroup(parser, "Delete options") + delete_group = config.OptionGroup(parser, "Delete options") delete_group.add_option( "-f", "--force", action='store_true', help="Force removing the CA even if chain validation fails") diff --git a/ipaserver/install/ipa_kra_install.py b/ipaserver/install/ipa_kra_install.py index 3e4cd67fa..8a09179f7 100644 --- a/ipaserver/install/ipa_kra_install.py +++ b/ipaserver/install/ipa_kra_install.py @@ -22,13 +22,12 @@ from __future__ import print_function, absolute_import import logging import sys import tempfile -from optparse import SUPPRESS_HELP # pylint: disable=deprecated-module from textwrap import dedent from ipalib import api from ipalib.constants import DOMAIN_LEVEL_1 from ipaplatform.paths import paths -from ipapython import admintool +from ipapython import admintool, config from ipaserver.install import service from ipaserver.install import cainstance from ipaserver.install import custodiainstance @@ -73,7 +72,7 @@ class KRAInstall(admintool.AdminTool): parser.add_option( "--uninstall", dest="uninstall", action="store_true", default=False, - help=SUPPRESS_HELP) + help=config.SUPPRESS_HELP) parser.add_option( "--pki-config-override", dest="pki_config_override", diff --git a/ipaserver/install/ipa_restore.py b/ipaserver/install/ipa_restore.py index 57ad8dd05..8d75a0e6b 100644 --- a/ipaserver/install/ipa_restore.py +++ b/ipaserver/install/ipa_restore.py @@ -20,7 +20,6 @@ from __future__ import absolute_import, print_function import logging -import optparse # pylint: disable=deprecated-module import os import shutil import sys @@ -34,7 +33,7 @@ import six from ipaclient.install.client import update_ipa_nssdb from ipalib import api, errors from ipalib.constants import FQDN -from ipapython import version, ipautil +from ipapython import version, ipautil, config from ipapython.ipautil import run, user_input from ipapython import admintool, certdb from ipapython.dn import DN @@ -190,7 +189,7 @@ class Restore(admintool.AdminTool): help="Directory Manager password") parser.add_option( "--gpg-keyring", dest="gpg_keyring", - help=optparse.SUPPRESS_HELP) + help=config.SUPPRESS_HELP) parser.add_option( "--data", dest="data_only", action="store_true", default=False, help="Restore only the data") diff --git a/ipaserver/install/ipa_server_certinstall.py b/ipaserver/install/ipa_server_certinstall.py index e29f00ec3..e9f680b1d 100644 --- a/ipaserver/install/ipa_server_certinstall.py +++ b/ipaserver/install/ipa_server_certinstall.py @@ -22,12 +22,11 @@ from __future__ import print_function, absolute_import import os import os.path import tempfile -import optparse # pylint: disable=deprecated-module from ipalib import x509 from ipalib.install import certmonger from ipaplatform.paths import paths -from ipapython import admintool, dogtag +from ipapython import admintool, dogtag, config from ipapython.certdb import NSSDatabase, get_ca_nickname from ipapython.dn import DN from ipapython import ipaldap @@ -65,8 +64,8 @@ class ServerCertInstall(admintool.AdminTool): help="The password of the PKCS#12 file") parser.add_option( "--dirsrv_pin", "--http_pin", - dest="pin", - help=optparse.SUPPRESS_HELP) + dest="pin", sensitive=True, + help=config.SUPPRESS_HELP) parser.add_option( "--cert-name", dest="cert_name", metavar="NAME", diff --git a/ipatests/i18n.py b/ipatests/i18n.py index 49f5c4c32..57915c286 100644 --- a/ipatests/i18n.py +++ b/ipatests/i18n.py @@ -22,7 +22,6 @@ from __future__ import print_function # WARNING: Do not import ipa modules, this is also used as a # stand-alone script (invoked from po Makefile). -import optparse # pylint: disable=deprecated-module import sys import gettext import re @@ -30,6 +29,7 @@ import os import traceback import polib from collections import namedtuple +from ipapython import config import six @@ -722,9 +722,9 @@ usage =''' def main(): global verbose, print_traceback, pedantic, show_strings - parser = optparse.OptionParser(usage=usage) + parser = config.IPAOptionParser(usage=usage) - mode_group = optparse.OptionGroup(parser, 'Operational Mode', + mode_group = config.OptionGroup(parser, 'Operational Mode', 'You must select one these modes to run in') mode_group.add_option('-g', '--test-gettext', action='store_const', const='test_gettext', dest='mode', @@ -748,7 +748,7 @@ def main(): parser.add_option('--traceback', action='store_true', dest='print_traceback', default=False, help='print the traceback when an exception occurs') - param_group = optparse.OptionGroup(parser, 'Run Time Parameters', + param_group = config.OptionGroup(parser, 'Run Time Parameters', 'These may be used to modify the run time defaults') param_group.add_option('--test-lang', action='store', dest='test_lang', default='test', diff --git a/makeapi.in b/makeapi.in index a801b9253..8fc87d23d 100644 --- a/makeapi.in +++ b/makeapi.in @@ -38,6 +38,7 @@ from ipalib.parameters import Param from ipalib.output import Output from ipalib.text import Gettext, NGettext, ConcatenatedLazyText from ipalib.capabilities import capabilities +from ipapython import config API_FILE='API.txt' @@ -84,9 +85,7 @@ OUTPUT_IGNORED_ATTRIBUTES = ( ) def parse_options(): - from optparse import OptionParser # pylint: disable=deprecated-module - - parser = OptionParser() + parser = config.IPAOptionParser() parser.add_option("--validate", dest="validate", action="store_true", default=False, help="Validate the API vs the stored API") From 3b38efe75865d0696829b4f26572575a8e74ddce Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Fri, 8 Nov 2024 14:59:20 +0200 Subject: [PATCH 2/7] ipa tools: remove sensitive material from the commandline When command line tools accept passwords, remove them from the command line so that they don't get visible in '/proc/pid/commandline'. There is no common method to access the original ARGV vector and modify it from Python. Since this mostly affects Linux systems where IPA services run, we expect use of GNU libc and thus can rely on internal glibc symbols. If they aren't available, the code will skip removing passwords. Fixes: CVE-2024-11029 Signed-off-by: Alexander Bokovoy --- .../com.redhat.idm.trust-fetch-domains.in | 5 ++- install/tools/ipa-adtrust-install.in | 3 +- install/tools/ipa-ca-install.in | 2 + install/tools/ipa-compat-manage.in | 6 ++- install/tools/ipa-csreplica-manage.in | 12 +++--- install/tools/ipa-managed-entries.in | 5 ++- install/tools/ipa-replica-conncheck.in | 7 ++-- install/tools/ipa-replica-manage.in | 10 +++-- ipapython/admintool.py | 40 +++++++++++++++++++ ipaserver/install/ipa_migrate.py | 17 +++++++- ipaserver/install/ipa_restore.py | 2 +- ipaserver/install/ipa_server_certinstall.py | 2 +- 12 files changed, 90 insertions(+), 21 deletions(-) diff --git a/install/oddjob/com.redhat.idm.trust-fetch-domains.in b/install/oddjob/com.redhat.idm.trust-fetch-domains.in index 45c1f1463..b86be0212 100644 --- a/install/oddjob/com.redhat.idm.trust-fetch-domains.in +++ b/install/oddjob/com.redhat.idm.trust-fetch-domains.in @@ -15,6 +15,7 @@ import six import gssapi from ipalib.install.kinit import kinit_keytab, kinit_password +from ipapython.admintool import admin_cleanup_global_argv if six.PY3: unicode = str @@ -52,11 +53,13 @@ def parse_options(): "--password", action="store", dest="password", - help="Display debugging information", + help="Password for Active Directory administrator", + sensitive=True ) options, args = parser.parse_args() safe_options = parser.get_safe_opts(options) + admin_cleanup_global_argv(parser, options, sys.argv) # We only use first argument of the passed args but as D-BUS interface # in oddjobd cannot expose optional, we fill in empty slots from IPA side diff --git a/install/tools/ipa-adtrust-install.in b/install/tools/ipa-adtrust-install.in index e7b0e3692..1efccdb67 100644 --- a/install/tools/ipa-adtrust-install.in +++ b/install/tools/ipa-adtrust-install.in @@ -35,7 +35,7 @@ from ipaserver.install.installutils import ( read_password, check_server_configuration, run_script) -from ipapython.admintool import ScriptError +from ipapython.admintool import ScriptError, admin_cleanup_global_argv from ipapython import version from ipapython import ipautil from ipalib import api, errors, krb_utils @@ -93,6 +93,7 @@ def parse_options(): options, _args = parser.parse_args() safe_options = parser.get_safe_opts(options) + admin_cleanup_global_argv(parser, options, sys.argv) return safe_options, options diff --git a/install/tools/ipa-ca-install.in b/install/tools/ipa-ca-install.in index 9f3d16669..b437e761f 100644 --- a/install/tools/ipa-ca-install.in +++ b/install/tools/ipa-ca-install.in @@ -42,6 +42,7 @@ from ipalib.constants import DOMAIN_LEVEL_1 from ipapython.config import IPAOptionParser from ipapython.ipa_log_manager import standard_logging_setup from ipaplatform.paths import paths +from ipapython.admintool import admin_cleanup_global_argv logger = logging.getLogger(os.path.basename(__file__)) @@ -132,6 +133,7 @@ def parse_options(): options, args = parser.parse_args() safe_options = parser.get_safe_opts(options) + admin_cleanup_global_argv(parser, options, sys.argv) if args: parser.error("Too many arguments provided") diff --git a/install/tools/ipa-compat-manage.in b/install/tools/ipa-compat-manage.in index 459f39fc8..9650abd6f 100644 --- a/install/tools/ipa-compat-manage.in +++ b/install/tools/ipa-compat-manage.in @@ -24,13 +24,13 @@ from __future__ import print_function import sys from ipaplatform.paths import paths try: - from optparse import OptionParser # pylint: disable=deprecated-module from ipapython import ipautil, config from ipaserver.install import installutils from ipaserver.install.ldapupdate import LDAPUpdate from ipalib import api, errors from ipapython.ipa_log_manager import standard_logging_setup from ipapython.dn import DN + from ipapython.admintool import admin_cleanup_global_argv except ImportError as e: print("""\ There was a problem importing one of the required Python modules. The @@ -46,7 +46,8 @@ nis_config_dn = DN(('cn', 'NIS Server'), ('cn', 'plugins'), ('cn', 'config')) def parse_options(): usage = "%prog [options] \n" usage += "%prog [options]\n" - parser = OptionParser(usage=usage, formatter=config.IPAFormatter()) + parser = config.IPAOptionParser(usage=usage, + formatter=config.IPAFormatter()) parser.add_option("-d", "--debug", action="store_true", dest="debug", help="Display debugging information about the update(s)") @@ -55,6 +56,7 @@ def parse_options(): config.add_standard_options(parser) options, args = parser.parse_args() + admin_cleanup_global_argv(parser, options, sys.argv) return options, args diff --git a/install/tools/ipa-csreplica-manage.in b/install/tools/ipa-csreplica-manage.in index 6f248cc50..2fab27a94 100644 --- a/install/tools/ipa-csreplica-manage.in +++ b/install/tools/ipa-csreplica-manage.in @@ -32,8 +32,8 @@ from ipaserver.install import (replication, installutils, bindinstance, from ipalib import api, errors from ipalib.constants import FQDN from ipalib.util import has_managed_topology, print_replication_status -from ipapython import ipautil, ipaldap, version -from ipapython.admintool import ScriptError +from ipapython import ipautil, ipaldap, version, config +from ipapython.admintool import admin_cleanup_global_argv, ScriptError from ipapython.dn import DN logger = logging.getLogger(os.path.basename(__file__)) @@ -54,11 +54,10 @@ commands = { def parse_options(): - from optparse import OptionParser # pylint: disable=deprecated-module - - parser = OptionParser(version=version.VERSION) + parser = config.IPAOptionParser(version=version.VERSION) parser.add_option("-H", "--host", dest="host", help="starting host") - parser.add_option("-p", "--password", dest="dirman_passwd", help="Directory Manager password") + parser.add_option("-p", "--password", dest="dirman_passwd", sensitive=True, + help="Directory Manager password") parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False, help="provide additional information") parser.add_option("-f", "--force", dest="force", action="store_true", default=False, @@ -66,6 +65,7 @@ def parse_options(): parser.add_option("--from", dest="fromhost", help="Host to get data from") options, args = parser.parse_args() + admin_cleanup_global_argv(parser, options, sys.argv) valid_syntax = False diff --git a/install/tools/ipa-managed-entries.in b/install/tools/ipa-managed-entries.in index e3f121943..ff2fd6a58 100644 --- a/install/tools/ipa-managed-entries.in +++ b/install/tools/ipa-managed-entries.in @@ -24,7 +24,6 @@ import logging import os import re import sys -from optparse import OptionParser # pylint: disable=deprecated-module from ipaplatform.paths import paths from ipapython import config @@ -32,6 +31,7 @@ from ipaserver.install import installutils from ipalib import api, errors from ipapython.ipa_log_manager import standard_logging_setup from ipapython.dn import DN +from ipapython.admintool import admin_cleanup_global_argv logger = logging.getLogger(os.path.basename(__file__)) @@ -51,9 +51,10 @@ def parse_options(): action="store_true", help="List available Managed Entries") parser.add_option("-p", "--password", dest="dirman_password", - help="Directory Manager password") + sensitive=True, help="Directory Manager password") options, args = parser.parse_args() + admin_cleanup_global_argv(parser, options, sys.argv) return options, args diff --git a/install/tools/ipa-replica-conncheck.in b/install/tools/ipa-replica-conncheck.in index 8eee82483..81b7d13ac 100644 --- a/install/tools/ipa-replica-conncheck.in +++ b/install/tools/ipa-replica-conncheck.in @@ -23,15 +23,15 @@ from __future__ import print_function import logging from ipapython import ipachangeconf -from ipapython.config import IPAOptionParser +from ipapython.config import (IPAOptionParser, OptionGroup, + OptionValueError) +from ipapython.admintool import admin_cleanup_global_argv from ipapython.dn import DN from ipapython import version from ipapython import ipautil, certdb from ipalib import api, errors, x509 from ipalib.constants import FQDN from ipaserver.install import installutils -# pylint: disable=deprecated-module -from optparse import OptionGroup, OptionValueError # pylint: enable=deprecated-module from ipapython.ipa_log_manager import standard_logging_setup import copy @@ -189,6 +189,7 @@ def parse_options(): options, _args = parser.parse_args() safe_options = parser.get_safe_opts(options) + admin_cleanup_global_argv(parser, options, sys.argv) if options.master and options.replica: parser.error("on-master and on-replica options are mutually exclusive!") diff --git a/install/tools/ipa-replica-manage.in b/install/tools/ipa-replica-manage.in index d6e6ef57c..7e5b31a59 100644 --- a/install/tools/ipa-replica-manage.in +++ b/install/tools/ipa-replica-manage.in @@ -43,6 +43,7 @@ from ipalib.util import ( print_replication_status, verify_host_resolvable, ) +from ipapython.admintool import admin_cleanup_global_argv from ipapython.ipa_log_manager import standard_logging_setup from ipapython.dn import DN from ipapython.config import IPAOptionParser @@ -84,7 +85,8 @@ class NoRUVsFound(Exception): def parse_options(): parser = IPAOptionParser(version=version.VERSION) parser.add_option("-H", "--host", dest="host", help="starting host") - parser.add_option("-p", "--password", dest="dirman_passwd", help="Directory Manager password") + parser.add_option("-p", "--password", dest="dirman_passwd", sensitive=True, + help="Directory Manager password") parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False, help="provide additional information") parser.add_option("-d", "--debug", dest="debug", action="store_true", default=False, @@ -95,7 +97,7 @@ def parse_options(): help="DANGER: clean up references to a ghost master") parser.add_option("--binddn", dest="binddn", default=None, type="dn", help="Bind DN to use with remote server") - parser.add_option("--bindpw", dest="bindpw", default=None, + parser.add_option("--bindpw", dest="bindpw", default=None, sensitive=True, help="Password for Bind DN to use with remote server") parser.add_option("--winsync", dest="winsync", action="store_true", default=False, help="This is a Windows Sync Agreement") @@ -103,13 +105,15 @@ def parse_options(): help="Full path and filename of CA certificate to use with TLS/SSL to the remote server") parser.add_option("--win-subtree", dest="win_subtree", default=None, help="DN of Windows subtree containing the users you want to sync (default cn=Users, ...', add two args + _argc = len(argv) + 2 + all_options = [] + if '_get_all_options' in dir(option_parser): + # OptParse parser + all_options = option_parser._get_all_options() + elif '_actions' in dir(option_parser): + # ArgParse parser + all_options = option_parser._actions + + for opt in all_options: + if getattr(opt, 'sensitive', False): + v = getattr(options, opt.dest) + for i in range(0, _argc): + vi = ctypes.cast(_argv[i], + ctypes.c_char_p + ).value.decode('utf-8') + if vi == v: + ctypes.memset(_argv[i], ord('X'), len(v)) + except Exception: + pass + + class ScriptError(Exception): """An exception that records an error message and a return value """ @@ -148,6 +187,7 @@ class AdminTool: cls._option_parsers[cls] = cls.option_parser options, args = cls.option_parser.parse_args(argv[1:]) + admin_cleanup_global_argv(cls.option_parser, options, argv) command_class = cls.get_command_class(options, args) command = command_class(options, args) diff --git a/ipaserver/install/ipa_migrate.py b/ipaserver/install/ipa_migrate.py index e21937401..1d1342788 100644 --- a/ipaserver/install/ipa_migrate.py +++ b/ipaserver/install/ipa_migrate.py @@ -28,6 +28,7 @@ from ipaplatform.paths import paths from ipapython.dn import DN from ipapython.ipaldap import LDAPClient, LDAPEntry, realm_to_ldapi_uri from ipapython.ipa_log_manager import standard_logging_setup +from ipapython.admintool import admin_cleanup_global_argv from ipaserver.install.ipa_migrate_constants import ( DS_CONFIG, DB_OBJECTS, DS_INDEXES, BIND_DN, LOG_FILE_NAME, STRIP_OP_ATTRS, STRIP_ATTRS, STRIP_OC, PROD_ATTRS, @@ -284,6 +285,18 @@ class LDIFParser(ldif.LDIFParser): self.mc.process_db_entry(entry_dn=dn, entry_attrs=entry_attrs) +class SensitiveStoreAction(argparse._StoreAction): + def __init__(self, *, sensitive, **options): + super(SensitiveStoreAction, self).__init__(**options) + self.sensitive = sensitive + + def _get_kwargs(self): + names = super(SensitiveStoreAction, self)._get_kwargs() + sensitive_name = 'sensitive' + names.extend((sensitive_name, getattr(self, sensitive_name))) + return names + + # # Migrate IPA to IPA Class # @@ -344,7 +357,8 @@ class IPAMigrate(): help='Password for the Bind DN. If a password ' 'is not provided then the user will be ' 'prompted to enter it', - default=None) + default=None, sensitive=True, + action=SensitiveStoreAction) parser.add_argument('-j', '--bind-pw-file', help='A text file containing the clear text ' 'password for the Bind DN', default=None) @@ -2023,6 +2037,7 @@ class IPAMigrate(): parser = argparse.ArgumentParser(description=desc, allow_abbrev=True) self.add_options(parser) self.validate_options() + admin_cleanup_global_argv(parser, self.args, sys.argv) # Check for dryrun mode if self.args.dryrun or self.args.dryrun_record is not None: diff --git a/ipaserver/install/ipa_restore.py b/ipaserver/install/ipa_restore.py index 8d75a0e6b..539501ab4 100644 --- a/ipaserver/install/ipa_restore.py +++ b/ipaserver/install/ipa_restore.py @@ -185,7 +185,7 @@ class Restore(admintool.AdminTool): super(Restore, cls).add_options(parser, debug_option=True) parser.add_option( - "-p", "--password", dest="password", + "-p", "--password", dest="password", sensitive=True, help="Directory Manager password") parser.add_option( "--gpg-keyring", dest="gpg_keyring", diff --git a/ipaserver/install/ipa_server_certinstall.py b/ipaserver/install/ipa_server_certinstall.py index e9f680b1d..76ad37ca7 100644 --- a/ipaserver/install/ipa_server_certinstall.py +++ b/ipaserver/install/ipa_server_certinstall.py @@ -72,7 +72,7 @@ class ServerCertInstall(admintool.AdminTool): help="Name of the certificate to install") parser.add_option( "-p", "--dirman-password", - dest="dirman_password", + dest="dirman_password", sensitive=True, help="Directory Manager password") def validate_options(self): From 7a5a10b6bf2e3eafd4b69362ffaece39791be2a8 Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Wed, 27 Nov 2024 12:16:09 +0100 Subject: [PATCH 3/7] ipa-otpd: use oidc_child's --client-secret-stdin option To remove the client secret from the command line where it would be visible e.g. when calling ps it is now passed via stdin to oidc_child. Fixes: CVE-2024-11029 Signed-off-by: Sumit Bose --- daemons/ipa-otpd/oauth2.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/daemons/ipa-otpd/oauth2.c b/daemons/ipa-otpd/oauth2.c index a33cf5171..52d7d7c9c 100644 --- a/daemons/ipa-otpd/oauth2.c +++ b/daemons/ipa-otpd/oauth2.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "internal.h" @@ -93,6 +94,7 @@ static void oauth2_on_child_writable(verto_ctx *vctx, verto_ev *ev) (void)vctx; /* Unused */ ssize_t io; struct child_ctx *child_ctx; + struct iovec iov[3]; child_ctx = verto_get_private(ev); if (child_ctx == NULL) { @@ -102,15 +104,18 @@ static void oauth2_on_child_writable(verto_ctx *vctx, verto_ev *ev) } if (child_ctx->oauth2_state == OAUTH2_GET_DEVICE_CODE) { - /* no input needed */ - verto_del(ev); - return; + io = write(verto_get_fd(ev), child_ctx->item->idp.ipaidpClientSecret, + strlen(child_ctx->item->idp.ipaidpClientSecret)); + } else { + iov[0].iov_base = child_ctx->item->idp.ipaidpClientSecret; + iov[0].iov_len = strlen(child_ctx->item->idp.ipaidpClientSecret); + iov[1].iov_base = "\n"; + iov[1].iov_len = 1; + iov[2].iov_base = child_ctx->saved_item->oauth2.device_code_reply; + iov[2].iov_len = strlen(child_ctx->saved_item->oauth2.device_code_reply); + + io = writev(verto_get_fd(ev), iov, 3); } - - - io = write(verto_get_fd(ev), - child_ctx->saved_item->oauth2.device_code_reply, - strlen(child_ctx->saved_item->oauth2.device_code_reply)); otpd_queue_item_free(child_ctx->saved_item); if (io < 0) { @@ -429,8 +434,7 @@ int oauth2(struct otpd_queue_item **item, enum oauth2_state oauth2_state) args[args_idx++] = (*item)->idp.ipaidpClientID; if ((*item)->idp.ipaidpClientSecret) { - args[args_idx++] = "--client-secret"; - args[args_idx++] = (*item)->idp.ipaidpClientSecret; + args[args_idx++] = "--client-secret-stdin"; } if ((*item)->idp.ipaidpScope) { From f33a0e8e712b2fc173408a26441d73f47e7348db Mon Sep 17 00:00:00 2001 From: Antonio Torres Date: Wed, 15 Jan 2025 12:01:25 +0100 Subject: [PATCH 4/7] Become IPA 4.12.3 --- VERSION.m4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.m4 b/VERSION.m4 index d57acf130..eb969d93c 100644 --- a/VERSION.m4 +++ b/VERSION.m4 @@ -21,7 +21,7 @@ ######################################################## define(IPA_VERSION_MAJOR, 4) define(IPA_VERSION_MINOR, 12) -define(IPA_VERSION_RELEASE, 2) +define(IPA_VERSION_RELEASE, 3) ######################################################## # For 'pre' releases the version will be # From 6ae52a2fb451bbe57a4f0c584e14bca0274b85e8 Mon Sep 17 00:00:00 2001 From: Julien Rische Date: Mon, 28 Apr 2025 18:01:39 +0200 Subject: [PATCH 5/7] kdb: keep ipadb_get_connection() from succeeding with null LDAP context The final call to ipadb_reinit_mspac() in ipadb_get_connection() is not considered essential for the function to succeed, as there might be cases where the required pieces of information to generate PACs are not yet configured in the database. However, in environments where 389ds is overwhelmed, the LDAP connection established at the beginning of ipadb_get_connection() might already be lost while executing ipadb_reinit_mspac(). Connection errors were not distinguished from configuration errors, which could result in ipadb_get_connection() succeeding while the LDAP context is set to null, leading to a KDC crash on the next LDAP request. ipadb_get_connection() now explicitly checks the value of the LDAP context before returning. Fixes: https://pagure.io/freeipa/issue/9777 Reviewed-By: Rob Crittenden Reviewed-By: Rob Crittenden Reviewed-By: Rafael Guterres Jeffman --- daemons/ipa-kdb/ipa_kdb.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/daemons/ipa-kdb/ipa_kdb.c b/daemons/ipa-kdb/ipa_kdb.c index 903e19e83..531ee223e 100644 --- a/daemons/ipa-kdb/ipa_kdb.c +++ b/daemons/ipa-kdb/ipa_kdb.c @@ -530,26 +530,43 @@ int ipadb_get_connection(struct ipadb_context *ipactx) /* get adtrust options using default refresh interval */ ret = ipadb_reinit_mspac(ipactx, false, &stmsg); - if (ret && stmsg) - krb5_klog_syslog(LOG_WARNING, "MS-PAC generator: %s", stmsg); + if (ret) { + if (stmsg) { + krb5_klog_syslog(LOG_WARNING, "MS-PAC generator: %s", stmsg); + } + /* Initialization of the MS-PAC generator is an optional dependency. + * Fail only if the connection was lost. */ + if (!ipactx->lcontext) { + goto done; + } + } ret = 0; done: ldap_msgfree(res); + /* LDAP context should never be null on success, but keep this test out of + * security to make sure we do not return an invalid context. */ + if (ret == 0 && !ipactx->lcontext) { + krb5_klog_syslog(LOG_WARNING, "Internal malfunction: LDAP connection " + "process resulted in an invalid context " + "(please report this incident)"); + ret = LDAP_SERVER_DOWN; + } + if (ret) { + /* Cleanup LDAP context if connection failed. */ if (ipactx->lcontext) { ldap_unbind_ext_s(ipactx->lcontext, NULL, NULL); ipactx->lcontext = NULL; } - if (ret == LDAP_SERVER_DOWN) { - return ETIMEDOUT; - } - return EIO; + + /* Replace LDAP error code by POSIX error code. */ + ret = ret == LDAP_SERVER_DOWN ? ETIMEDOUT : EIO; } - return 0; + return ret; } static krb5_principal ipadb_create_local_tgs(krb5_context kcontext, From e8c410ae5f7cdd36fecba66713ca94bd47465122 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Mon, 28 Apr 2025 13:43:40 -0400 Subject: [PATCH 6/7] Set krbCanonicalName=admin@REALM on the admin user The admin must always own this name. If another entry has this value set then remove it. There is a uniqueness plugin for this attribute so the only two possibilities are: - no entry has this value set - the admin user has this value set - a different entry has the value set Still, for robustness purposes, the upgrade plugin will handle more entries. Signed-off-by: Rob Crittenden --- install/share/bootstrap-template.ldif | 1 + .../updates/90-post_upgrade_plugins.update | 1 + .../plugins/add_admin_krbcanonicalname.py | 79 +++++++++++++++++++ ipatests/test_integration/test_commands.py | 38 +++++++++ 4 files changed, 119 insertions(+) create mode 100644 ipaserver/install/plugins/add_admin_krbcanonicalname.py diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif index 325eb8450..94972eb72 100644 --- a/install/share/bootstrap-template.ldif +++ b/install/share/bootstrap-template.ldif @@ -239,6 +239,7 @@ objectClass: ipasshuser uid: admin krbPrincipalName: admin@$REALM krbPrincipalName: root@$REALM +krbCanonicalName: admin@$REALM cn: Administrator sn: Administrator uidNumber: $IDSTART diff --git a/install/updates/90-post_upgrade_plugins.update b/install/updates/90-post_upgrade_plugins.update index bb48f483c..458d81045 100644 --- a/install/updates/90-post_upgrade_plugins.update +++ b/install/updates/90-post_upgrade_plugins.update @@ -27,6 +27,7 @@ plugin: update_mapping_Guests_to_nobody plugin: fix_kra_people_entry plugin: update_pwpolicy plugin: update_pwpolicy_grace +plugin: add_admin_krbcanonicalname # last # DNS version 1 diff --git a/ipaserver/install/plugins/add_admin_krbcanonicalname.py b/ipaserver/install/plugins/add_admin_krbcanonicalname.py new file mode 100644 index 000000000..e9ffdf55a --- /dev/null +++ b/ipaserver/install/plugins/add_admin_krbcanonicalname.py @@ -0,0 +1,79 @@ +# +# Copyright (C) 2025 FreeIPA Contributors see COPYING for license +# + +from __future__ import absolute_import + +import logging + +from ipalib import errors +from ipalib import Registry +from ipalib import Updater +from ipapython.dn import DN + +logger = logging.getLogger(__name__) + +register = Registry() + + +@register() +class add_admin_krbcanonicalname(Updater): + """ + Ensures that only the admin user has the krbCanonicalName of + admin@$REALM. + """ + + def execute(self, **options): + ldap = self.api.Backend.ldap2 + + search_filter = ( + "(krbcanonicalname=admin@{})".format(self.api.env.realm)) + try: + (entries, _truncated) = ldap.find_entries( + filter=search_filter, base_dn=self.api.env.basedn, + time_limit=0, size_limit=0) + except errors.EmptyResult: + logger.debug("add_admin_krbcanonicalname: No user set with " + "admin krbcanonicalname") + entries = [] + # fall through + except errors.ExecutionError as e: + logger.error("add_admin_krbcanonicalname: Can not get list " + "of krbcanonicalname: %s", e) + return False, [] + + admin_set = False + # admin should be only user with admin@ as krbcanonicalname + # It has a uniquness setting so there can be only one, we + # just didn't automatically set it for admin. + for entry in entries: + if entry.single_value.get('uid') != 'admin': + logger.critical( + "add_admin_krbcanonicalname: " + "entry %s has a krbcanonicalname of admin. Removing.", + entry.dn) + del entry['krbcanonicalname'] + ldap.update_entry(entry) + else: + admin_set = True + + if not admin_set: + dn = DN( + ('uid', 'admin'), + self.api.env.container_user, + self.api.env.basedn) + entry = ldap.get_entry(dn) + entry['krbcanonicalname'] = 'admin@%s' % self.api.env.realm + try: + ldap.update_entry(entry) + except errors.DuplicateEntry: + logger.critical( + "add_admin_krbcanonicalname: " + "Failed to set krbcanonicalname on admin. It is set " + "on another entry.") + except errors.ExecutionError as e: + logger.critical( + "add_admin_krbcanonicalname: " + "Failed to set krbcanonicalname on admin: %s", e) + + return False, [] diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py index fd34defe5..421e69043 100644 --- a/ipatests/test_integration/test_commands.py +++ b/ipatests/test_integration/test_commands.py @@ -1883,6 +1883,44 @@ class TestIPACommandWithoutReplica(IntegrationTest): assert old_err_msg not in dirsrv_error_log assert re.search(new_err_msg, dirsrv_error_log) + def test_unique_krbcanonicalname(self): + """Verify that the uniqueness for krbcanonicalname is working""" + master = self.master + + base_dn = str(master.domain.basedn) + hostname = master.hostname + realm = master.domain.realm + principal = f'test/{hostname}@{realm}' + entry_ldif = textwrap.dedent(""" + dn: krbprincipalname={principal},cn=services,cn=accounts,{base_dn} + changetype: add + ipakrbprincipalalias: test/{hostname}@{realm} + krbprincipalname: {principal} + objectclass: ipakrbprincipal + objectclass: ipaobject + objectclass: ipaservice + objectclass: krbprincipal + objectclass: krbprincipalaux + objectclass: top + krbcanonicalname: admin@{realm} + managedby: fqdn={hostname},cn=computers,cn=accounts,{base_dn} + """).format( + base_dn=base_dn, + hostname=hostname, + principal=principal, + realm=realm) + tasks.kdestroy_all(master) + master.run_command( + ['kinit', '-kt', '/etc/krb5.keytab', f'host/{hostname}@{realm}']) + args = [ + 'ldapmodify', + '-Y', + 'GSSAPI' + ] + result = master.run_command(args, stdin_text=entry_ldif, + raiseonerr=False) + assert "entry with the same attribute value" in result.stderr_text + class TestIPAautomount(IntegrationTest): @classmethod From f2fc367fb00193a8ca8a1f22786fccd6b0024dac Mon Sep 17 00:00:00 2001 From: Antonio Torres Date: Tue, 17 Jun 2025 09:14:30 +0200 Subject: [PATCH 7/7] Become IPA 4.12.4 --- VERSION.m4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.m4 b/VERSION.m4 index eb969d93c..8aa7f65c6 100644 --- a/VERSION.m4 +++ b/VERSION.m4 @@ -21,7 +21,7 @@ ######################################################## define(IPA_VERSION_MAJOR, 4) define(IPA_VERSION_MINOR, 12) -define(IPA_VERSION_RELEASE, 3) +define(IPA_VERSION_RELEASE, 4) ######################################################## # For 'pre' releases the version will be #