Compare commits
	
		
			No commits in common. "c8-stream-DL1" and "c8-beta-stream-DL1" have entirely different histories.
		
	
	
		
			c8-stream-
			...
			c8-beta-st
		
	
		
| @ -43,8 +43,9 @@ index 06d511c76..dbb98dba6 100644 | |||||||
|   |   | ||||||
|  #define IPADB_GLOBAL_CONFIG_CACHE_TIME 60 |  #define IPADB_GLOBAL_CONFIG_CACHE_TIME 60 | ||||||
|   |   | ||||||
| @@ -207,5 +208,18 @@ static const struct {
 | @@ -207,6 +208,19 @@ static const struct {
 | ||||||
|      { "idp", IPADB_USER_AUTH_IDP }, |      { "idp", IPADB_USER_AUTH_IDP }, | ||||||
|  |      { "passkey", IPADB_USER_AUTH_PASSKEY }, | ||||||
|      { } |      { } | ||||||
| +},
 | +},
 | ||||||
| +  objclass_table[] = {
 | +  objclass_table[] = {
 | ||||||
|  | |||||||
| @ -1,392 +0,0 @@ | |||||||
| From b039f3087a13de3f34b230dbe29a7cfb1965700d Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Alexander Bokovoy <abokovoy@redhat.com> |  | ||||||
| Date: Feb 23 2024 09:49:27 +0000 |  | ||||||
| Subject: rpcserver: validate Kerberos principal name before running kinit |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Do minimal validation of the Kerberos principal name when passing it to |  | ||||||
| kinit command line tool. Also pass it as the final argument to prevent |  | ||||||
| option injection. |  | ||||||
| 
 |  | ||||||
| Accepted Kerberos principals are: |  | ||||||
|  - user names, using the following regexp |  | ||||||
|    (username with optional @realm, no spaces or slashes in the name): |  | ||||||
|    "(?!^[0-9]+$)^[a-zA-Z0-9_.][a-zA-Z0-9_.-]*[a-zA-Z0-9_.$-]?@?[a-zA-Z0-9.-]*$" |  | ||||||
| 
 |  | ||||||
|  - service names (with slash in the name but no spaces). Validation of |  | ||||||
|    the hostname is done. There is no validation of the service name. |  | ||||||
| 
 |  | ||||||
| The regular expression above also covers cases where a principal name |  | ||||||
| starts with '-'. This prevents option injection as well. |  | ||||||
| 
 |  | ||||||
| This fixes CVE-2024-1481 |  | ||||||
| 
 |  | ||||||
| Fixes: https://pagure.io/freeipa/issue/9541 |  | ||||||
| 
 |  | ||||||
| Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com> |  | ||||||
| Signed-off-by: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com> |  | ||||||
| Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com> |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
| 
 |  | ||||||
| diff --git a/ipalib/install/kinit.py b/ipalib/install/kinit.py
 |  | ||||||
| index cc839ec..4ad4eaa 100644
 |  | ||||||
| --- a/ipalib/install/kinit.py
 |  | ||||||
| +++ b/ipalib/install/kinit.py
 |  | ||||||
| @@ -6,12 +6,16 @@ from __future__ import absolute_import
 |  | ||||||
|   |  | ||||||
|  import logging |  | ||||||
|  import os |  | ||||||
| +import re
 |  | ||||||
|  import time |  | ||||||
|   |  | ||||||
|  import gssapi |  | ||||||
|   |  | ||||||
|  from ipaplatform.paths import paths |  | ||||||
|  from ipapython.ipautil import run |  | ||||||
| +from ipalib.constants import PATTERN_GROUPUSER_NAME
 |  | ||||||
| +from ipalib.util import validate_hostname
 |  | ||||||
| +from ipalib import api
 |  | ||||||
|   |  | ||||||
|  logger = logging.getLogger(__name__) |  | ||||||
|   |  | ||||||
| @@ -21,6 +25,40 @@ KRB5_KDC_UNREACH = 2529639068
 |  | ||||||
|  # A service is not available that s required to process the request |  | ||||||
|  KRB5KDC_ERR_SVC_UNAVAILABLE = 2529638941 |  | ||||||
|   |  | ||||||
| +PATTERN_REALM = '@?([a-zA-Z0-9.-]*)$'
 |  | ||||||
| +PATTERN_PRINCIPAL = '(' + PATTERN_GROUPUSER_NAME[:-1] + ')' + PATTERN_REALM
 |  | ||||||
| +PATTERN_SERVICE = '([a-zA-Z0-9.-]+)/([a-zA-Z0-9.-]+)' + PATTERN_REALM
 |  | ||||||
| +
 |  | ||||||
| +user_pattern = re.compile(PATTERN_PRINCIPAL)
 |  | ||||||
| +service_pattern = re.compile(PATTERN_SERVICE)
 |  | ||||||
| +
 |  | ||||||
| +
 |  | ||||||
| +def validate_principal(principal):
 |  | ||||||
| +    if not isinstance(principal, str):
 |  | ||||||
| +        raise RuntimeError('Invalid principal: not a string')
 |  | ||||||
| +    if ('/' in principal) and (' ' in principal):
 |  | ||||||
| +        raise RuntimeError('Invalid principal: bad spacing')
 |  | ||||||
| +    else:
 |  | ||||||
| +        realm = None
 |  | ||||||
| +        match = user_pattern.match(principal)
 |  | ||||||
| +        if match is None:
 |  | ||||||
| +            match = service_pattern.match(principal)
 |  | ||||||
| +            if match is None:
 |  | ||||||
| +                raise RuntimeError('Invalid principal: cannot parse')
 |  | ||||||
| +            else:
 |  | ||||||
| +                # service = match[1]
 |  | ||||||
| +                hostname = match[2]
 |  | ||||||
| +                realm = match[3]
 |  | ||||||
| +                try:
 |  | ||||||
| +                    validate_hostname(hostname)
 |  | ||||||
| +                except ValueError as e:
 |  | ||||||
| +                    raise RuntimeError(str(e))
 |  | ||||||
| +        else:  # user match, validate realm
 |  | ||||||
| +            # username = match[1]
 |  | ||||||
| +            realm = match[2]
 |  | ||||||
| +        if realm and 'realm' in api.env and realm != api.env.realm:
 |  | ||||||
| +            raise RuntimeError('Invalid principal: realm mismatch')
 |  | ||||||
| +
 |  | ||||||
|   |  | ||||||
|  def kinit_keytab(principal, keytab, ccache_name, config=None, attempts=1): |  | ||||||
|      """ |  | ||||||
| @@ -29,6 +67,7 @@ def kinit_keytab(principal, keytab, ccache_name, config=None, attempts=1):
 |  | ||||||
|      The optional parameter 'attempts' specifies how many times the credential |  | ||||||
|      initialization should be attempted in case of non-responsive KDC. |  | ||||||
|      """ |  | ||||||
| +    validate_principal(principal)
 |  | ||||||
|      errors_to_retry = {KRB5KDC_ERR_SVC_UNAVAILABLE, |  | ||||||
|                         KRB5_KDC_UNREACH} |  | ||||||
|      logger.debug("Initializing principal %s using keytab %s", |  | ||||||
| @@ -65,6 +104,7 @@ def kinit_keytab(principal, keytab, ccache_name, config=None, attempts=1):
 |  | ||||||
|   |  | ||||||
|          return None |  | ||||||
|   |  | ||||||
| +
 |  | ||||||
|  def kinit_password(principal, password, ccache_name, config=None, |  | ||||||
|                     armor_ccache_name=None, canonicalize=False, |  | ||||||
|                     enterprise=False, lifetime=None): |  | ||||||
| @@ -73,8 +113,9 @@ def kinit_password(principal, password, ccache_name, config=None,
 |  | ||||||
|      web-based authentication, use armor_ccache_path to specify http service |  | ||||||
|      ccache. |  | ||||||
|      """ |  | ||||||
| +    validate_principal(principal)
 |  | ||||||
|      logger.debug("Initializing principal %s using password", principal) |  | ||||||
| -    args = [paths.KINIT, principal, '-c', ccache_name]
 |  | ||||||
| +    args = [paths.KINIT, '-c', ccache_name]
 |  | ||||||
|      if armor_ccache_name is not None: |  | ||||||
|          logger.debug("Using armor ccache %s for FAST webauth", |  | ||||||
|                       armor_ccache_name) |  | ||||||
| @@ -91,6 +132,7 @@ def kinit_password(principal, password, ccache_name, config=None,
 |  | ||||||
|          logger.debug("Using enterprise principal") |  | ||||||
|          args.append('-E') |  | ||||||
|   |  | ||||||
| +    args.extend(['--', principal])
 |  | ||||||
|      env = {'LC_ALL': 'C'} |  | ||||||
|      if config is not None: |  | ||||||
|          env['KRB5_CONFIG'] = config |  | ||||||
| @@ -154,6 +196,7 @@ def kinit_pkinit(
 |  | ||||||
|   |  | ||||||
|      :raises: CalledProcessError if PKINIT fails |  | ||||||
|      """ |  | ||||||
| +    validate_principal(principal)
 |  | ||||||
|      logger.debug( |  | ||||||
|          "Initializing principal %s using PKINIT %s", principal, user_identity |  | ||||||
|      ) |  | ||||||
| @@ -168,7 +211,7 @@ def kinit_pkinit(
 |  | ||||||
|              assert pkinit_anchor.startswith(("FILE:", "DIR:", "ENV:")) |  | ||||||
|              args.extend(["-X", f"X509_anchors={pkinit_anchor}"]) |  | ||||||
|      args.extend(["-X", f"X509_user_identity={user_identity}"]) |  | ||||||
| -    args.append(principal)
 |  | ||||||
| +    args.extend(['--', principal])
 |  | ||||||
|   |  | ||||||
|      # this workaround enables us to capture stderr and put it |  | ||||||
|      # into the raised exception in case of unsuccessful authentication |  | ||||||
| diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py
 |  | ||||||
| index 3555014..60bfa61 100644
 |  | ||||||
| --- a/ipaserver/rpcserver.py
 |  | ||||||
| +++ b/ipaserver/rpcserver.py
 |  | ||||||
| @@ -1134,10 +1134,6 @@ class login_password(Backend, KerberosSession):
 |  | ||||||
|                  canonicalize=True, |  | ||||||
|                  lifetime=self.api.env.kinit_lifetime) |  | ||||||
|   |  | ||||||
| -            if armor_path:
 |  | ||||||
| -                logger.debug('Cleanup the armor ccache')
 |  | ||||||
| -                ipautil.run([paths.KDESTROY, '-A', '-c', armor_path],
 |  | ||||||
| -                            env={'KRB5CCNAME': armor_path}, raiseonerr=False)
 |  | ||||||
|          except RuntimeError as e: |  | ||||||
|              if ('kinit: Cannot read password while ' |  | ||||||
|                      'getting initial credentials') in str(e): |  | ||||||
| @@ -1155,6 +1151,11 @@ class login_password(Backend, KerberosSession):
 |  | ||||||
|                  raise KrbPrincipalWrongFAST(principal=principal) |  | ||||||
|              raise InvalidSessionPassword(principal=principal, |  | ||||||
|                                           message=unicode(e)) |  | ||||||
| +        finally:
 |  | ||||||
| +            if armor_path:
 |  | ||||||
| +                logger.debug('Cleanup the armor ccache')
 |  | ||||||
| +                ipautil.run([paths.KDESTROY, '-A', '-c', armor_path],
 |  | ||||||
| +                            env={'KRB5CCNAME': armor_path}, raiseonerr=False)
 |  | ||||||
|   |  | ||||||
|   |  | ||||||
|  class change_password(Backend, HTTP_Status): |  | ||||||
| diff --git a/ipatests/prci_definitions/gating.yaml b/ipatests/prci_definitions/gating.yaml
 |  | ||||||
| index 91be057..400a248 100644
 |  | ||||||
| --- a/ipatests/prci_definitions/gating.yaml
 |  | ||||||
| +++ b/ipatests/prci_definitions/gating.yaml
 |  | ||||||
| @@ -310,3 +310,15 @@ jobs:
 |  | ||||||
|          template: *ci-ipa-4-9-latest |  | ||||||
|          timeout: 3600 |  | ||||||
|          topology: *master_1repl_1client |  | ||||||
| +
 |  | ||||||
| +  fedora-latest-ipa-4-9/test_ipalib_install:
 |  | ||||||
| +    requires: [fedora-latest-ipa-4-9/build]
 |  | ||||||
| +    priority: 100
 |  | ||||||
| +    job:
 |  | ||||||
| +      class: RunPytest
 |  | ||||||
| +      args:
 |  | ||||||
| +        build_url: '{fedora-latest-ipa-4-9/build_url}'
 |  | ||||||
| +        test_suite: test_ipalib_install/test_kinit.py
 |  | ||||||
| +        template: *ci-ipa-4-9-latest
 |  | ||||||
| +        timeout: 600
 |  | ||||||
| +        topology: *master_1repl
 |  | ||||||
| diff --git a/ipatests/prci_definitions/nightly_ipa-4-9_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-9_latest.yaml
 |  | ||||||
| index b2ab765..7c03a48 100644
 |  | ||||||
| --- a/ipatests/prci_definitions/nightly_ipa-4-9_latest.yaml
 |  | ||||||
| +++ b/ipatests/prci_definitions/nightly_ipa-4-9_latest.yaml
 |  | ||||||
| @@ -1801,3 +1801,15 @@ jobs:
 |  | ||||||
|          template: *ci-ipa-4-9-latest |  | ||||||
|          timeout: 5000 |  | ||||||
|          topology: *master_2repl_1client |  | ||||||
| +
 |  | ||||||
| +  fedora-latest-ipa-4-9/test_ipalib_install:
 |  | ||||||
| +    requires: [fedora-latest-ipa-4-9/build]
 |  | ||||||
| +    priority: 50
 |  | ||||||
| +    job:
 |  | ||||||
| +      class: RunPytest
 |  | ||||||
| +      args:
 |  | ||||||
| +        build_url: '{fedora-latest-ipa-4-9/build_url}'
 |  | ||||||
| +        test_suite: test_ipalib_install/test_kinit.py
 |  | ||||||
| +        template: *ci-ipa-4-9-latest
 |  | ||||||
| +        timeout: 600
 |  | ||||||
| +        topology: *master_1repl
 |  | ||||||
| diff --git a/ipatests/prci_definitions/nightly_ipa-4-9_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-9_latest_selinux.yaml
 |  | ||||||
| index b7b3d3b..802bd2a 100644
 |  | ||||||
| --- a/ipatests/prci_definitions/nightly_ipa-4-9_latest_selinux.yaml
 |  | ||||||
| +++ b/ipatests/prci_definitions/nightly_ipa-4-9_latest_selinux.yaml
 |  | ||||||
| @@ -1944,3 +1944,16 @@ jobs:
 |  | ||||||
|          template: *ci-ipa-4-9-latest |  | ||||||
|          timeout: 5000 |  | ||||||
|          topology: *master_2repl_1client |  | ||||||
| +
 |  | ||||||
| +  fedora-latest-ipa-4-9/test_ipalib_install:
 |  | ||||||
| +    requires: [fedora-latest-ipa-4-9/build]
 |  | ||||||
| +    priority: 50
 |  | ||||||
| +    job:
 |  | ||||||
| +      class: RunPytest
 |  | ||||||
| +      args:
 |  | ||||||
| +        build_url: '{fedora-latest-ipa-4-9/build_url}'
 |  | ||||||
| +        selinux_enforcing: True
 |  | ||||||
| +        test_suite: test_ipalib_install/test_kinit.py
 |  | ||||||
| +        template: *ci-ipa-4-9-latest
 |  | ||||||
| +        timeout: 600
 |  | ||||||
| +        topology: *master_1repl
 |  | ||||||
| diff --git a/ipatests/prci_definitions/nightly_ipa-4-9_previous.yaml b/ipatests/prci_definitions/nightly_ipa-4-9_previous.yaml
 |  | ||||||
| index eb3849e..1e1adb8 100644
 |  | ||||||
| --- a/ipatests/prci_definitions/nightly_ipa-4-9_previous.yaml
 |  | ||||||
| +++ b/ipatests/prci_definitions/nightly_ipa-4-9_previous.yaml
 |  | ||||||
| @@ -1801,3 +1801,15 @@ jobs:
 |  | ||||||
|          template: *ci-ipa-4-9-previous |  | ||||||
|          timeout: 5000 |  | ||||||
|          topology: *master_2repl_1client |  | ||||||
| +
 |  | ||||||
| +  fedora-previous-ipa-4-9/test_ipalib_install:
 |  | ||||||
| +    requires: [fedora-previous-ipa-4-9/build]
 |  | ||||||
| +    priority: 50
 |  | ||||||
| +    job:
 |  | ||||||
| +      class: RunPytest
 |  | ||||||
| +      args:
 |  | ||||||
| +        build_url: '{fedora-previous-ipa-4-9/build_url}'
 |  | ||||||
| +        test_suite: test_ipalib_install/test_kinit.py
 |  | ||||||
| +        template: *ci-ipa-4-9-previous
 |  | ||||||
| +        timeout: 600
 |  | ||||||
| +        topology: *master_1repl
 |  | ||||||
| diff --git a/ipatests/setup.py b/ipatests/setup.py
 |  | ||||||
| index 6217a1b..0aec4a7 100644
 |  | ||||||
| --- a/ipatests/setup.py
 |  | ||||||
| +++ b/ipatests/setup.py
 |  | ||||||
| @@ -41,6 +41,7 @@ if __name__ == '__main__':
 |  | ||||||
|              "ipatests.test_integration", |  | ||||||
|              "ipatests.test_ipaclient", |  | ||||||
|              "ipatests.test_ipalib", |  | ||||||
| +            "ipatests.test_ipalib_install",
 |  | ||||||
|              "ipatests.test_ipaplatform", |  | ||||||
|              "ipatests.test_ipapython", |  | ||||||
|              "ipatests.test_ipaserver", |  | ||||||
| diff --git a/ipatests/test_ipalib_install/__init__.py b/ipatests/test_ipalib_install/__init__.py
 |  | ||||||
| new file mode 100644 |  | ||||||
| index 0000000..e69de29
 |  | ||||||
| --- /dev/null
 |  | ||||||
| +++ b/ipatests/test_ipalib_install/__init__.py
 |  | ||||||
| diff --git a/ipatests/test_ipalib_install/test_kinit.py b/ipatests/test_ipalib_install/test_kinit.py
 |  | ||||||
| new file mode 100644 |  | ||||||
| index 0000000..f89ea17
 |  | ||||||
| --- /dev/null
 |  | ||||||
| +++ b/ipatests/test_ipalib_install/test_kinit.py
 |  | ||||||
| @@ -0,0 +1,29 @@
 |  | ||||||
| +#
 |  | ||||||
| +# Copyright (C) 2024  FreeIPA Contributors see COPYING for license
 |  | ||||||
| +#
 |  | ||||||
| +"""Tests for ipalib.install.kinit module
 |  | ||||||
| +"""
 |  | ||||||
| +
 |  | ||||||
| +import pytest
 |  | ||||||
| +
 |  | ||||||
| +from ipalib.install.kinit import validate_principal
 |  | ||||||
| +
 |  | ||||||
| +
 |  | ||||||
| +# None means no exception is expected
 |  | ||||||
| +@pytest.mark.parametrize('principal, exception', [
 |  | ||||||
| +    ('testuser', None),
 |  | ||||||
| +    ('testuser@EXAMPLE.TEST', None),
 |  | ||||||
| +    ('test/ipa.example.test', None),
 |  | ||||||
| +    ('test/ipa.example.test@EXAMPLE.TEST', None),
 |  | ||||||
| +    ('test/ipa@EXAMPLE.TEST', RuntimeError),
 |  | ||||||
| +    ('test/-ipa.example.test@EXAMPLE.TEST', RuntimeError),
 |  | ||||||
| +    ('test/ipa.1example.test@EXAMPLE.TEST', RuntimeError),
 |  | ||||||
| +    ('test /ipa.example,test', RuntimeError),
 |  | ||||||
| +    ('testuser@OTHER.TEST', RuntimeError),
 |  | ||||||
| +    ('test/ipa.example.test@OTHER.TEST', RuntimeError),
 |  | ||||||
| +])
 |  | ||||||
| +def test_validate_principal(principal, exception):
 |  | ||||||
| +    try:
 |  | ||||||
| +        validate_principal(principal)
 |  | ||||||
| +    except Exception as e:
 |  | ||||||
| +        assert e.__class__ == exception
 |  | ||||||
| 
 |  | ||||||
| From 96a478bbedd49c31e0f078f00f2d1cb55bb952fd Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| Date: Feb 23 2024 09:49:27 +0000 |  | ||||||
| Subject: validate_principal: Don't try to verify that the realm is known |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| The actual value is less important than whether it matches the |  | ||||||
| regular expression. A number of legal but difficult to know in |  | ||||||
| context realms could be passed in here (trust for example). |  | ||||||
| 
 |  | ||||||
| This fixes CVE-2024-1481 |  | ||||||
| 
 |  | ||||||
| Fixes: https://pagure.io/freeipa/issue/9541 |  | ||||||
| 
 |  | ||||||
| Signed-off-by: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com> |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
| 
 |  | ||||||
| diff --git a/ipalib/install/kinit.py b/ipalib/install/kinit.py
 |  | ||||||
| index 4ad4eaa..d5fb56b 100644
 |  | ||||||
| --- a/ipalib/install/kinit.py
 |  | ||||||
| +++ b/ipalib/install/kinit.py
 |  | ||||||
| @@ -15,7 +15,6 @@ from ipaplatform.paths import paths
 |  | ||||||
|  from ipapython.ipautil import run |  | ||||||
|  from ipalib.constants import PATTERN_GROUPUSER_NAME |  | ||||||
|  from ipalib.util import validate_hostname |  | ||||||
| -from ipalib import api
 |  | ||||||
|   |  | ||||||
|  logger = logging.getLogger(__name__) |  | ||||||
|   |  | ||||||
| @@ -39,7 +38,9 @@ def validate_principal(principal):
 |  | ||||||
|      if ('/' in principal) and (' ' in principal): |  | ||||||
|          raise RuntimeError('Invalid principal: bad spacing') |  | ||||||
|      else: |  | ||||||
| -        realm = None
 |  | ||||||
| +        # For a user match in the regex
 |  | ||||||
| +        # username = match[1]
 |  | ||||||
| +        # realm = match[2]
 |  | ||||||
|          match = user_pattern.match(principal) |  | ||||||
|          if match is None: |  | ||||||
|              match = service_pattern.match(principal) |  | ||||||
| @@ -48,16 +49,11 @@ def validate_principal(principal):
 |  | ||||||
|              else: |  | ||||||
|                  # service = match[1] |  | ||||||
|                  hostname = match[2] |  | ||||||
| -                realm = match[3]
 |  | ||||||
| +                # realm = match[3]
 |  | ||||||
|                  try: |  | ||||||
|                      validate_hostname(hostname) |  | ||||||
|                  except ValueError as e: |  | ||||||
|                      raise RuntimeError(str(e)) |  | ||||||
| -        else:  # user match, validate realm
 |  | ||||||
| -            # username = match[1]
 |  | ||||||
| -            realm = match[2]
 |  | ||||||
| -        if realm and 'realm' in api.env and realm != api.env.realm:
 |  | ||||||
| -            raise RuntimeError('Invalid principal: realm mismatch')
 |  | ||||||
|   |  | ||||||
|   |  | ||||||
|  def kinit_keytab(principal, keytab, ccache_name, config=None, attempts=1): |  | ||||||
| diff --git a/ipatests/test_ipalib_install/test_kinit.py b/ipatests/test_ipalib_install/test_kinit.py
 |  | ||||||
| index f89ea17..8289c4b 100644
 |  | ||||||
| --- a/ipatests/test_ipalib_install/test_kinit.py
 |  | ||||||
| +++ b/ipatests/test_ipalib_install/test_kinit.py
 |  | ||||||
| @@ -17,13 +17,16 @@ from ipalib.install.kinit import validate_principal
 |  | ||||||
|      ('test/ipa.example.test@EXAMPLE.TEST', None), |  | ||||||
|      ('test/ipa@EXAMPLE.TEST', RuntimeError), |  | ||||||
|      ('test/-ipa.example.test@EXAMPLE.TEST', RuntimeError), |  | ||||||
| -    ('test/ipa.1example.test@EXAMPLE.TEST', RuntimeError),
 |  | ||||||
| +    ('test/ipa.1example.test@EXAMPLE.TEST', None),
 |  | ||||||
|      ('test /ipa.example,test', RuntimeError), |  | ||||||
| -    ('testuser@OTHER.TEST', RuntimeError),
 |  | ||||||
| -    ('test/ipa.example.test@OTHER.TEST', RuntimeError),
 |  | ||||||
| +    ('testuser@OTHER.TEST', None),
 |  | ||||||
| +    ('test/ipa.example.test@OTHER.TEST', None)
 |  | ||||||
|  ]) |  | ||||||
|  def test_validate_principal(principal, exception): |  | ||||||
|      try: |  | ||||||
|          validate_principal(principal) |  | ||||||
|      except Exception as e: |  | ||||||
|          assert e.__class__ == exception |  | ||||||
| +    else:
 |  | ||||||
| +        if exception is not None:
 |  | ||||||
| +            raise RuntimeError('Test should have failed')
 |  | ||||||
| 
 |  | ||||||
| @ -1,43 +0,0 @@ | |||||||
| From d7c1ba0672fc8964f7674a526f3019429a551372 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| Date: Mar 06 2024 08:34:57 +0000 |  | ||||||
| Subject: Vault: add additional fallback to RSA-OAEP wrapping algo |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| There is a fallback when creating the wrapping key but one was missing |  | ||||||
| when trying to use the cached transport_cert. |  | ||||||
| 
 |  | ||||||
| This allows, along with forcing keyWrap.useOAEP=true, vault creation |  | ||||||
| on an nCipher HSM. |  | ||||||
| 
 |  | ||||||
| This can be seen in HSMs where the device doesn't support the |  | ||||||
| PKCS#1 v1.5 mechanism. It will error out with either "invalid |  | ||||||
| algorithm" or CKR_FUNCTION_FAILED. |  | ||||||
| 
 |  | ||||||
| Related: https://pagure.io/freeipa/issue/9191 |  | ||||||
| 
 |  | ||||||
| Signed-off-by: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com> |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
| 
 |  | ||||||
| diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py
 |  | ||||||
| index ed16c73..1523187 100644
 |  | ||||||
| --- a/ipaclient/plugins/vault.py
 |  | ||||||
| +++ b/ipaclient/plugins/vault.py
 |  | ||||||
| @@ -757,8 +757,12 @@ class ModVaultData(Local):
 |  | ||||||
|          Calls the internal counterpart of the command. |  | ||||||
|          """ |  | ||||||
|          # try call with cached transport certificate |  | ||||||
| -        result = self._do_internal(algo, transport_cert, False,
 |  | ||||||
| -                                   False, *args, **options)
 |  | ||||||
| +        try:
 |  | ||||||
| +            result = self._do_internal(algo, transport_cert, False,
 |  | ||||||
| +                                       False, *args, **options)
 |  | ||||||
| +        except errors.EncodingError:
 |  | ||||||
| +            result = self._do_internal(algo, transport_cert, False,
 |  | ||||||
| +                                       True, *args, **options)
 |  | ||||||
|          if result is not None: |  | ||||||
|              return result |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| @ -1,50 +0,0 @@ | |||||||
| From 656a11ae961f8d1afad54567cfe8ccb53e084a67 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Alexander Bokovoy <abokovoy@redhat.com> |  | ||||||
| Date: Mar 20 2024 10:06:07 +0000 |  | ||||||
| Subject: dcerpc: invalidate forest trust info cache when filtering out realm domains |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| When get_realmdomains() method is called, it will filter out subdomains |  | ||||||
| of the IPA primary domain. This is required because Active Directory |  | ||||||
| domain controllers are assuming subdomains already covered by the main |  | ||||||
| domain namespace. |  | ||||||
| 
 |  | ||||||
| [MS-LSAD] 3.1.4.7.16.1, 'Forest Trust Collision Generation' defines the |  | ||||||
| method of validating the forest trust information. They are the same as |  | ||||||
| rules in [MS-ADTS] section 6.1.6. Specifically, |  | ||||||
| 
 |  | ||||||
|   - A top-level name must not be superior to an enabled top-level name |  | ||||||
|     for another trusted domain object, unless the current trusted domain |  | ||||||
|     object has a corresponding exclusion record. |  | ||||||
| 
 |  | ||||||
| In practice, we filtered those subdomains already but the code wasn't |  | ||||||
| invalidating a previously retrieved forest trust information. |  | ||||||
| 
 |  | ||||||
| Fixes: https://pagure.io/freeipa/issue/9551 |  | ||||||
| 
 |  | ||||||
| Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com> |  | ||||||
| Reviewed-By: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
| 
 |  | ||||||
| diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
 |  | ||||||
| index b6139db..7ee553d 100644
 |  | ||||||
| --- a/ipaserver/dcerpc.py
 |  | ||||||
| +++ b/ipaserver/dcerpc.py
 |  | ||||||
| @@ -1103,6 +1103,7 @@ class TrustDomainInstance:
 |  | ||||||
|   |  | ||||||
|          info.count = len(ftinfo_records) |  | ||||||
|          info.entries = ftinfo_records |  | ||||||
| +        another_domain.ftinfo_data = info
 |  | ||||||
|          return info |  | ||||||
|   |  | ||||||
|      def clear_ftinfo_conflict(self, another_domain, cinfo): |  | ||||||
| @@ -1778,6 +1779,7 @@ class TrustDomainJoins:
 |  | ||||||
|              return |  | ||||||
|   |  | ||||||
|          self.local_domain.ftinfo_records = [] |  | ||||||
| +        self.local_domain.ftinfo_data = None
 |  | ||||||
|   |  | ||||||
|          realm_domains = self.api.Command.realmdomains_show()['result'] |  | ||||||
|          # Use realmdomains' modification timestamp |  | ||||||
| 
 |  | ||||||
| @ -1,335 +0,0 @@ | |||||||
| From 3bba254ccdcf9b62fdd8a6d71baecf37c97c300c Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| Date: Mon, 3 Apr 2023 08:37:28 +0200 |  | ||||||
| Subject: [PATCH] ipatests: mark known failures for autoprivategroup |  | ||||||
| 
 |  | ||||||
| Two tests have known issues in test_trust.py with sssd 2.8.2+: |  | ||||||
| - TestNonPosixAutoPrivateGroup::test_idoverride_with_auto_private_group
 |  | ||||||
| (when called with the "hybrid" parameter) |  | ||||||
| - TestPosixAutoPrivateGroup::test_only_uid_number_auto_private_group_default
 |  | ||||||
| (when called with the "true" parameter) |  | ||||||
| 
 |  | ||||||
| Related: https://pagure.io/freeipa/issue/9295 |  | ||||||
| Signed-off-by: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| Reviewed-By: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com> |  | ||||||
| ---
 |  | ||||||
|  ipatests/test_integration/test_trust.py | 17 ++++++++++++----- |  | ||||||
|  1 file changed, 12 insertions(+), 5 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/ipatests/test_integration/test_trust.py b/ipatests/test_integration/test_trust.py
 |  | ||||||
| index 0d5b71cb0..12f000c1a 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_trust.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_trust.py
 |  | ||||||
| @@ -1154,11 +1154,15 @@ class TestNonPosixAutoPrivateGroup(BaseTestTrust):
 |  | ||||||
|                                       self.gid_override |  | ||||||
|                                       ): |  | ||||||
|              self.mod_idrange_auto_private_group(type) |  | ||||||
| -            (uid, gid) = self.get_user_id(self.clients[0], nonposixuser)
 |  | ||||||
| -            assert (uid == self.uid_override and gid == self.gid_override)
 |  | ||||||
| +            sssd_version = tasks.get_sssd_version(self.clients[0])
 |  | ||||||
| +            bad_version = sssd_version >= tasks.parse_version("2.8.2")
 |  | ||||||
| +            cond = (type == 'hybrid') and bad_version
 |  | ||||||
| +            with xfail_context(condition=cond,
 |  | ||||||
| +                               reason="https://pagure.io/freeipa/issue/9295"):
 |  | ||||||
| +                (uid, gid) = self.get_user_id(self.clients[0], nonposixuser)
 |  | ||||||
| +                assert (uid == self.uid_override and gid == self.gid_override)
 |  | ||||||
|              test_group = self.clients[0].run_command( |  | ||||||
|                  ["id", nonposixuser]).stdout_text |  | ||||||
| -            # version = tasks.get_sssd_version(self.clients[0])
 |  | ||||||
|              with xfail_context(type == "hybrid", |  | ||||||
|                                 'https://github.com/SSSD/sssd/issues/5989'): |  | ||||||
|                  assert "domain users@{0}".format(self.ad_domain) in test_group |  | ||||||
| @@ -1232,8 +1236,11 @@ class TestPosixAutoPrivateGroup(BaseTestTrust):
 |  | ||||||
|          posixuser = "testuser1@%s" % self.ad_domain |  | ||||||
|          self.mod_idrange_auto_private_group(type) |  | ||||||
|          if type == "true": |  | ||||||
| -            (uid, gid) = self.get_user_id(self.clients[0], posixuser)
 |  | ||||||
| -            assert uid == gid
 |  | ||||||
| +            sssd_version = tasks.get_sssd_version(self.clients[0])
 |  | ||||||
| +            with xfail_context(sssd_version >= tasks.parse_version("2.8.2"),
 |  | ||||||
| +                 "https://pagure.io/freeipa/issue/9295"):
 |  | ||||||
| +                (uid, gid) = self.get_user_id(self.clients[0], posixuser)
 |  | ||||||
| +                assert uid == gid
 |  | ||||||
|          else: |  | ||||||
|              for host in [self.master, self.clients[0]]: |  | ||||||
|                  result = host.run_command(['id', posixuser], raiseonerr=False) |  | ||||||
| -- 
 |  | ||||||
| 2.44.0 |  | ||||||
| 
 |  | ||||||
| From ed2a8eb0cefadfe0544074114facfef381349ae0 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| Date: Feb 12 2024 10:43:39 +0000 |  | ||||||
| Subject: ipatests: add xfail for autoprivate group test with override |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Because of SSSD issue 7169, secondary groups are not |  | ||||||
| retrieved when autoprivate group is set and an idoverride |  | ||||||
| replaces the user's primary group. |  | ||||||
| Mark the known issues as xfail. |  | ||||||
| 
 |  | ||||||
| Related: https://github.com/SSSD/sssd/issues/7169 |  | ||||||
| 
 |  | ||||||
| Signed-off-by: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| Reviewed-By: Anuja More <amore@redhat.com> |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
| 
 |  | ||||||
| diff --git a/ipatests/test_integration/test_trust.py b/ipatests/test_integration/test_trust.py
 |  | ||||||
| index 3b9f0fb..2b94514 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_trust.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_trust.py
 |  | ||||||
| @@ -1164,8 +1164,12 @@ class TestNonPosixAutoPrivateGroup(BaseTestTrust):
 |  | ||||||
|                  assert (uid == self.uid_override and gid == self.gid_override) |  | ||||||
|              test_group = self.clients[0].run_command( |  | ||||||
|                  ["id", nonposixuser]).stdout_text |  | ||||||
| -            with xfail_context(type == "hybrid",
 |  | ||||||
| -                               'https://github.com/SSSD/sssd/issues/5989'):
 |  | ||||||
| +            cond2 = ((type == 'false'
 |  | ||||||
| +                      and sssd_version >= tasks.parse_version("2.9.4"))
 |  | ||||||
| +                     or type == 'hybrid')
 |  | ||||||
| +            with xfail_context(cond2,
 |  | ||||||
| +                               'https://github.com/SSSD/sssd/issues/5989 '
 |  | ||||||
| +                               'and 7169'):
 |  | ||||||
|                  assert "domain users@{0}".format(self.ad_domain) in test_group |  | ||||||
|   |  | ||||||
|      @pytest.mark.parametrize('type', ['hybrid', 'true', "false"]) |  | ||||||
| @@ -1287,5 +1291,9 @@ class TestPosixAutoPrivateGroup(BaseTestTrust):
 |  | ||||||
|              assert(uid == self.uid_override |  | ||||||
|                     and gid == self.gid_override) |  | ||||||
|              result = self.clients[0].run_command(['id', posixuser]) |  | ||||||
| -            assert "10047(testgroup@{0})".format(
 |  | ||||||
| -                self.ad_domain) in result.stdout_text
 |  | ||||||
| +            sssd_version = tasks.get_sssd_version(self.clients[0])
 |  | ||||||
| +            bad_version = sssd_version >= tasks.parse_version("2.9.4")
 |  | ||||||
| +            with xfail_context(bad_version and type in ('false', 'hybrid'),
 |  | ||||||
| +                 "https://github.com/SSSD/sssd/issues/7169"):
 |  | ||||||
| +                assert "10047(testgroup@{0})".format(
 |  | ||||||
| +                    self.ad_domain) in result.stdout_text
 |  | ||||||
| 
 |  | ||||||
| From d5392300d77170ea3202ee80690ada8bf81b60b5 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| Date: Feb 12 2024 10:44:47 +0000 |  | ||||||
| Subject: ipatests: remove xfail thanks to sssd 2.9.4 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| SSSD 2.9.4 fixes some issues related to auto-private-group |  | ||||||
| 
 |  | ||||||
| Related: https://pagure.io/freeipa/issue/9295 |  | ||||||
| Signed-off-by: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| Reviewed-By: Anuja More <amore@redhat.com> |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
| 
 |  | ||||||
| diff --git a/ipatests/test_integration/test_trust.py b/ipatests/test_integration/test_trust.py
 |  | ||||||
| index 12f000c..3b9f0fb 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_trust.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_trust.py
 |  | ||||||
| @@ -1155,7 +1155,8 @@ class TestNonPosixAutoPrivateGroup(BaseTestTrust):
 |  | ||||||
|                                       ): |  | ||||||
|              self.mod_idrange_auto_private_group(type) |  | ||||||
|              sssd_version = tasks.get_sssd_version(self.clients[0]) |  | ||||||
| -            bad_version = sssd_version >= tasks.parse_version("2.8.2")
 |  | ||||||
| +            bad_version = (tasks.parse_version("2.8.2") <= sssd_version
 |  | ||||||
| +                           < tasks.parse_version("2.9.4"))
 |  | ||||||
|              cond = (type == 'hybrid') and bad_version |  | ||||||
|              with xfail_context(condition=cond, |  | ||||||
|                                 reason="https://pagure.io/freeipa/issue/9295"): |  | ||||||
| @@ -1237,7 +1238,9 @@ class TestPosixAutoPrivateGroup(BaseTestTrust):
 |  | ||||||
|          self.mod_idrange_auto_private_group(type) |  | ||||||
|          if type == "true": |  | ||||||
|              sssd_version = tasks.get_sssd_version(self.clients[0]) |  | ||||||
| -            with xfail_context(sssd_version >= tasks.parse_version("2.8.2"),
 |  | ||||||
| +            bad_version = (tasks.parse_version("2.8.2") <= sssd_version
 |  | ||||||
| +                           < tasks.parse_version("2.9.4"))
 |  | ||||||
| +            with xfail_context(bad_version,
 |  | ||||||
|                   "https://pagure.io/freeipa/issue/9295"): |  | ||||||
|                  (uid, gid) = self.get_user_id(self.clients[0], posixuser) |  | ||||||
|                  assert uid == gid |  | ||||||
| 
 |  | ||||||
| From 34d048ede0c439b3a53e02f8ace96ff91aa1609d Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| Date: Mar 14 2023 16:50:25 +0000 |  | ||||||
| Subject: ipatests: adapt for new automembership fixup behavior |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| The automembership fixup task now needs to be called |  | ||||||
| with --cleanup argument when the user expects automember |  | ||||||
| to remove user/hosts from automember groups. |  | ||||||
| Update the test to call create a cleanup task equivalent to |  | ||||||
| dsconf plugin automember fixup --cleanup |  | ||||||
| when it is needed. |  | ||||||
| 
 |  | ||||||
| Fixes: https://pagure.io/freeipa/issue/9313 |  | ||||||
| Signed-off-by: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com> |  | ||||||
| Reviewed-By: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
| 
 |  | ||||||
| diff --git a/ipatests/test_integration/test_automember.py b/ipatests/test_integration/test_automember.py
 |  | ||||||
| index 7acd0d7..8b27f4d 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_automember.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_automember.py
 |  | ||||||
| @@ -4,6 +4,7 @@
 |  | ||||||
|  """This covers tests for automemberfeature.""" |  | ||||||
|   |  | ||||||
|  from __future__ import absolute_import |  | ||||||
| +import uuid
 |  | ||||||
|   |  | ||||||
|  from ipapython.dn import DN |  | ||||||
|   |  | ||||||
| @@ -211,11 +212,27 @@ class TestAutounmembership(IntegrationTest):
 |  | ||||||
|              # Running automember-build so that user is part of correct group |  | ||||||
|              result = self.master.run_command(['ipa', 'automember-rebuild', |  | ||||||
|                                                '--users=%s' % user2]) |  | ||||||
| +            assert msg in result.stdout_text
 |  | ||||||
| +
 |  | ||||||
| +            # The additional --cleanup argument is required
 |  | ||||||
| +            cleanup_ldif = (
 |  | ||||||
| +                "dn: cn={cn},cn=automember rebuild membership,"
 |  | ||||||
| +                "cn=tasks,cn=config\n"
 |  | ||||||
| +                "changetype: add\n"
 |  | ||||||
| +                "objectclass: top\n"
 |  | ||||||
| +                "objectclass: extensibleObject\n"
 |  | ||||||
| +                "basedn: cn=users,cn=accounts,{suffix}\n"
 |  | ||||||
| +                "filter: (uid={user})\n"
 |  | ||||||
| +                "cleanup: yes\n"
 |  | ||||||
| +                "scope: sub"
 |  | ||||||
| +            ).format(cn=str(uuid.uuid4()),
 |  | ||||||
| +                     suffix=str(self.master.domain.basedn),
 |  | ||||||
| +                     user=user2)
 |  | ||||||
| +            tasks.ldapmodify_dm(self.master, cleanup_ldif)
 |  | ||||||
| +
 |  | ||||||
|              assert self.is_user_member_of_group(user2, group2) |  | ||||||
|              assert not self.is_user_member_of_group(user2, group1) |  | ||||||
|   |  | ||||||
| -            assert msg in result.stdout_text
 |  | ||||||
| -
 |  | ||||||
|          finally: |  | ||||||
|              # testcase cleanup |  | ||||||
|              self.remove_user_automember(user2, raiseonerr=False) |  | ||||||
| @@ -248,11 +265,27 @@ class TestAutounmembership(IntegrationTest):
 |  | ||||||
|              result = self.master.run_command( |  | ||||||
|                  ['ipa', 'automember-rebuild', '--hosts=%s' % host2] |  | ||||||
|              ) |  | ||||||
| +            assert msg in result.stdout_text
 |  | ||||||
| +
 |  | ||||||
| +            # The additional --cleanup argument is required
 |  | ||||||
| +            cleanup_ldif = (
 |  | ||||||
| +                "dn: cn={cn},cn=automember rebuild membership,"
 |  | ||||||
| +                "cn=tasks,cn=config\n"
 |  | ||||||
| +                "changetype: add\n"
 |  | ||||||
| +                "objectclass: top\n"
 |  | ||||||
| +                "objectclass: extensibleObject\n"
 |  | ||||||
| +                "basedn: cn=computers,cn=accounts,{suffix}\n"
 |  | ||||||
| +                "filter: (fqdn={fqdn})\n"
 |  | ||||||
| +                "cleanup: yes\n"
 |  | ||||||
| +                "scope: sub"
 |  | ||||||
| +            ).format(cn=str(uuid.uuid4()),
 |  | ||||||
| +                     suffix=str(self.master.domain.basedn),
 |  | ||||||
| +                     fqdn=host2)
 |  | ||||||
| +            tasks.ldapmodify_dm(self.master, cleanup_ldif)
 |  | ||||||
| +
 |  | ||||||
|              assert self.is_host_member_of_hostgroup(host2, hostgroup2) |  | ||||||
|              assert not self.is_host_member_of_hostgroup(host2, hostgroup1) |  | ||||||
|   |  | ||||||
| -            assert msg in result.stdout_text
 |  | ||||||
| -
 |  | ||||||
|          finally: |  | ||||||
|              # testcase cleanup |  | ||||||
|              self.remove_host_automember(host2, raiseonerr=False) |  | ||||||
| 
 |  | ||||||
| From 9b777390fbb6d4c683bf7d3e5f74d5443209b1d5 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Alexander Bokovoy <abokovoy@redhat.com> |  | ||||||
| Date: Fri, 24 Mar 2023 08:15:00 +0200 |  | ||||||
| Subject: [PATCH] test_xmlrpc: adopt to automember plugin message changes in |  | ||||||
|  389-ds |  | ||||||
| 
 |  | ||||||
| Another change in automember plugin messaging that breaks FreeIPA tests. |  | ||||||
| Use common substring to match. |  | ||||||
| 
 |  | ||||||
| Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com> |  | ||||||
| Reviewed-By: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| ---
 |  | ||||||
|  ipatests/test_xmlrpc/xmlrpc_test.py | 2 +- |  | ||||||
|  1 file changed, 1 insertion(+), 1 deletion(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/ipatests/test_xmlrpc/xmlrpc_test.py b/ipatests/test_xmlrpc/xmlrpc_test.py
 |  | ||||||
| index cf11721bfca..5fe1245dc65 100644
 |  | ||||||
| --- a/ipatests/test_xmlrpc/xmlrpc_test.py
 |  | ||||||
| +++ b/ipatests/test_xmlrpc/xmlrpc_test.py
 |  | ||||||
| @@ -64,7 +64,7 @@ def test(xs):
 |  | ||||||
|   |  | ||||||
|  # Matches an automember task finish message |  | ||||||
|  fuzzy_automember_message = Fuzzy( |  | ||||||
| -    r'^Automember rebuild task finished\. Processed \(\d+\) entries\.$'
 |  | ||||||
| +    r'^Automember rebuild task finished\. Processed \(\d+\) entries'
 |  | ||||||
|  ) |  | ||||||
|   |  | ||||||
|  # Matches trusted domain GUID, like u'463bf2be-3456-4a57-979e-120304f2a0eb' |  | ||||||
| From 8e8b97a2251329aec9633a5c7c644bc5034bc8c2 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Sudhir Menon <sumenon@redhat.com> |  | ||||||
| Date: Wed, 20 Mar 2024 14:29:46 +0530 |  | ||||||
| Subject: [PATCH] ipatests: Fixes for test_ipahealthcheck_ipansschainvalidation |  | ||||||
|  testcases. |  | ||||||
| 
 |  | ||||||
| Currently the test is using IPA_NSSDB_PWDFILE_TXT which is /etc/ipa/nssdb/pwdfile.txt |  | ||||||
| which causes error in STIG mode. |  | ||||||
| 
 |  | ||||||
| [root@master slapd-TESTRELM-TEST]# certutil -M -n 'TESTRELM.TEST IPA CA' -t ',,' -d . -f /etc/ipa/nssdb/pwdfile.txt |  | ||||||
| Incorrect password/PIN entered. |  | ||||||
| 
 |  | ||||||
| Hence modified the test to include paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE/pwd.txt. |  | ||||||
| 
 |  | ||||||
| Signed-off-by: Sudhir Menon <sumenon@redhat.com> |  | ||||||
| Reviewed-By: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| Reviewed-By: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| ---
 |  | ||||||
|  ipatests/test_integration/test_ipahealthcheck.py | 11 ++++++----- |  | ||||||
|  1 file changed, 6 insertions(+), 5 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
 |  | ||||||
| index 8aae9fad776..a96de7088aa 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_ipahealthcheck.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_ipahealthcheck.py
 |  | ||||||
| @@ -2731,17 +2731,18 @@ def remove_server_cert(self):
 |  | ||||||
|          Fixture to remove Server cert and revert the change. |  | ||||||
|          """ |  | ||||||
|          instance = realm_to_serverid(self.master.domain.realm) |  | ||||||
| +        instance_dir = paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % instance
 |  | ||||||
|          self.master.run_command( |  | ||||||
|              [ |  | ||||||
|                  "certutil", |  | ||||||
|                  "-L", |  | ||||||
|                  "-d", |  | ||||||
| -                paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % instance,
 |  | ||||||
| +                instance_dir,
 |  | ||||||
|                  "-n", |  | ||||||
|                  "Server-Cert", |  | ||||||
|                  "-a", |  | ||||||
|                  "-o", |  | ||||||
| -                paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % instance
 |  | ||||||
| +                instance_dir
 |  | ||||||
|                  + "/Server-Cert.pem", |  | ||||||
|              ] |  | ||||||
|          ) |  | ||||||
| @@ -2760,15 +2761,15 @@ def remove_server_cert(self):
 |  | ||||||
|              [ |  | ||||||
|                  "certutil", |  | ||||||
|                  "-d", |  | ||||||
| -                paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % instance,
 |  | ||||||
| +                instance_dir,
 |  | ||||||
|                  "-A", |  | ||||||
|                  "-i", |  | ||||||
| -                paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % instance
 |  | ||||||
| +                instance_dir
 |  | ||||||
|                  + "/Server-Cert.pem", |  | ||||||
|                  "-t", |  | ||||||
|                  "u,u,u", |  | ||||||
|                  "-f", |  | ||||||
| -                paths.IPA_NSSDB_PWDFILE_TXT,
 |  | ||||||
| +                "%s/pwdfile.txt" % instance_dir,
 |  | ||||||
|                  "-n", |  | ||||||
|                  "Server-Cert", |  | ||||||
|              ] |  | ||||||
| @ -1,341 +0,0 @@ | |||||||
| From 0a48726e104282fb40d8f471ebb306bc9134cb0c Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Julien Rische <jrische@redhat.com> |  | ||||||
| Date: Tue, 19 Mar 2024 12:24:40 +0100 |  | ||||||
| Subject: [PATCH] kdb: fix vulnerability in GCD rules handling |  | ||||||
| 
 |  | ||||||
| The initial implementation of MS-SFU by MIT Kerberos was missing some |  | ||||||
| a condition for granting the "forwardable" flag on S4U2Self tickets. |  | ||||||
| Fixing this mistake required to add a special case for the |  | ||||||
| check_allowed_to_delegate() function: if the target service argument is |  | ||||||
| NULL, then it means the KDC is probing for general constrained |  | ||||||
| delegation rules, not actually checking a specific S4U2Proxy request. |  | ||||||
| 
 |  | ||||||
| In commit e86807b5, the behavior of ipadb_match_acl() was modified to |  | ||||||
| match the changes from upstream MIT Kerberos a441fbe3. However, a |  | ||||||
| mistake resulted in this mechanism to apply in cases where target |  | ||||||
| service argument is set AND unset. This results in S4U2Proxy requests to |  | ||||||
| be accepted regardless of the fact there is a matching service |  | ||||||
| delegation rule or not. |  | ||||||
| 
 |  | ||||||
| This vulnerability does not affect services having RBCD (resource-based |  | ||||||
| constrained delegation) rules. |  | ||||||
| 
 |  | ||||||
| This fixes CVE-2024-2698 |  | ||||||
| 
 |  | ||||||
| Signed-off-by: Julien Rische <jrische@redhat.com> |  | ||||||
| ---
 |  | ||||||
|  daemons/ipa-kdb/README.s4u2proxy.txt |  19 ++- |  | ||||||
|  daemons/ipa-kdb/ipa_kdb_delegation.c | 191 +++++++++++++++------------ |  | ||||||
|  2 files changed, 118 insertions(+), 92 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/daemons/ipa-kdb/README.s4u2proxy.txt b/daemons/ipa-kdb/README.s4u2proxy.txt
 |  | ||||||
| index 254fcc4d1..ab34aff36 100644
 |  | ||||||
| --- a/daemons/ipa-kdb/README.s4u2proxy.txt
 |  | ||||||
| +++ b/daemons/ipa-kdb/README.s4u2proxy.txt
 |  | ||||||
| @@ -12,9 +12,7 @@ much more easily managed.
 |  | ||||||
|   |  | ||||||
|  The grouping mechanism has been built so that lookup is highly optimized |  | ||||||
|  and is basically reduced to a single search that uses the derefernce |  | ||||||
| -control. Speed is very important in this case because KDC operations
 |  | ||||||
| -time out very quickly and unless we add a caching layer in ipa-kdb we
 |  | ||||||
| -must keep the number of searches down to avoid client timeouts.
 |  | ||||||
| +control.
 |  | ||||||
|   |  | ||||||
|  The grouping mechanism is very simple a groupOfPrincipals object is |  | ||||||
|  introduced, this Auxiliary class have a single optional attribute called |  | ||||||
| @@ -112,8 +110,7 @@ kinit -kt /etc/httpd/conf/ipa.keytab HTTP/ipaserver.example.com
 |  | ||||||
|  kvno -U admin HTTP/ipaserver.example.com |  | ||||||
|   |  | ||||||
|  # Perform S4U2Proxy |  | ||||||
| -kvno -k /etc/httpd/conf/ipa.keytab -U admin -P HTTP/ipaserver.example.com
 |  | ||||||
| -ldap/ipaserver.example.com
 |  | ||||||
| +kvno -U admin -P ldap/ipaserver.example.com
 |  | ||||||
|   |  | ||||||
|   |  | ||||||
|  If this works it means you successfully impersonated the admin user with |  | ||||||
| @@ -125,6 +122,18 @@ modprinc -ok_to_auth_as_delegate HTTP/ipaserver.example.com
 |  | ||||||
|  Simo. |  | ||||||
|   |  | ||||||
|   |  | ||||||
| +If IPA is compiled with krb5 1.20 and newer (KDB DAL >= 9), then the
 |  | ||||||
| +behavior of S4U2Self changes: S4U2Self TGS-REQs produce forwardable
 |  | ||||||
| +tickets for all requesters, except if the requester principal is set as
 |  | ||||||
| +the proxy (impersonating service) in at least one `servicedelegation`
 |  | ||||||
| +rule. In this case, even if the rule has no target, the KDC will
 |  | ||||||
| +response to S4U2Self requests with a non-forwardable ticket. Hence,
 |  | ||||||
| +granting the `ok_to_auth_as_delegate` permission to the proxy service
 |  | ||||||
| +remains the only way for this service to obtain the evidence ticket
 |  | ||||||
| +required for general constrained delegation requests if this ticket is
 |  | ||||||
| +not provided by the client.
 |  | ||||||
| +
 |  | ||||||
| +
 |  | ||||||
|  [1] |  | ||||||
|  Note that here I use the term proxy in a different way than it is used in |  | ||||||
|  the krb interfaces. It may seem a bit confusing but I think people will |  | ||||||
| diff --git a/daemons/ipa-kdb/ipa_kdb_delegation.c b/daemons/ipa-kdb/ipa_kdb_delegation.c
 |  | ||||||
| index de82174ad..3581f3c79 100644
 |  | ||||||
| --- a/daemons/ipa-kdb/ipa_kdb_delegation.c
 |  | ||||||
| +++ b/daemons/ipa-kdb/ipa_kdb_delegation.c
 |  | ||||||
| @@ -91,120 +91,110 @@ static bool ipadb_match_member(char *princ, LDAPDerefRes *dres)
 |  | ||||||
|      return false; |  | ||||||
|  } |  | ||||||
|   |  | ||||||
| -static krb5_error_code ipadb_match_acl(krb5_context kcontext,
 |  | ||||||
| -                                       LDAPMessage *results,
 |  | ||||||
| -                                       krb5_const_principal client,
 |  | ||||||
| -                                       krb5_const_principal target)
 |  | ||||||
| +#if KRB5_KDB_DAL_MAJOR_VERSION >= 9
 |  | ||||||
| +static krb5_error_code
 |  | ||||||
| +ipadb_has_acl(krb5_context kcontext, LDAPMessage *ldap_acl, bool *res)
 |  | ||||||
|  { |  | ||||||
|      struct ipadb_context *ipactx; |  | ||||||
| -    krb5_error_code kerr;
 |  | ||||||
| -    LDAPMessage *lentry;
 |  | ||||||
| -    LDAPDerefRes *deref_results;
 |  | ||||||
| -    LDAPDerefRes *dres;
 |  | ||||||
| -    char *client_princ = NULL;
 |  | ||||||
| -    char *target_princ = NULL;
 |  | ||||||
| -    bool client_missing;
 |  | ||||||
| -    bool client_found;
 |  | ||||||
| -    bool target_found;
 |  | ||||||
| -    bool is_constraint_delegation = false;
 |  | ||||||
| -    size_t nrules = 0;
 |  | ||||||
| -    int ret;
 |  | ||||||
| +    bool in_res = false;
 |  | ||||||
| +    krb5_error_code kerr = 0;
 |  | ||||||
|   |  | ||||||
|      ipactx = ipadb_get_context(kcontext); |  | ||||||
| -    if (!ipactx) {
 |  | ||||||
| +    if (!ipactx)
 |  | ||||||
|          return KRB5_KDB_DBNOTINITED; |  | ||||||
| -    }
 |  | ||||||
|   |  | ||||||
| -    if ((client != NULL) && (target != NULL)) {
 |  | ||||||
| -        kerr = krb5_unparse_name(kcontext, client, &client_princ);
 |  | ||||||
| -        if (kerr != 0) {
 |  | ||||||
| -            goto done;
 |  | ||||||
| -        }
 |  | ||||||
| -        kerr = krb5_unparse_name(kcontext, target, &target_princ);
 |  | ||||||
| -        if (kerr != 0) {
 |  | ||||||
| -            goto done;
 |  | ||||||
| -        }
 |  | ||||||
| -    } else {
 |  | ||||||
| -        is_constraint_delegation = true;
 |  | ||||||
| +    switch (ldap_count_entries(ipactx->lcontext, ldap_acl)) {
 |  | ||||||
| +    case 0:
 |  | ||||||
| +        break;
 |  | ||||||
| +    case -1:
 |  | ||||||
| +        kerr = EINVAL;
 |  | ||||||
| +        goto end;
 |  | ||||||
| +    default:
 |  | ||||||
| +        in_res = true;
 |  | ||||||
| +        goto end;
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| -    lentry = ldap_first_entry(ipactx->lcontext, results);
 |  | ||||||
| -    if (!lentry) {
 |  | ||||||
| -        kerr = ENOENT;
 |  | ||||||
| -        goto done;
 |  | ||||||
| -    }
 |  | ||||||
| +end:
 |  | ||||||
| +    if (res)
 |  | ||||||
| +        *res = in_res;
 |  | ||||||
| +
 |  | ||||||
| +    return kerr;
 |  | ||||||
| +}
 |  | ||||||
| +#endif
 |  | ||||||
| +
 |  | ||||||
| +static krb5_error_code
 |  | ||||||
| +ipadb_match_acl(krb5_context kcontext, LDAPMessage *ldap_acl,
 |  | ||||||
| +                krb5_const_principal client, krb5_const_principal target)
 |  | ||||||
| +{
 |  | ||||||
| +    struct ipadb_context *ipactx;
 |  | ||||||
| +    LDAPMessage *rule;
 |  | ||||||
| +    LDAPDerefRes *acis, *aci;
 |  | ||||||
| +    char *client_princ = NULL, *target_princ= NULL;
 |  | ||||||
| +    bool client_missing, client_found, target_found;
 |  | ||||||
| +    int lerr;
 |  | ||||||
| +    krb5_error_code kerr;
 |  | ||||||
| +
 |  | ||||||
| +    ipactx = ipadb_get_context(kcontext);
 |  | ||||||
| +    if (!ipactx)
 |  | ||||||
| +        return KRB5_KDB_DBNOTINITED;
 |  | ||||||
| +
 |  | ||||||
| +    kerr = krb5_unparse_name(kcontext, client, &client_princ);
 |  | ||||||
| +    if (kerr)
 |  | ||||||
| +        goto end;
 |  | ||||||
| +
 |  | ||||||
| +    kerr = krb5_unparse_name(kcontext, target, &target_princ);
 |  | ||||||
| +    if (kerr)
 |  | ||||||
| +        goto end;
 |  | ||||||
|   |  | ||||||
|      /* the default is that we fail */ |  | ||||||
| -    kerr = ENOENT;
 |  | ||||||
| +    kerr = KRB5KDC_ERR_BADOPTION;
 |  | ||||||
|   |  | ||||||
| -    while (lentry) {
 |  | ||||||
| +    for (rule = ldap_first_entry(ipactx->lcontext, ldap_acl);
 |  | ||||||
| +         rule;
 |  | ||||||
| +         rule = ldap_next_entry(ipactx->lcontext, rule))
 |  | ||||||
| +    {
 |  | ||||||
|          /* both client and target must be found in the same ACI */ |  | ||||||
|          client_missing = true; |  | ||||||
|          client_found = false; |  | ||||||
|          target_found = false; |  | ||||||
|   |  | ||||||
| -        ret = ipadb_ldap_deref_results(ipactx->lcontext, lentry,
 |  | ||||||
| -                                       &deref_results);
 |  | ||||||
| -        switch (ret) {
 |  | ||||||
| +        lerr = ipadb_ldap_deref_results(ipactx->lcontext, rule, &acis);
 |  | ||||||
| +        switch (lerr) {
 |  | ||||||
|          case 0: |  | ||||||
| -            for (dres = deref_results; dres; dres = dres->next) {
 |  | ||||||
| -                nrules++;
 |  | ||||||
| -                if (is_constraint_delegation) {
 |  | ||||||
| -                    /*
 |  | ||||||
| -                        Microsoft revised the S4U2Proxy rules for forwardable
 |  | ||||||
| -                        tickets.  All S4U2Proxy operations require forwardable
 |  | ||||||
| -                        evidence tickets, but S4U2Self should issue a
 |  | ||||||
| -                        forwardable ticket if the requesting service has no
 |  | ||||||
| -                        ok-to-auth-as-delegate bit but also no constrained
 |  | ||||||
| -                        delegation privileges for traditional S4U2Proxy.
 |  | ||||||
| -                        Implement these rules, extending the
 |  | ||||||
| -                        check_allowed_to_delegate() DAL method so that the KDC
 |  | ||||||
| -                        can ask if a principal has any delegation privileges.
 |  | ||||||
| -
 |  | ||||||
| -                        Since target principal is NULL and client principal is
 |  | ||||||
| -                        NULL in this case, we simply calculate number of rules associated
 |  | ||||||
| -                        with the server principal to decide whether to deny forwardable bit
 |  | ||||||
| -                    */
 |  | ||||||
| -                    continue;
 |  | ||||||
| -                }
 |  | ||||||
| -                if (client_found == false &&
 |  | ||||||
| -                    strcasecmp(dres->derefAttr, "ipaAllowToImpersonate") == 0) {
 |  | ||||||
| +            for (aci = acis; aci; aci = aci->next) {
 |  | ||||||
| +                if (!client_found &&
 |  | ||||||
| +                    0 == strcasecmp(aci->derefAttr, "ipaAllowToImpersonate"))
 |  | ||||||
| +                {
 |  | ||||||
|                      /* NOTE: client_missing is used to signal that the |  | ||||||
|                       * attribute was completely missing. This signals that |  | ||||||
|                       * ANY client is allowed to be impersonated. |  | ||||||
|                       * This logic is valid only for clients, not for targets */ |  | ||||||
|                      client_missing = false; |  | ||||||
| -                    client_found = ipadb_match_member(client_princ, dres);
 |  | ||||||
| +                    client_found = ipadb_match_member(client_princ, aci);
 |  | ||||||
|                  } |  | ||||||
| -                if (target_found == false &&
 |  | ||||||
| -                    strcasecmp(dres->derefAttr, "ipaAllowedTarget") == 0) {
 |  | ||||||
| -                    target_found = ipadb_match_member(target_princ, dres);
 |  | ||||||
| +                if (!target_found &&
 |  | ||||||
| +                    0 == strcasecmp(aci->derefAttr, "ipaAllowedTarget"))
 |  | ||||||
| +                {
 |  | ||||||
| +                    target_found = ipadb_match_member(target_princ, aci);
 |  | ||||||
|                  } |  | ||||||
|              } |  | ||||||
|   |  | ||||||
| -            ldap_derefresponse_free(deref_results);
 |  | ||||||
| +            ldap_derefresponse_free(acis);
 |  | ||||||
|              break; |  | ||||||
|          case ENOENT: |  | ||||||
|              break; |  | ||||||
|          default: |  | ||||||
| -            kerr = ret;
 |  | ||||||
| -            goto done;
 |  | ||||||
| +            kerr = lerr;
 |  | ||||||
| +            goto end;
 |  | ||||||
|          } |  | ||||||
|   |  | ||||||
| -        if ((client_found == true || client_missing == true) &&
 |  | ||||||
| -            target_found == true) {
 |  | ||||||
| +        if ((client_found || client_missing) && target_found) {
 |  | ||||||
|              kerr = 0; |  | ||||||
| -            goto done;
 |  | ||||||
| +            goto end;
 |  | ||||||
|          } |  | ||||||
| -
 |  | ||||||
| -        lentry = ldap_next_entry(ipactx->lcontext, lentry);
 |  | ||||||
| -    }
 |  | ||||||
| -
 |  | ||||||
| -    if (nrules > 0) {
 |  | ||||||
| -        kerr = 0;
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| -done:
 |  | ||||||
| +end:
 |  | ||||||
|      krb5_free_unparsed_name(kcontext, client_princ); |  | ||||||
|      krb5_free_unparsed_name(kcontext, target_princ); |  | ||||||
|      return kerr; |  | ||||||
| @@ -223,7 +213,7 @@ krb5_error_code ipadb_check_allowed_to_delegate(krb5_context kcontext,
 |  | ||||||
|      char *srv_principal = NULL; |  | ||||||
|      krb5_db_entry *proxy_entry = NULL; |  | ||||||
|      struct ipadb_e_data *ied_server, *ied_proxy; |  | ||||||
| -    LDAPMessage *res = NULL;
 |  | ||||||
| +    LDAPMessage *ldap_gcd_acl = NULL;
 |  | ||||||
|   |  | ||||||
|      if (proxy != NULL) { |  | ||||||
|          /* Handle the case where server == proxy, this is allowed in S4U */ |  | ||||||
| @@ -261,26 +251,53 @@ krb5_error_code ipadb_check_allowed_to_delegate(krb5_context kcontext,
 |  | ||||||
|          goto done; |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| -    kerr = ipadb_get_delegation_acl(kcontext, srv_principal, &res);
 |  | ||||||
| +    /* Load general constrained delegation rules */
 |  | ||||||
| +    kerr = ipadb_get_delegation_acl(kcontext, srv_principal, &ldap_gcd_acl);
 |  | ||||||
|      if (kerr) { |  | ||||||
|          goto done; |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| -    kerr = ipadb_match_acl(kcontext, res, client, proxy);
 |  | ||||||
| -    if (kerr) {
 |  | ||||||
| -        goto done;
 |  | ||||||
| +#if KRB5_KDB_DAL_MAJOR_VERSION >= 9
 |  | ||||||
| +    /*
 |  | ||||||
| +     * Microsoft revised the S4U2Proxy rules for forwardable tickets.  All
 |  | ||||||
| +     * S4U2Proxy operations require forwardable evidence tickets, but
 |  | ||||||
| +     * S4U2Self should issue a forwardable ticket if the requesting service
 |  | ||||||
| +     * has no ok-to-auth-as-delegate bit but also no constrained delegation
 |  | ||||||
| +     * privileges for traditional S4U2Proxy.  Implement these rules,
 |  | ||||||
| +     * extending the check_allowed_to_delegate() DAL method so that the KDC
 |  | ||||||
| +     * can ask if a principal has any delegation privileges.
 |  | ||||||
| +     *
 |  | ||||||
| +     * If target service principal is NULL, and the impersonating service has
 |  | ||||||
| +     * at least one GCD rule, then succeed.
 |  | ||||||
| +     */
 |  | ||||||
| +    if (!proxy) {
 |  | ||||||
| +        bool has_gcd_rules;
 |  | ||||||
| +
 |  | ||||||
| +        kerr = ipadb_has_acl(kcontext, ldap_gcd_acl, &has_gcd_rules);
 |  | ||||||
| +        if (!kerr)
 |  | ||||||
| +            kerr = has_gcd_rules ? 0 : KRB5KDC_ERR_BADOPTION;
 |  | ||||||
| +    } else if (client) {
 |  | ||||||
| +#else
 |  | ||||||
| +    if (client && proxy) {
 |  | ||||||
| +#endif
 |  | ||||||
| +        kerr = ipadb_match_acl(kcontext, ldap_gcd_acl, client, proxy);
 |  | ||||||
| +    } else {
 |  | ||||||
| +        /* client and/or proxy is missing */
 |  | ||||||
| +        kerr = KRB5KDC_ERR_BADOPTION;
 |  | ||||||
|      } |  | ||||||
| +    if (kerr)
 |  | ||||||
| +        goto done;
 |  | ||||||
|   |  | ||||||
|  done: |  | ||||||
|      if (kerr) { |  | ||||||
| -#if KRB5_KDB_DAL_MAJOR_VERSION < 9
 |  | ||||||
| -        kerr = KRB5KDC_ERR_POLICY;
 |  | ||||||
| -#else
 |  | ||||||
| +#if KRB5_KDB_DAL_MAJOR_VERSION >= 9
 |  | ||||||
|          kerr = KRB5KDC_ERR_BADOPTION; |  | ||||||
| +#else
 |  | ||||||
| +        kerr = KRB5KDC_ERR_POLICY;
 |  | ||||||
|  #endif |  | ||||||
|      } |  | ||||||
|      ipadb_free_principal(kcontext, proxy_entry); |  | ||||||
|      krb5_free_unparsed_name(kcontext, srv_principal); |  | ||||||
| -    ldap_msgfree(res);
 |  | ||||||
| +    ldap_msgfree(ldap_gcd_acl);
 |  | ||||||
|      return kerr; |  | ||||||
|  } |  | ||||||
| -- 
 |  | ||||||
| 2.44.0 |  | ||||||
| 
 |  | ||||||
| @ -1,615 +0,0 @@ | |||||||
| From 542e12325afc2f64298f90296760235bfdcef04a Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Julien Rische <jrische@redhat.com> |  | ||||||
| Date: Mon, 25 Mar 2024 18:25:52 +0200 |  | ||||||
| Subject: [PATCH] kdb: apply combinatorial logic for ticket flags |  | ||||||
| 
 |  | ||||||
| The initial design for ticket flags was implementing this logic: |  | ||||||
| * If a ticket policy is defined for the principal entry, use flags from |  | ||||||
|   this policy if they are set. Otherwise, use default ticket flags. |  | ||||||
| * If no ticket policy is defined for the principal entry, but there is a |  | ||||||
|   global one, use flags from the global ticket policy if they are set. |  | ||||||
|   Otherwise, use default ticket flags. |  | ||||||
| * If no policy (principal nor global) is defined, use default ticket |  | ||||||
|   flags. |  | ||||||
| 
 |  | ||||||
| However, this logic was broken by a1165ffb which introduced creation of |  | ||||||
| a principal-level ticket policy in case the ticket flag set is modified. |  | ||||||
| This was typically the case for the -allow_tix flag, which was set |  | ||||||
| virtually by the KDB driver when a user was locked until they initialize |  | ||||||
| their password on first kinit pre-authentication. |  | ||||||
| 
 |  | ||||||
| This was causing multiple issues, which are mitigated by the new |  | ||||||
| approach: |  | ||||||
| 
 |  | ||||||
| Now flags from each level are combined together. There flags like |  | ||||||
| +requires_preauth which are set systematically by the KDB diver, as
 |  | ||||||
| well as -allow_tix which is set based on the value of "nsAccountLock". |  | ||||||
| This commit also adds the implicit -allow_svr ticket flag for user |  | ||||||
| principals to protect users against Kerberoast-type attacks. None of |  | ||||||
| these flags are stored in the LDAP database, they are hard-coded in the |  | ||||||
| KDB driver. |  | ||||||
| 
 |  | ||||||
| In addition to these "virtual" ticket flags, flags from both global and |  | ||||||
| principal ticket policies are applied (if these policies exist). |  | ||||||
| 
 |  | ||||||
| Principal ticket policies are not supported for hosts and services, but |  | ||||||
| this is only an HTTP API limitation. The "krbTicketPolicyAux" object |  | ||||||
| class is supported for all account types. This is required for ticket |  | ||||||
| flags like +ok_to_auth_as_delegate. Such flags can be set using "ipa |  | ||||||
| host-mod" and "ipa serivce-mod", or using kadmin's "modprinc". |  | ||||||
| 
 |  | ||||||
| It is possible to ignore flags from the global ticket policy or default |  | ||||||
| flags like -allow_svr for a user principal by setting the |  | ||||||
| "final_user_tkt_flags" string attribute to "true" in kadmin. In this |  | ||||||
| case, any ticket flag can be configured in the principal ticket policy, |  | ||||||
| except requires_preauth and allow_tix. |  | ||||||
| 
 |  | ||||||
| When in IPA setup mode (using the "ipa-setup-override-restrictions" KDB |  | ||||||
| argument), all the system described above is disabled and ticket flags |  | ||||||
| are written in the principal ticket policy as they are provided. This is |  | ||||||
| required to initialize the Kerberos LDAP container during IPA server |  | ||||||
| installation. |  | ||||||
| 
 |  | ||||||
| This fixes CVE-2024-3183 |  | ||||||
| 
 |  | ||||||
| Signed-off-by: Julien Rische <jrische@redhat.com> |  | ||||||
| ---
 |  | ||||||
|  daemons/ipa-kdb/ipa_kdb.h            |  43 ++++ |  | ||||||
|  daemons/ipa-kdb/ipa_kdb_principals.c | 353 +++++++++++++++++++++++---- |  | ||||||
|  util/ipa_krb5.c                      |  18 ++ |  | ||||||
|  util/ipa_krb5.h                      |   4 + |  | ||||||
|  4 files changed, 365 insertions(+), 53 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h
 |  | ||||||
| index 7baf4697f..85cabe142 100644
 |  | ||||||
| --- a/daemons/ipa-kdb/ipa_kdb.h
 |  | ||||||
| +++ b/daemons/ipa-kdb/ipa_kdb.h
 |  | ||||||
| @@ -94,6 +94,34 @@
 |  | ||||||
|  #define IPA_KRB_AUTHZ_DATA_ATTR "ipaKrbAuthzData" |  | ||||||
|  #define IPA_USER_AUTH_TYPE "ipaUserAuthType" |  | ||||||
|   |  | ||||||
| +/* Virtual managed ticket flags like "-allow_tix", are always controlled by the
 |  | ||||||
| + * "nsAccountLock" attribute, such flags should never be set in the database.
 |  | ||||||
| + * The following expression combine all of them, and is used to filter them
 |  | ||||||
| + * out. */
 |  | ||||||
| +#define IPA_KDB_TKTFLAGS_VIRTUAL_MANAGED_ALL          (KRB5_KDB_DISALLOW_ALL_TIX)
 |  | ||||||
| +
 |  | ||||||
| +/* Virtual static ticket flags are hard-coded in the KDB driver. */
 |  | ||||||
| +/*   Virtual static mandatory flags are set systematically and implicitly for all
 |  | ||||||
| + *   principals. They are filtered out from database ticket flags updates.
 |  | ||||||
| + *   (However, "KRB5_KDB_REQUIRES_PRE_AUTH" can still be unset by the
 |  | ||||||
| + *   "KDC:Disable Default Preauth for SPNs" global setting) */
 |  | ||||||
| +#define IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_MANDATORY     (KRB5_KDB_REQUIRES_PRE_AUTH)
 |  | ||||||
| +/*   Virtual static default ticket flags are implicitly set for user and non-user
 |  | ||||||
| + *   (SPN) principals, and not stored in the database.
 |  | ||||||
| + *   (Except if the "IPA_KDB_STRATTR_FINAL_TKTFLAGS" string attribute is "true"
 |  | ||||||
| + *   the principal) */
 |  | ||||||
| +/*     Virtual static default user ticket flags are set for users only. The
 |  | ||||||
| + *     "-allow_svr" flag is set to protect them from CVE-2024-3183. */
 |  | ||||||
| +#define IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_DEFAULTS_USER (KRB5_KDB_DISALLOW_SVR)
 |  | ||||||
| +#define IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_DEFAULTS_SPN  (0)
 |  | ||||||
| +
 |  | ||||||
| +/* If this string attribute is set to "true", then only the virtual managed and
 |  | ||||||
| + * virtual static mandatory ticket flags are applied and filtered out from
 |  | ||||||
| + * database read and write operations for the concerned user principal.
 |  | ||||||
| + * Configurable principal ticket flags are applied, but not the configurable
 |  | ||||||
| + * global ticket policy flags. */
 |  | ||||||
| +#define IPA_KDB_STRATTR_FINAL_USER_TKTFLAGS "final_user_tkt_flags"
 |  | ||||||
| +
 |  | ||||||
|  struct ipadb_mspac; |  | ||||||
|  struct dom_sid; |  | ||||||
|   |  | ||||||
| @@ -178,6 +206,21 @@ struct ipadb_e_data {
 |  | ||||||
|      struct dom_sid *sid; |  | ||||||
|  }; |  | ||||||
|   |  | ||||||
| +inline static krb5_error_code
 |  | ||||||
| +ipadb_get_edata(krb5_db_entry *entry, struct ipadb_e_data **ied)
 |  | ||||||
| +{
 |  | ||||||
| +    struct ipadb_e_data *in_ied;
 |  | ||||||
| +
 |  | ||||||
| +    in_ied = (struct ipadb_e_data *)entry->e_data;
 |  | ||||||
| +    if (!in_ied || in_ied->magic != IPA_E_DATA_MAGIC)
 |  | ||||||
| +        return EINVAL;
 |  | ||||||
| +
 |  | ||||||
| +    if (ied)
 |  | ||||||
| +        *ied = in_ied;
 |  | ||||||
| +
 |  | ||||||
| +    return 0;
 |  | ||||||
| +}
 |  | ||||||
| +
 |  | ||||||
|  struct ipadb_context *ipadb_get_context(krb5_context kcontext); |  | ||||||
|  int ipadb_get_connection(struct ipadb_context *ipactx); |  | ||||||
|   |  | ||||||
| diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c
 |  | ||||||
| index 07cc87746..6eb542d4f 100644
 |  | ||||||
| --- a/daemons/ipa-kdb/ipa_kdb_principals.c
 |  | ||||||
| +++ b/daemons/ipa-kdb/ipa_kdb_principals.c
 |  | ||||||
| @@ -706,9 +706,12 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext,
 |  | ||||||
|                                   "krbTicketFlags", &result); |  | ||||||
|      if (ret == 0) { |  | ||||||
|          entry->attributes = result; |  | ||||||
| -    } else {
 |  | ||||||
| -        *polmask |= TKTFLAGS_BIT;
 |  | ||||||
|      } |  | ||||||
| +    /* Since principal, global policy, and virtual ticket flags are combined,
 |  | ||||||
| +     * they must always be resolved, except if we are in IPA setup mode (because
 |  | ||||||
| +     * ticket policies and virtual ticket flags are irrelevant in this case). */
 |  | ||||||
| +    if (!ipactx->override_restrictions)
 |  | ||||||
| +        *polmask |= TKTFLAGS_BIT;
 |  | ||||||
|   |  | ||||||
|      ret = ipadb_ldap_attr_to_int(lcontext, lentry, |  | ||||||
|                                   "krbMaxTicketLife", &result); |  | ||||||
| @@ -912,7 +915,12 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext,
 |  | ||||||
|          goto done; |  | ||||||
|      } |  | ||||||
|      if (ret == 0) { |  | ||||||
| -        ied->ipa_user = true;
 |  | ||||||
| +        if (1 == krb5_princ_size(kcontext, entry->princ)) {
 |  | ||||||
| +            /* A principal must be a POSIX account AND have only one element to
 |  | ||||||
| +             * be considered a user (this is to filter out CIFS principals). */
 |  | ||||||
| +            ied->ipa_user = true;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
|          ret = ipadb_ldap_attr_to_str(lcontext, lentry, |  | ||||||
|                                       "uid", &uidstring); |  | ||||||
|          if (ret != 0 && ret != ENOENT) { |  | ||||||
| @@ -1251,23 +1259,150 @@ done:
 |  | ||||||
|      return ret; |  | ||||||
|  } |  | ||||||
|   |  | ||||||
| -static krb5_flags maybe_require_preauth(struct ipadb_context *ipactx,
 |  | ||||||
| -                                        krb5_db_entry *entry)
 |  | ||||||
| +static krb5_error_code
 |  | ||||||
| +are_final_tktflags(struct ipadb_context *ipactx, krb5_db_entry *entry,
 |  | ||||||
| +                   bool *final_tktflags)
 |  | ||||||
|  { |  | ||||||
| -    const struct ipadb_global_config *config;
 |  | ||||||
| +    krb5_error_code kerr;
 |  | ||||||
|      struct ipadb_e_data *ied; |  | ||||||
| +    char *str = NULL;
 |  | ||||||
| +    bool in_final_tktflags = false;
 |  | ||||||
|   |  | ||||||
| -    config = ipadb_get_global_config(ipactx);
 |  | ||||||
| -    if (config && config->disable_preauth_for_spns) {
 |  | ||||||
| -        ied = (struct ipadb_e_data *)entry->e_data;
 |  | ||||||
| -        if (ied && ied->ipa_user != true) {
 |  | ||||||
| -            /* not a user, assume SPN */
 |  | ||||||
| -            return 0;
 |  | ||||||
| -        }
 |  | ||||||
| +    kerr = ipadb_get_edata(entry, &ied);
 |  | ||||||
| +    if (kerr)
 |  | ||||||
| +        goto end;
 |  | ||||||
| +
 |  | ||||||
| +    if (!ied->ipa_user) {
 |  | ||||||
| +        kerr = 0;
 |  | ||||||
| +        goto end;
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| -    /* By default require preauth for all principals */
 |  | ||||||
| -    return KRB5_KDB_REQUIRES_PRE_AUTH;
 |  | ||||||
| +    kerr = krb5_dbe_get_string(ipactx->kcontext, entry,
 |  | ||||||
| +                               IPA_KDB_STRATTR_FINAL_USER_TKTFLAGS, &str);
 |  | ||||||
| +    if (kerr)
 |  | ||||||
| +        goto end;
 |  | ||||||
| +
 |  | ||||||
| +    in_final_tktflags = str && ipa_krb5_parse_bool(str);
 |  | ||||||
| +
 |  | ||||||
| +end:
 |  | ||||||
| +    if (final_tktflags)
 |  | ||||||
| +        *final_tktflags = in_final_tktflags;
 |  | ||||||
| +
 |  | ||||||
| +    krb5_dbe_free_string(ipactx->kcontext, str);
 |  | ||||||
| +    return kerr;
 |  | ||||||
| +}
 |  | ||||||
| +
 |  | ||||||
| +static krb5_error_code
 |  | ||||||
| +add_virtual_static_tktflags(struct ipadb_context *ipactx, krb5_db_entry *entry,
 |  | ||||||
| +                            krb5_flags *tktflags)
 |  | ||||||
| +{
 |  | ||||||
| +    krb5_error_code kerr;
 |  | ||||||
| +    krb5_flags vsflg;
 |  | ||||||
| +    bool final_tktflags;
 |  | ||||||
| +    const struct ipadb_global_config *gcfg;
 |  | ||||||
| +    struct ipadb_e_data *ied;
 |  | ||||||
| +
 |  | ||||||
| +    vsflg = IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_MANDATORY;
 |  | ||||||
| +
 |  | ||||||
| +    kerr = ipadb_get_edata(entry, &ied);
 |  | ||||||
| +    if (kerr)
 |  | ||||||
| +        goto end;
 |  | ||||||
| +
 |  | ||||||
| +    kerr = are_final_tktflags(ipactx, entry, &final_tktflags);
 |  | ||||||
| +    if (kerr)
 |  | ||||||
| +        goto end;
 |  | ||||||
| +
 |  | ||||||
| +    /* In practice, principal ticket flags cannot be final for SPNs. */
 |  | ||||||
| +    if (!final_tktflags)
 |  | ||||||
| +        vsflg |= ied->ipa_user ? IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_DEFAULTS_USER
 |  | ||||||
| +                               : IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_DEFAULTS_SPN;
 |  | ||||||
| +
 |  | ||||||
| +    if (!ied->ipa_user) {
 |  | ||||||
| +        gcfg = ipadb_get_global_config(ipactx);
 |  | ||||||
| +        if (gcfg && gcfg->disable_preauth_for_spns)
 |  | ||||||
| +            vsflg &= ~KRB5_KDB_REQUIRES_PRE_AUTH;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    if (tktflags)
 |  | ||||||
| +        *tktflags |= vsflg;
 |  | ||||||
| +
 |  | ||||||
| +end:
 |  | ||||||
| +    return kerr;
 |  | ||||||
| +}
 |  | ||||||
| +
 |  | ||||||
| +static krb5_error_code
 |  | ||||||
| +get_virtual_static_tktflags_mask(struct ipadb_context *ipactx,
 |  | ||||||
| +                                 krb5_db_entry *entry, krb5_flags *mask)
 |  | ||||||
| +{
 |  | ||||||
| +    krb5_error_code kerr;
 |  | ||||||
| +    krb5_flags flags = IPA_KDB_TKTFLAGS_VIRTUAL_MANAGED_ALL;
 |  | ||||||
| +
 |  | ||||||
| +    kerr = add_virtual_static_tktflags(ipactx, entry, &flags);
 |  | ||||||
| +    if (kerr)
 |  | ||||||
| +        goto end;
 |  | ||||||
| +
 |  | ||||||
| +    if (mask)
 |  | ||||||
| +        *mask = ~flags;
 |  | ||||||
| +
 |  | ||||||
| +    kerr = 0;
 |  | ||||||
| +
 |  | ||||||
| +end:
 |  | ||||||
| +    return kerr;
 |  | ||||||
| +}
 |  | ||||||
| +
 |  | ||||||
| +/* Add ticket flags from the global ticket policy if it exists, otherwise
 |  | ||||||
| + * succeed. If the global ticket policy is set, the "exists" parameter is set to
 |  | ||||||
| + * true. */
 |  | ||||||
| +static krb5_error_code
 |  | ||||||
| +add_global_ticket_policy_flags(struct ipadb_context *ipactx,
 |  | ||||||
| +                               bool *gtpol_exists, krb5_flags *tktflags)
 |  | ||||||
| +{
 |  | ||||||
| +    krb5_error_code kerr;
 |  | ||||||
| +    char *policy_dn;
 |  | ||||||
| +    char *tktflags_attr[] = { "krbticketflags", NULL };
 |  | ||||||
| +    LDAPMessage *res = NULL, *first;
 |  | ||||||
| +    int ec, ldap_tktflags;
 |  | ||||||
| +    bool in_gtpol_exists = false;
 |  | ||||||
| +
 |  | ||||||
| +    ec = asprintf(&policy_dn, "cn=%s,cn=kerberos,%s", ipactx->realm,
 |  | ||||||
| +                  ipactx->base);
 |  | ||||||
| +    if (-1 == ec) {
 |  | ||||||
| +        kerr = ENOMEM;
 |  | ||||||
| +        goto end;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    kerr = ipadb_simple_search(ipactx, policy_dn, LDAP_SCOPE_BASE,
 |  | ||||||
| +                               "(objectclass=krbticketpolicyaux)",
 |  | ||||||
| +                               tktflags_attr, &res);
 |  | ||||||
| +    if (kerr) {
 |  | ||||||
| +        if (KRB5_KDB_NOENTRY == kerr)
 |  | ||||||
| +            kerr = 0;
 |  | ||||||
| +        goto end;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    first = ldap_first_entry(ipactx->lcontext, res);
 |  | ||||||
| +    if (!first) {
 |  | ||||||
| +        kerr = 0;
 |  | ||||||
| +        goto end;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    in_gtpol_exists = true;
 |  | ||||||
| +
 |  | ||||||
| +    ec = ipadb_ldap_attr_to_int(ipactx->lcontext, first, "krbticketflags",
 |  | ||||||
| +                                &ldap_tktflags);
 |  | ||||||
| +    if (0 == ec && tktflags) {
 |  | ||||||
| +        *tktflags |= (krb5_flags)ldap_tktflags;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    kerr = 0;
 |  | ||||||
| +
 |  | ||||||
| +end:
 |  | ||||||
| +    if (gtpol_exists)
 |  | ||||||
| +        *gtpol_exists = in_gtpol_exists;
 |  | ||||||
| +
 |  | ||||||
| +    ldap_msgfree(res);
 |  | ||||||
| +    free(policy_dn);
 |  | ||||||
| +    return kerr;
 |  | ||||||
|  } |  | ||||||
|   |  | ||||||
|  static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext, |  | ||||||
| @@ -1280,6 +1415,7 @@ static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext,
 |  | ||||||
|      char *policy_dn = NULL; |  | ||||||
|      LDAPMessage *res = NULL; |  | ||||||
|      LDAPMessage *first; |  | ||||||
| +    bool final_tktflags, has_local_tktpolicy = true;
 |  | ||||||
|      int result; |  | ||||||
|      int ret; |  | ||||||
|   |  | ||||||
| @@ -1288,12 +1424,18 @@ static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext,
 |  | ||||||
|          return KRB5_KDB_DBNOTINITED; |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| +    kerr = are_final_tktflags(ipactx, entry, &final_tktflags);
 |  | ||||||
| +    if (kerr)
 |  | ||||||
| +        goto done;
 |  | ||||||
| +
 |  | ||||||
|      ret = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry, |  | ||||||
|                                   "krbticketpolicyreference", &policy_dn); |  | ||||||
|      switch (ret) { |  | ||||||
|      case 0: |  | ||||||
|          break; |  | ||||||
|      case ENOENT: |  | ||||||
| +        /* If no principal ticket policy, fallback to the global one. */
 |  | ||||||
| +        has_local_tktpolicy = false;
 |  | ||||||
|          ret = asprintf(&policy_dn, "cn=%s,cn=kerberos,%s", |  | ||||||
|                                     ipactx->realm, ipactx->base); |  | ||||||
|          if (ret == -1) { |  | ||||||
| @@ -1337,12 +1479,13 @@ static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext,
 |  | ||||||
|                  } |  | ||||||
|              } |  | ||||||
|              if (polmask & TKTFLAGS_BIT) { |  | ||||||
| -                ret = ipadb_ldap_attr_to_int(ipactx->lcontext, first,
 |  | ||||||
| -                                             "krbticketflags", &result);
 |  | ||||||
| -                if (ret == 0) {
 |  | ||||||
| -                    entry->attributes |= result;
 |  | ||||||
| -                } else {
 |  | ||||||
| -                    entry->attributes |= maybe_require_preauth(ipactx, entry);
 |  | ||||||
| +                /* If global ticket policy is being applied, set flags only if
 |  | ||||||
| +                 * user principal ticket flags are not final. */
 |  | ||||||
| +                if (has_local_tktpolicy || !final_tktflags) {
 |  | ||||||
| +                    ret = ipadb_ldap_attr_to_int(ipactx->lcontext, first,
 |  | ||||||
| +                                                 "krbticketflags", &result);
 |  | ||||||
| +                    if (ret == 0)
 |  | ||||||
| +                        entry->attributes |= result;
 |  | ||||||
|                  } |  | ||||||
|              } |  | ||||||
|   |  | ||||||
| @@ -1366,13 +1509,27 @@ static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext,
 |  | ||||||
|          if (polmask & MAXRENEWABLEAGE_BIT) { |  | ||||||
|              entry->max_renewable_life = 604800; |  | ||||||
|          } |  | ||||||
| -        if (polmask & TKTFLAGS_BIT) {
 |  | ||||||
| -            entry->attributes |= maybe_require_preauth(ipactx, entry);
 |  | ||||||
| -        }
 |  | ||||||
|   |  | ||||||
|          kerr = 0; |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| +    if (polmask & TKTFLAGS_BIT) {
 |  | ||||||
| +        /* If the principal ticket flags were applied, then flags from the
 |  | ||||||
| +         * global ticket policy has to be applied atop of them if user principal
 |  | ||||||
| +         * ticket flags are not final. */
 |  | ||||||
| +        if (has_local_tktpolicy && !final_tktflags) {
 |  | ||||||
| +            kerr = add_global_ticket_policy_flags(ipactx, NULL,
 |  | ||||||
| +                                                  &entry->attributes);
 |  | ||||||
| +            if (kerr)
 |  | ||||||
| +            goto done;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        /* Virtual static ticket flags are set regardless of database content */
 |  | ||||||
| +        kerr = add_virtual_static_tktflags(ipactx, entry, &entry->attributes);
 |  | ||||||
| +        if (kerr)
 |  | ||||||
| +            goto done;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
|  done: |  | ||||||
|      ldap_msgfree(res); |  | ||||||
|      free(policy_dn); |  | ||||||
| @@ -1864,6 +2021,36 @@ static void ipadb_mods_free_tip(struct ipadb_mods *imods)
 |  | ||||||
|      imods->tip--; |  | ||||||
|  } |  | ||||||
|   |  | ||||||
| +/* Use LDAP REPLACE operation to remove an attribute.
 |  | ||||||
| + * Contrary to the DELETE operation, it will not fail if the attribute does not
 |  | ||||||
| + * exist. */
 |  | ||||||
| +static krb5_error_code
 |  | ||||||
| +ipadb_ldap_replace_remove(struct ipadb_mods *imods, char *attribute)
 |  | ||||||
| +{
 |  | ||||||
| +    krb5_error_code kerr;
 |  | ||||||
| +    LDAPMod *m = NULL;
 |  | ||||||
| +
 |  | ||||||
| +    kerr = ipadb_mods_new(imods, &m);
 |  | ||||||
| +    if (kerr)
 |  | ||||||
| +        return kerr;
 |  | ||||||
| +
 |  | ||||||
| +    m->mod_op = LDAP_MOD_REPLACE;
 |  | ||||||
| +    m->mod_type = strdup(attribute);
 |  | ||||||
| +    if (!m->mod_type) {
 |  | ||||||
| +        kerr = ENOMEM;
 |  | ||||||
| +        goto end;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    m->mod_values = NULL;
 |  | ||||||
| +
 |  | ||||||
| +    kerr = 0;
 |  | ||||||
| +
 |  | ||||||
| +end:
 |  | ||||||
| +    if (kerr)
 |  | ||||||
| +        ipadb_mods_free_tip(imods);
 |  | ||||||
| +    return kerr;
 |  | ||||||
| +}
 |  | ||||||
| +
 |  | ||||||
|  static krb5_error_code ipadb_get_ldap_mod_str(struct ipadb_mods *imods, |  | ||||||
|                                                char *attribute, char *value, |  | ||||||
|                                                int mod_op) |  | ||||||
| @@ -2275,6 +2462,93 @@ static krb5_error_code ipadb_get_ldap_mod_auth_ind(krb5_context kcontext,
 |  | ||||||
|      return ret; |  | ||||||
|  } |  | ||||||
|   |  | ||||||
| +static krb5_error_code
 |  | ||||||
| +update_tktflags(krb5_context kcontext, struct ipadb_mods *imods,
 |  | ||||||
| +                krb5_db_entry *entry, int mod_op)
 |  | ||||||
| +{
 |  | ||||||
| +    krb5_error_code kerr;
 |  | ||||||
| +    struct ipadb_context *ipactx;
 |  | ||||||
| +    struct ipadb_e_data *ied;
 |  | ||||||
| +    bool final_tktflags;
 |  | ||||||
| +    krb5_flags tktflags_mask;
 |  | ||||||
| +    int tktflags;
 |  | ||||||
| +
 |  | ||||||
| +    ipactx = ipadb_get_context(kcontext);
 |  | ||||||
| +    if (!ipactx) {
 |  | ||||||
| +        kerr = KRB5_KDB_DBNOTINITED;
 |  | ||||||
| +        goto end;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    if (ipactx->override_restrictions) {
 |  | ||||||
| +        /* In IPA setup mode, IPA edata might not be available. In this mode,
 |  | ||||||
| +         * ticket flags are written as they are provided. */
 |  | ||||||
| +        tktflags = (int)entry->attributes;
 |  | ||||||
| +    } else {
 |  | ||||||
| +        kerr = ipadb_get_edata(entry, &ied);
 |  | ||||||
| +        if (kerr)
 |  | ||||||
| +            goto end;
 |  | ||||||
| +
 |  | ||||||
| +        kerr = get_virtual_static_tktflags_mask(ipactx, entry, &tktflags_mask);
 |  | ||||||
| +        if (kerr)
 |  | ||||||
| +            goto end;
 |  | ||||||
| +
 |  | ||||||
| +        kerr = are_final_tktflags(ipactx, entry, &final_tktflags);
 |  | ||||||
| +        if (kerr)
 |  | ||||||
| +            goto end;
 |  | ||||||
| +
 |  | ||||||
| +        /* Flags from the global ticket policy are filtered out only if the user
 |  | ||||||
| +         * principal flags are not final. */
 |  | ||||||
| +        if (!final_tktflags) {
 |  | ||||||
| +            krb5_flags gbl_tktflags = 0;
 |  | ||||||
| +
 |  | ||||||
| +            kerr = add_global_ticket_policy_flags(ipactx, NULL, &gbl_tktflags);
 |  | ||||||
| +            if (kerr)
 |  | ||||||
| +                goto end;
 |  | ||||||
| +
 |  | ||||||
| +            tktflags_mask &= ~gbl_tktflags;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        tktflags = (int)(entry->attributes & tktflags_mask);
 |  | ||||||
| +
 |  | ||||||
| +        if (LDAP_MOD_REPLACE == mod_op && ied && !ied->has_tktpolaux) {
 |  | ||||||
| +            if (0 == tktflags) {
 |  | ||||||
| +                /* No point initializing principal ticket policy if there are no
 |  | ||||||
| +                 * flags left after filtering out virtual and global ticket
 |  | ||||||
| +                 * policy ones. */
 |  | ||||||
| +                kerr = 0;
 |  | ||||||
| +                goto end;
 |  | ||||||
| +            }
 |  | ||||||
| +
 |  | ||||||
| +            /* if the object does not have the krbTicketPolicyAux class
 |  | ||||||
| +             * we need to add it or this will fail, only for modifications.
 |  | ||||||
| +             * We always add this objectclass by default when doing an add
 |  | ||||||
| +             * from scratch. */
 |  | ||||||
| +            kerr = ipadb_get_ldap_mod_str(imods, "objectclass",
 |  | ||||||
| +                                          "krbTicketPolicyAux", LDAP_MOD_ADD);
 |  | ||||||
| +            if (kerr)
 |  | ||||||
| +                goto end;
 |  | ||||||
| +        }
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    if (tktflags != 0) {
 |  | ||||||
| +        kerr = ipadb_get_ldap_mod_int(imods, "krbTicketFlags", tktflags,
 |  | ||||||
| +                                      mod_op);
 |  | ||||||
| +        if (kerr)
 |  | ||||||
| +            goto end;
 |  | ||||||
| +    } else if (LDAP_MOD_REPLACE == mod_op) {
 |  | ||||||
| +        /* If the principal is not being created, and there are no custom ticket
 |  | ||||||
| +         * flags to be set, remove the "krbTicketFlags" attribute. */
 |  | ||||||
| +        kerr = ipadb_ldap_replace_remove(imods, "krbTicketFlags");
 |  | ||||||
| +        if (kerr)
 |  | ||||||
| +            goto end;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    kerr = 0;
 |  | ||||||
| +
 |  | ||||||
| +end:
 |  | ||||||
| +    return kerr;
 |  | ||||||
| +}
 |  | ||||||
| +
 |  | ||||||
|  static krb5_error_code ipadb_entry_to_mods(krb5_context kcontext, |  | ||||||
|                                             struct ipadb_mods *imods, |  | ||||||
|                                             krb5_db_entry *entry, |  | ||||||
| @@ -2350,36 +2624,9 @@ static krb5_error_code ipadb_entry_to_mods(krb5_context kcontext,
 |  | ||||||
|   |  | ||||||
|      /* KADM5_ATTRIBUTES */ |  | ||||||
|      if (entry->mask & KMASK_ATTRIBUTES) { |  | ||||||
| -        /* if the object does not have the krbTicketPolicyAux class
 |  | ||||||
| -         * we need to add it or this will fail, only for modifications.
 |  | ||||||
| -         * We always add this objectclass by default when doing an add
 |  | ||||||
| -         * from scratch. */
 |  | ||||||
| -        if ((mod_op == LDAP_MOD_REPLACE) && entry->e_data) {
 |  | ||||||
| -            struct ipadb_e_data *ied;
 |  | ||||||
| -
 |  | ||||||
| -            ied = (struct ipadb_e_data *)entry->e_data;
 |  | ||||||
| -            if (ied->magic != IPA_E_DATA_MAGIC) {
 |  | ||||||
| -                kerr = EINVAL;
 |  | ||||||
| -                goto done;
 |  | ||||||
| -            }
 |  | ||||||
| -
 |  | ||||||
| -            if (!ied->has_tktpolaux) {
 |  | ||||||
| -                kerr = ipadb_get_ldap_mod_str(imods, "objectclass",
 |  | ||||||
| -                                              "krbTicketPolicyAux",
 |  | ||||||
| -                                              LDAP_MOD_ADD);
 |  | ||||||
| -                if (kerr) {
 |  | ||||||
| -                    goto done;
 |  | ||||||
| -                }
 |  | ||||||
| -            }
 |  | ||||||
| -        }
 |  | ||||||
| -
 |  | ||||||
| -        kerr = ipadb_get_ldap_mod_int(imods,
 |  | ||||||
| -                                      "krbTicketFlags",
 |  | ||||||
| -                                      (int)entry->attributes,
 |  | ||||||
| -                                      mod_op);
 |  | ||||||
| -        if (kerr) {
 |  | ||||||
| +        kerr = update_tktflags(kcontext, imods, entry, mod_op);
 |  | ||||||
| +        if (kerr)
 |  | ||||||
|              goto done; |  | ||||||
| -        }
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
|      /* KADM5_MAX_LIFE */ |  | ||||||
| diff --git a/util/ipa_krb5.c b/util/ipa_krb5.c
 |  | ||||||
| index 1ba6d25ee..2e663c506 100644
 |  | ||||||
| --- a/util/ipa_krb5.c
 |  | ||||||
| +++ b/util/ipa_krb5.c
 |  | ||||||
| @@ -38,6 +38,12 @@ const char *ipapwd_password_max_len_errmsg = \
 |  | ||||||
|      TOSTR(IPAPWD_PASSWORD_MAX_LEN) \ |  | ||||||
|      " chars)!"; |  | ||||||
|   |  | ||||||
| +/* Case-insensitive string values to by parsed as boolean true */
 |  | ||||||
| +static const char *const conf_yes[] = {
 |  | ||||||
| +    "y", "yes", "true", "t", "1", "on",
 |  | ||||||
| +    NULL,
 |  | ||||||
| +};
 |  | ||||||
| +
 |  | ||||||
|  /* Salt types */ |  | ||||||
|  #define KRB5P_SALT_SIZE 16 |  | ||||||
|   |  | ||||||
| @@ -1237,3 +1243,15 @@ done:
 |  | ||||||
|      } |  | ||||||
|      return ret; |  | ||||||
|  } |  | ||||||
| +
 |  | ||||||
| +bool ipa_krb5_parse_bool(const char *str)
 |  | ||||||
| +{
 |  | ||||||
| +    const char *const *p;
 |  | ||||||
| +
 |  | ||||||
| +    for (p = conf_yes; *p; p++) {
 |  | ||||||
| +        if (!strcasecmp(*p, str))
 |  | ||||||
| +            return true;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    return false;
 |  | ||||||
| +}
 |  | ||||||
| diff --git a/util/ipa_krb5.h b/util/ipa_krb5.h
 |  | ||||||
| index 7d2ebae98..d0280940a 100644
 |  | ||||||
| --- a/util/ipa_krb5.h
 |  | ||||||
| +++ b/util/ipa_krb5.h
 |  | ||||||
| @@ -174,3 +174,7 @@ static inline bool
 |  | ||||||
|  krb5_ts_after(krb5_timestamp a, krb5_timestamp b) { |  | ||||||
|      return (uint32_t)a > (uint32_t)b; |  | ||||||
|  } |  | ||||||
| +
 |  | ||||||
| +/* Implement boolean string parsing function from MIT krb5:
 |  | ||||||
| + * src/lib/krb5/krb/libdef_parse.c:_krb5_conf_boolean() */
 |  | ||||||
| +bool ipa_krb5_parse_bool(const char *str);
 |  | ||||||
| -- 
 |  | ||||||
| 2.45.1 |  | ||||||
| 
 |  | ||||||
| @ -1,127 +0,0 @@ | |||||||
| diff --git a/ipaserver/plugins/user.py b/ipaserver/plugins/user.py
 |  | ||||||
| index 6f5e349..febc22f 100644
 |  | ||||||
| --- a/ipaserver/plugins/user.py
 |  | ||||||
| +++ b/ipaserver/plugins/user.py
 |  | ||||||
| @@ -144,8 +144,7 @@ PROTECTED_USERS = ('admin',)
 |  | ||||||
|  def check_protected_member(user, protected_group_name=u'admins'): |  | ||||||
|      ''' |  | ||||||
|      Ensure admin and the last enabled member of a protected group cannot |  | ||||||
| -    be deleted or disabled by raising ProtectedEntryError or
 |  | ||||||
| -    LastMemberError as appropriate.
 |  | ||||||
| +    be deleted.
 |  | ||||||
|      ''' |  | ||||||
|   |  | ||||||
|      if user in PROTECTED_USERS: |  | ||||||
| @@ -155,6 +154,12 @@ def check_protected_member(user, protected_group_name=u'admins'):
 |  | ||||||
|              reason=_("privileged user"), |  | ||||||
|          ) |  | ||||||
|   |  | ||||||
| +
 |  | ||||||
| +def check_last_member(user, protected_group_name=u'admins'):
 |  | ||||||
| +    '''
 |  | ||||||
| +    Ensure the last enabled member of a protected group cannot
 |  | ||||||
| +    be disabled.
 |  | ||||||
| +    '''
 |  | ||||||
|      # Get all users in the protected group |  | ||||||
|      result = api.Command.user_find(in_group=protected_group_name) |  | ||||||
|   |  | ||||||
| @@ -796,6 +801,7 @@ class user_del(baseuser_del):
 |  | ||||||
|          # If the target entry is a Delete entry, skip the orphaning/removal |  | ||||||
|          # of OTP tokens. |  | ||||||
|          check_protected_member(keys[-1]) |  | ||||||
| +        check_last_member(keys[-1])
 |  | ||||||
|   |  | ||||||
|          preserve = options.get('preserve', False) |  | ||||||
|   |  | ||||||
| @@ -1128,7 +1134,7 @@ class user_disable(LDAPQuery):
 |  | ||||||
|      def execute(self, *keys, **options): |  | ||||||
|          ldap = self.obj.backend |  | ||||||
|   |  | ||||||
| -        check_protected_member(keys[-1])
 |  | ||||||
| +        check_last_member(keys[-1])
 |  | ||||||
|   |  | ||||||
|          dn, _oc = self.obj.get_either_dn(*keys, **options) |  | ||||||
|          ldap.deactivate_entry(dn) |  | ||||||
| diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
 |  | ||||||
| index c0cb4d0..c2a55b8 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_commands.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_commands.py
 |  | ||||||
| @@ -1530,6 +1530,30 @@ class TestIPACommand(IntegrationTest):
 |  | ||||||
|   |  | ||||||
|          assert 'Discovered server %s' % self.master.hostname in result |  | ||||||
|   |  | ||||||
| +    def test_delete_last_enabled_admin(self):
 |  | ||||||
| +        """
 |  | ||||||
| +        The admin user may be disabled. Don't allow all other
 |  | ||||||
| +        members of admins to be removed if the admin user is
 |  | ||||||
| +        disabled which would leave the install with no
 |  | ||||||
| +        usable admins users
 |  | ||||||
| +        """
 |  | ||||||
| +        user = 'adminuser2'
 |  | ||||||
| +        passwd = 'Secret123'
 |  | ||||||
| +        tasks.create_active_user(self.master, user, passwd)
 |  | ||||||
| +        tasks.kinit_admin(self.master)
 |  | ||||||
| +        self.master.run_command(['ipa', 'group-add-member', 'admins',
 |  | ||||||
| +                                '--users', user])
 |  | ||||||
| +        tasks.kinit_user(self.master, user, passwd)
 |  | ||||||
| +        self.master.run_command(['ipa', 'user-disable', 'admin'])
 |  | ||||||
| +        result = self.master.run_command(
 |  | ||||||
| +            ['ipa', 'user-del', user],
 |  | ||||||
| +            raiseonerr=False
 |  | ||||||
| +        )
 |  | ||||||
| +        self.master.run_command(['ipa', 'user-enable', 'admin'])
 |  | ||||||
| +        tasks.kdestroy_all(self.master)
 |  | ||||||
| +        assert result.returncode == 1
 |  | ||||||
| +        assert 'cannot be deleted or disabled' in result.stderr_text
 |  | ||||||
| +
 |  | ||||||
|   |  | ||||||
|  class TestIPACommandWithoutReplica(IntegrationTest): |  | ||||||
|      """ |  | ||||||
| diff --git a/ipatests/test_xmlrpc/test_user_plugin.py b/ipatests/test_xmlrpc/test_user_plugin.py
 |  | ||||||
| index 3c58845..68c6c48 100644
 |  | ||||||
| --- a/ipatests/test_xmlrpc/test_user_plugin.py
 |  | ||||||
| +++ b/ipatests/test_xmlrpc/test_user_plugin.py
 |  | ||||||
| @@ -1045,8 +1045,8 @@ class TestAdmins(XMLRPC_test):
 |  | ||||||
|          tracker = Tracker() |  | ||||||
|          command = tracker.make_command('user_disable', admin1) |  | ||||||
|   |  | ||||||
| -        with raises_exact(errors.ProtectedEntryError(label=u'user',
 |  | ||||||
| -                          key=admin1, reason='privileged user')):
 |  | ||||||
| +        with raises_exact(errors.LastMemberError(label=u'group',
 |  | ||||||
| +                          key=admin1, container=admin_group)):
 |  | ||||||
|              command() |  | ||||||
|   |  | ||||||
|      def test_create_admin2(self, admin2): |  | ||||||
| @@ -1064,8 +1064,8 @@ class TestAdmins(XMLRPC_test):
 |  | ||||||
|          admin2.disable() |  | ||||||
|          tracker = Tracker() |  | ||||||
|   |  | ||||||
| -        with raises_exact(errors.ProtectedEntryError(label=u'user',
 |  | ||||||
| -                          key=admin1, reason='privileged user')):
 |  | ||||||
| +        with raises_exact(errors.LastMemberError(label=u'group',
 |  | ||||||
| +                          key=admin1, container=admin_group)):
 |  | ||||||
|              tracker.run_command('user_disable', admin1) |  | ||||||
|          admin2.delete() |  | ||||||
|   |  | ||||||
| diff --git a/ipatests/test_webui/test_user.py b/ipatests/test_webui/test_user.py
 |  | ||||||
| index a8a92d0..9083e50 100644
 |  | ||||||
| --- a/ipatests/test_webui/test_user.py
 |  | ||||||
| +++ b/ipatests/test_webui/test_user.py
 |  | ||||||
| @@ -50,6 +50,8 @@ INV_FIRSTNAME = ("invalid 'first': Leading and trailing spaces are "
 |  | ||||||
|  FIELD_REQ = 'Required field' |  | ||||||
|  ERR_INCLUDE = 'may only include letters, numbers, _, -, . and $' |  | ||||||
|  ERR_MISMATCH = 'Passwords must match' |  | ||||||
| +ERR_ADMIN_DISABLE = ('admin cannot be deleted or disabled because '
 |  | ||||||
| +                     'it is the last member of group admins')
 |  | ||||||
|  ERR_ADMIN_DEL = ('user admin cannot be deleted/modified: privileged user') |  | ||||||
|  USR_EXIST = 'user with name "{}" already exists' |  | ||||||
|  ENTRY_EXIST = 'This entry already exists' |  | ||||||
| @@ -546,7 +548,7 @@ class test_user(user_tasks):
 |  | ||||||
|          self.select_record('admin') |  | ||||||
|          self.facet_button_click('disable') |  | ||||||
|          self.dialog_button_click('ok') |  | ||||||
| -        self.assert_last_error_dialog(ERR_ADMIN_DEL, details=True)
 |  | ||||||
| +        self.assert_last_error_dialog(ERR_ADMIN_DISABLE, details=True)
 |  | ||||||
|          self.dialog_button_click('ok') |  | ||||||
|          self.assert_record('admin') |  | ||||||
|   |  | ||||||
| @ -1,13 +0,0 @@ | |||||||
| diff --git a/ipaserver/install/ipa_otptoken_import.py b/ipaserver/install/ipa_otptoken_import.py
 |  | ||||||
| index b3f9347..75e8680 100644
 |  | ||||||
| --- a/ipaserver/install/ipa_otptoken_import.py
 |  | ||||||
| +++ b/ipaserver/install/ipa_otptoken_import.py
 |  | ||||||
| @@ -539,7 +539,7 @@ class OTPTokenImport(admintool.AdminTool):
 |  | ||||||
|   |  | ||||||
|              # Load the keyfile. |  | ||||||
|              keyfile = self.safe_options.keyfile |  | ||||||
| -            with open(keyfile) as f:
 |  | ||||||
| +            with open(keyfile, "rb") as f:
 |  | ||||||
|                  self.doc.setKey(f.read()) |  | ||||||
|   |  | ||||||
|      def run(self): |  | ||||||
| @ -1,114 +0,0 @@ | |||||||
| diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
 |  | ||||||
| index 38693c9..35cec89 100644
 |  | ||||||
| --- a/ipaserver/install/cainstance.py
 |  | ||||||
| +++ b/ipaserver/install/cainstance.py
 |  | ||||||
| @@ -1327,6 +1327,8 @@ class CAInstance(DogtagInstance):
 |  | ||||||
|          generation master: |  | ||||||
|          - in CS.cfg ca.crl.MasterCRL.enableCRLCache=true |  | ||||||
|          - in CS.cfg ca.crl.MasterCRL.enableCRLUpdates=true |  | ||||||
| +        - in CS.cfg ca.listenToCloneModifications=true
 |  | ||||||
| +        - in CS.cfg ca.certStatusUpdateInterval != 0
 |  | ||||||
|          - in /etc/httpd/conf.d/ipa-pki-proxy.conf the RewriteRule |  | ||||||
|          ^/ipa/crl/MasterCRL.bin is disabled (commented or removed) |  | ||||||
|   |  | ||||||
| @@ -1342,15 +1344,30 @@ class CAInstance(DogtagInstance):
 |  | ||||||
|              updates = directivesetter.get_directive( |  | ||||||
|                  self.config, 'ca.crl.MasterCRL.enableCRLUpdates', '=') |  | ||||||
|              enableCRLUpdates = updates.lower() == 'true' |  | ||||||
| +            listen = directivesetter.get_directive(
 |  | ||||||
| +                self.config, 'ca.listenToCloneModifications', '=')
 |  | ||||||
| +            enableToClone = listen.lower() == 'true'
 |  | ||||||
| +            updateinterval = directivesetter.get_directive(
 |  | ||||||
| +                self.config, 'ca.certStatusUpdateInterval', '=')
 |  | ||||||
|   |  | ||||||
|              # If the values are different, the config is inconsistent |  | ||||||
| -            if enableCRLCache != enableCRLUpdates:
 |  | ||||||
| +            if not (enableCRLCache == enableCRLUpdates == enableToClone):
 |  | ||||||
|                  raise InconsistentCRLGenConfigException( |  | ||||||
|                      "Configuration is inconsistent, please check " |  | ||||||
| -                    "ca.crl.MasterCRL.enableCRLCache and "
 |  | ||||||
| -                    "ca.crl.MasterCRL.enableCRLUpdates in {} and "
 |  | ||||||
| +                    "ca.crl.MasterCRL.enableCRLCache, "
 |  | ||||||
| +                    "ca.crl.MasterCRL.enableCRLUpdates and "
 |  | ||||||
| +                    "ca.listenToCloneModifications in {} and "
 |  | ||||||
|                      "run ipa-crlgen-manage [enable|disable] to repair".format( |  | ||||||
|                          self.config)) |  | ||||||
| +            # If they are the same then we are the CRL renewal master. Ensure
 |  | ||||||
| +            # the update task is configured.
 |  | ||||||
| +            if enableCRLCache and updateinterval == '0':
 |  | ||||||
| +                raise InconsistentCRLGenConfigException(
 |  | ||||||
| +                    "Configuration is inconsistent, please check "
 |  | ||||||
| +                    "ca.certStatusUpdateInterval in {}. It should "
 |  | ||||||
| +                    "be either not present or not zero. Run "
 |  | ||||||
| +                    "ipa-crlgen-manage [enable|disable] to repair".format(
 |  | ||||||
| +                        self.config))
 |  | ||||||
|          except IOError: |  | ||||||
|              raise RuntimeError( |  | ||||||
|                  "Unable to read {}".format(self.config)) |  | ||||||
| @@ -1407,6 +1424,11 @@ class CAInstance(DogtagInstance):
 |  | ||||||
|              str_value = str(setup_crlgen).lower() |  | ||||||
|              ds.set('ca.crl.MasterCRL.enableCRLCache', str_value) |  | ||||||
|              ds.set('ca.crl.MasterCRL.enableCRLUpdates', str_value) |  | ||||||
| +            ds.set('ca.listenToCloneModifications', str_value)
 |  | ||||||
| +            if setup_crlgen:
 |  | ||||||
| +                ds.set('ca.certStatusUpdateInterval', None)
 |  | ||||||
| +            else:
 |  | ||||||
| +                ds.set('ca.certStatusUpdateInterval', '0')
 |  | ||||||
|   |  | ||||||
|          # Start pki-tomcat |  | ||||||
|          logger.info("Starting %s", self.service_name) |  | ||||||
| diff --git a/ipatests/test_integration/test_crlgen_manage.py b/ipatests/test_integration/test_crlgen_manage.py
 |  | ||||||
| index 2a733bd..c6f41eb 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_crlgen_manage.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_crlgen_manage.py
 |  | ||||||
| @@ -61,6 +61,16 @@ def check_crlgen_status(host, rc=0, msg=None, enabled=True, check_crl=False):
 |  | ||||||
|                          ext.value.crl_number) |  | ||||||
|                      assert number_msg in result.stdout_text |  | ||||||
|   |  | ||||||
| +        try:
 |  | ||||||
| +            value = get_CS_cfg_value(host, 'ca.certStatusUpdateInterval')
 |  | ||||||
| +        except IOError:
 |  | ||||||
| +            return
 |  | ||||||
| +
 |  | ||||||
| +        if enabled:
 |  | ||||||
| +            assert value is None
 |  | ||||||
| +        else:
 |  | ||||||
| +            assert value == '0'
 |  | ||||||
| +
 |  | ||||||
|   |  | ||||||
|  def check_crlgen_enable(host, rc=0, msg=None, check_crl=False): |  | ||||||
|      """Check ipa-crlgen-manage enable command |  | ||||||
| @@ -125,6 +135,23 @@ def break_crlgen_with_CS_cfg(host):
 |  | ||||||
|      check_crlgen_status(host, rc=1, msg="Configuration is inconsistent") |  | ||||||
|   |  | ||||||
|   |  | ||||||
| +def get_CS_cfg_value(host, directive):
 |  | ||||||
| +    """Retrieve and return the a directive from the CA CS.cfg
 |  | ||||||
| +
 |  | ||||||
| +       This returns None if the directives is not found.
 |  | ||||||
| +    """
 |  | ||||||
| +    content = host.get_file_contents(paths.CA_CS_CFG_PATH,
 |  | ||||||
| +                                     encoding='utf-8')
 |  | ||||||
| +    value = None
 |  | ||||||
| +    for line in content.split('\n'):
 |  | ||||||
| +        l = line.lower()
 |  | ||||||
| +
 |  | ||||||
| +        if l.startswith(directive.lower()):
 |  | ||||||
| +            value = line.split('=', 1)[1]
 |  | ||||||
| +
 |  | ||||||
| +    return value
 |  | ||||||
| +
 |  | ||||||
| +
 |  | ||||||
|  class TestCRLGenManage(IntegrationTest): |  | ||||||
|      """Tests the ipa-crlgen-manage command. |  | ||||||
|   |  | ||||||
| @@ -196,6 +223,9 @@ class TestCRLGenManage(IntegrationTest):
 |  | ||||||
|   |  | ||||||
|          Install a CA clone and enable CRLgen""" |  | ||||||
|          tasks.install_ca(self.replicas[0]) |  | ||||||
| +        value = get_CS_cfg_value(self.replicas[0],
 |  | ||||||
| +                                 'ca.certStatusUpdateInterval')
 |  | ||||||
| +        assert value == '0'
 |  | ||||||
|          check_crlgen_enable( |  | ||||||
|              self.replicas[0], rc=0, |  | ||||||
|              msg="make sure to have only a single CRL generation master", |  | ||||||
| @ -1,337 +0,0 @@ | |||||||
| diff --git a/ipaserver/plugins/idrange.py b/ipaserver/plugins/idrange.py
 |  | ||||||
| index d5b184f..b38ea73 100644
 |  | ||||||
| --- a/ipaserver/plugins/idrange.py
 |  | ||||||
| +++ b/ipaserver/plugins/idrange.py
 |  | ||||||
| @@ -549,6 +549,12 @@ class idrange_add(LDAPCreate):
 |  | ||||||
|          self.obj.handle_ipabaserid(entry_attrs, options) |  | ||||||
|          self.obj.handle_iparangetype(entry_attrs, options, |  | ||||||
|                                       keep_objectclass=True) |  | ||||||
| +        self.add_message(
 |  | ||||||
| +            messages.ServiceRestartRequired(
 |  | ||||||
| +                service=services.knownservices.dirsrv.service_instance(""),
 |  | ||||||
| +                server=_('<all IPA servers>')
 |  | ||||||
| +            )
 |  | ||||||
| +        )
 |  | ||||||
|          return dn |  | ||||||
|   |  | ||||||
|   |  | ||||||
| diff --git a/ipatests/test_xmlrpc/test_range_plugin.py b/ipatests/test_xmlrpc/test_range_plugin.py
 |  | ||||||
| index f912e04..e3f4c23 100644
 |  | ||||||
| --- a/ipatests/test_xmlrpc/test_range_plugin.py
 |  | ||||||
| +++ b/ipatests/test_xmlrpc/test_range_plugin.py
 |  | ||||||
| @@ -372,6 +372,8 @@ IPA_LOCAL_RANGE_MOD_ERR = (
 |  | ||||||
|      "domain. Run `ipa help idrange` for more information" |  | ||||||
|  ) |  | ||||||
|   |  | ||||||
| +dirsrv_instance = services.knownservices.dirsrv.service_instance("")
 |  | ||||||
| +
 |  | ||||||
|   |  | ||||||
|  @pytest.mark.tier1 |  | ||||||
|  class test_range(Declarative): |  | ||||||
| @@ -464,6 +466,11 @@ class test_range(Declarative):
 |  | ||||||
|                  ), |  | ||||||
|                  value=testrange1, |  | ||||||
|                  summary=u'Added ID range "%s"' % (testrange1), |  | ||||||
| +                messages=(
 |  | ||||||
| +                    messages.ServiceRestartRequired(
 |  | ||||||
| +                        service=dirsrv_instance,
 |  | ||||||
| +                        server='<all IPA servers>').to_dict(),
 |  | ||||||
| +                ),
 |  | ||||||
|              ), |  | ||||||
|          ), |  | ||||||
|   |  | ||||||
| @@ -633,6 +640,11 @@ class test_range(Declarative):
 |  | ||||||
|                  ), |  | ||||||
|                  value=testrange2, |  | ||||||
|                  summary=u'Added ID range "%s"' % (testrange2), |  | ||||||
| +                messages=(
 |  | ||||||
| +                    messages.ServiceRestartRequired(
 |  | ||||||
| +                        service=dirsrv_instance,
 |  | ||||||
| +                        server='<all IPA servers>').to_dict(),
 |  | ||||||
| +                ),
 |  | ||||||
|              ), |  | ||||||
|          ), |  | ||||||
|   |  | ||||||
| @@ -792,6 +804,11 @@ class test_range(Declarative):
 |  | ||||||
|                  ), |  | ||||||
|                  value=unicode(domain7range1), |  | ||||||
|                  summary=u'Added ID range "%s"' % (domain7range1), |  | ||||||
| +                messages=(
 |  | ||||||
| +                    messages.ServiceRestartRequired(
 |  | ||||||
| +                        service=dirsrv_instance,
 |  | ||||||
| +                        server='<all IPA servers>').to_dict(),
 |  | ||||||
| +                ),
 |  | ||||||
|              ), |  | ||||||
|          ), |  | ||||||
|   |  | ||||||
| @@ -1079,6 +1096,11 @@ class test_range(Declarative):
 |  | ||||||
|                  ), |  | ||||||
|                  value=testrange9, |  | ||||||
|                  summary=u'Added ID range "%s"' % (testrange9), |  | ||||||
| +                messages=(
 |  | ||||||
| +                    messages.ServiceRestartRequired(
 |  | ||||||
| +                        service=dirsrv_instance,
 |  | ||||||
| +                        server='<all IPA servers>').to_dict(),
 |  | ||||||
| +                ),
 |  | ||||||
|              ), |  | ||||||
|          ), |  | ||||||
|   |  | ||||||
| diff --git a/ipaserver/plugins/idrange.py b/ipaserver/plugins/idrange.py
 |  | ||||||
| index b38ea73..b12e1b8 100644
 |  | ||||||
| --- a/ipaserver/plugins/idrange.py
 |  | ||||||
| +++ b/ipaserver/plugins/idrange.py
 |  | ||||||
| @@ -549,12 +549,15 @@ class idrange_add(LDAPCreate):
 |  | ||||||
|          self.obj.handle_ipabaserid(entry_attrs, options) |  | ||||||
|          self.obj.handle_iparangetype(entry_attrs, options, |  | ||||||
|                                       keep_objectclass=True) |  | ||||||
| -        self.add_message(
 |  | ||||||
| -            messages.ServiceRestartRequired(
 |  | ||||||
| -                service=services.knownservices.dirsrv.service_instance(""),
 |  | ||||||
| -                server=_('<all IPA servers>')
 |  | ||||||
| +
 |  | ||||||
| +        if entry_attrs.single_value.get('iparangetype') in (
 |  | ||||||
| +                'ipa-local', self.obj.range_types.get('ipa-local', None)):
 |  | ||||||
| +            self.add_message(
 |  | ||||||
| +                messages.ServiceRestartRequired(
 |  | ||||||
| +                    service=services.knownservices.dirsrv.service_instance(""),
 |  | ||||||
| +                    server=_('<all IPA servers>')
 |  | ||||||
| +                )
 |  | ||||||
|              ) |  | ||||||
| -        )
 |  | ||||||
|          return dn |  | ||||||
|   |  | ||||||
|   |  | ||||||
| @@ -568,7 +571,8 @@ class idrange_del(LDAPDelete):
 |  | ||||||
|          try: |  | ||||||
|              old_attrs = ldap.get_entry(dn, ['ipabaseid', |  | ||||||
|                                              'ipaidrangesize', |  | ||||||
| -                                            'ipanttrusteddomainsid'])
 |  | ||||||
| +                                            'ipanttrusteddomainsid',
 |  | ||||||
| +                                            'iparangetype'])
 |  | ||||||
|          except errors.NotFound: |  | ||||||
|              raise self.obj.handle_not_found(*keys) |  | ||||||
|   |  | ||||||
| @@ -602,6 +606,20 @@ class idrange_del(LDAPDelete):
 |  | ||||||
|                      key=keys[0], |  | ||||||
|                      dependent=trust_domains[0].dn[0].value) |  | ||||||
|   |  | ||||||
| +        self.add_message(
 |  | ||||||
| +            messages.ServiceRestartRequired(
 |  | ||||||
| +                service=services.knownservices['sssd'].systemd_name,
 |  | ||||||
| +                server=_('<all IPA servers>')
 |  | ||||||
| +            )
 |  | ||||||
| +        )
 |  | ||||||
| +
 |  | ||||||
| +        if old_attrs.single_value.get('iparangetype') == 'ipa-local':
 |  | ||||||
| +            self.add_message(
 |  | ||||||
| +                messages.ServiceRestartRequired(
 |  | ||||||
| +                    service=services.knownservices.dirsrv.service_instance(""),
 |  | ||||||
| +                    server=_('<all IPA servers>')
 |  | ||||||
| +                )
 |  | ||||||
| +            )
 |  | ||||||
|   |  | ||||||
|          return dn |  | ||||||
|   |  | ||||||
| @@ -804,10 +822,20 @@ class idrange_mod(LDAPUpdate):
 |  | ||||||
|          assert isinstance(dn, DN) |  | ||||||
|          self.obj.handle_ipabaserid(entry_attrs, options) |  | ||||||
|          self.obj.handle_iparangetype(entry_attrs, options) |  | ||||||
| +
 |  | ||||||
| +        if entry_attrs.single_value.get('iparangetype') in (
 |  | ||||||
| +                'ipa-local', self.obj.range_types.get('ipa-local', None)):
 |  | ||||||
| +            self.add_message(
 |  | ||||||
| +                messages.ServiceRestartRequired(
 |  | ||||||
| +                    service=services.knownservices.dirsrv.service_instance(""),
 |  | ||||||
| +                    server=_('<all IPA servers>')
 |  | ||||||
| +                )
 |  | ||||||
| +            )
 |  | ||||||
| +
 |  | ||||||
|          self.add_message( |  | ||||||
|              messages.ServiceRestartRequired( |  | ||||||
|                  service=services.knownservices['sssd'].systemd_name, |  | ||||||
| -                server=keys[0]
 |  | ||||||
| +                server=_('<all IPA servers>')
 |  | ||||||
|              ) |  | ||||||
|          ) |  | ||||||
|          return dn |  | ||||||
| diff --git a/ipatests/test_xmlrpc/test_range_plugin.py b/ipatests/test_xmlrpc/test_range_plugin.py
 |  | ||||||
| index e3f4c23..531fe4a 100644
 |  | ||||||
| --- a/ipatests/test_xmlrpc/test_range_plugin.py
 |  | ||||||
| +++ b/ipatests/test_xmlrpc/test_range_plugin.py
 |  | ||||||
| @@ -26,7 +26,8 @@ import six
 |  | ||||||
|  from ipalib import api, errors, messages |  | ||||||
|  from ipalib import constants |  | ||||||
|  from ipaplatform import services |  | ||||||
| -from ipatests.test_xmlrpc.xmlrpc_test import Declarative, fuzzy_uuid
 |  | ||||||
| +from ipatests.test_xmlrpc.xmlrpc_test import (
 |  | ||||||
| +    Declarative, fuzzy_uuid, Fuzzy, fuzzy_sequence_of)
 |  | ||||||
|  from ipatests.test_xmlrpc import objectclasses |  | ||||||
|  from ipatests.util import MockLDAP |  | ||||||
|  from ipapython.dn import DN |  | ||||||
| @@ -374,6 +375,8 @@ IPA_LOCAL_RANGE_MOD_ERR = (
 |  | ||||||
|   |  | ||||||
|  dirsrv_instance = services.knownservices.dirsrv.service_instance("") |  | ||||||
|   |  | ||||||
| +fuzzy_restart_messages = fuzzy_sequence_of(Fuzzy(type=dict))
 |  | ||||||
| +
 |  | ||||||
|   |  | ||||||
|  @pytest.mark.tier1 |  | ||||||
|  class test_range(Declarative): |  | ||||||
| @@ -610,7 +613,8 @@ class test_range(Declarative):
 |  | ||||||
|              desc='Delete ID range %r' % testrange1, |  | ||||||
|              command=('idrange_del', [testrange1], {}), |  | ||||||
|              expected=dict( |  | ||||||
| -                result=dict(failed=[]),
 |  | ||||||
| +                result=dict(failed=[],
 |  | ||||||
| +                            messages=fuzzy_restart_messages),
 |  | ||||||
|                  value=[testrange1], |  | ||||||
|                  summary=u'Deleted ID range "%s"' % testrange1, |  | ||||||
|              ), |  | ||||||
| @@ -714,7 +718,8 @@ class test_range(Declarative):
 |  | ||||||
|              desc='Delete ID range %r' % testrange2, |  | ||||||
|              command=('idrange_del', [testrange2], {}), |  | ||||||
|              expected=dict( |  | ||||||
| -                result=dict(failed=[]),
 |  | ||||||
| +                result=dict(failed=[],
 |  | ||||||
| +                            messages=fuzzy_restart_messages),
 |  | ||||||
|                  value=[testrange2], |  | ||||||
|                  summary=u'Deleted ID range "%s"' % testrange2, |  | ||||||
|              ), |  | ||||||
| diff --git a/ipatests/test_xmlrpc/test_range_plugin.py b/ipatests/test_xmlrpc/test_range_plugin.py
 |  | ||||||
| index 531fe4a..3646952 100644
 |  | ||||||
| --- a/ipatests/test_xmlrpc/test_range_plugin.py
 |  | ||||||
| +++ b/ipatests/test_xmlrpc/test_range_plugin.py
 |  | ||||||
| @@ -613,8 +613,8 @@ class test_range(Declarative):
 |  | ||||||
|              desc='Delete ID range %r' % testrange1, |  | ||||||
|              command=('idrange_del', [testrange1], {}), |  | ||||||
|              expected=dict( |  | ||||||
| -                result=dict(failed=[],
 |  | ||||||
| -                            messages=fuzzy_restart_messages),
 |  | ||||||
| +                result=dict(failed=[]),
 |  | ||||||
| +                messages=fuzzy_restart_messages,
 |  | ||||||
|                  value=[testrange1], |  | ||||||
|                  summary=u'Deleted ID range "%s"' % testrange1, |  | ||||||
|              ), |  | ||||||
| @@ -718,8 +718,8 @@ class test_range(Declarative):
 |  | ||||||
|              desc='Delete ID range %r' % testrange2, |  | ||||||
|              command=('idrange_del', [testrange2], {}), |  | ||||||
|              expected=dict( |  | ||||||
| -                result=dict(failed=[],
 |  | ||||||
| -                            messages=fuzzy_restart_messages),
 |  | ||||||
| +                result=dict(failed=[]),
 |  | ||||||
| +                messages=fuzzy_restart_messages,
 |  | ||||||
|                  value=[testrange2], |  | ||||||
|                  summary=u'Deleted ID range "%s"' % testrange2, |  | ||||||
|              ), |  | ||||||
| @@ -809,11 +809,6 @@ class test_range(Declarative):
 |  | ||||||
|                  ), |  | ||||||
|                  value=unicode(domain7range1), |  | ||||||
|                  summary=u'Added ID range "%s"' % (domain7range1), |  | ||||||
| -                messages=(
 |  | ||||||
| -                    messages.ServiceRestartRequired(
 |  | ||||||
| -                        service=dirsrv_instance,
 |  | ||||||
| -                        server='<all IPA servers>').to_dict(),
 |  | ||||||
| -                ),
 |  | ||||||
|              ), |  | ||||||
|          ), |  | ||||||
|   |  | ||||||
| @@ -836,6 +831,7 @@ class test_range(Declarative):
 |  | ||||||
|                  result=dict(failed=[]), |  | ||||||
|                  value=[domain1range1], |  | ||||||
|                  summary=u'Deleted ID range "%s"' % domain1range1, |  | ||||||
| +                messages=fuzzy_restart_messages,
 |  | ||||||
|              ), |  | ||||||
|          ), |  | ||||||
|   |  | ||||||
| @@ -862,12 +858,7 @@ class test_range(Declarative):
 |  | ||||||
|              command=('idrange_mod', [domain3range2], |  | ||||||
|                       dict(ipabaseid=domain3range1_base_id)), |  | ||||||
|              expected=dict( |  | ||||||
| -                messages=(
 |  | ||||||
| -                    messages.ServiceRestartRequired(
 |  | ||||||
| -                        service=services.knownservices['sssd'].systemd_name,
 |  | ||||||
| -                        server=domain3range2
 |  | ||||||
| -                    ).to_dict(),
 |  | ||||||
| -                ),
 |  | ||||||
| +                messages=fuzzy_restart_messages,
 |  | ||||||
|                  result=dict( |  | ||||||
|                      cn=[domain3range2], |  | ||||||
|                      ipabaseid=[unicode(domain3range1_base_id)], |  | ||||||
| @@ -933,12 +924,7 @@ class test_range(Declarative):
 |  | ||||||
|              command=('idrange_mod', [domain2range1], |  | ||||||
|                       dict(ipabaserid=domain5range1_base_rid)), |  | ||||||
|              expected=dict( |  | ||||||
| -                messages=(
 |  | ||||||
| -                    messages.ServiceRestartRequired(
 |  | ||||||
| -                        service=services.knownservices['sssd'].systemd_name,
 |  | ||||||
| -                        server=domain2range1
 |  | ||||||
| -                    ).to_dict(),
 |  | ||||||
| -                ),
 |  | ||||||
| +                messages=fuzzy_restart_messages,
 |  | ||||||
|                  result=dict( |  | ||||||
|                      cn=[domain2range1], |  | ||||||
|                      ipabaseid=[unicode(domain2range1_base_id)], |  | ||||||
| @@ -973,12 +959,7 @@ class test_range(Declarative):
 |  | ||||||
|              command=('idrange_mod', [domain2range1], |  | ||||||
|                       dict(ipaautoprivategroups='true')), |  | ||||||
|              expected=dict( |  | ||||||
| -                messages=(
 |  | ||||||
| -                    messages.ServiceRestartRequired(
 |  | ||||||
| -                        service=services.knownservices['sssd'].systemd_name,
 |  | ||||||
| -                        server=domain2range1
 |  | ||||||
| -                    ).to_dict(),
 |  | ||||||
| -                ),
 |  | ||||||
| +                messages=fuzzy_restart_messages,
 |  | ||||||
|                  result=dict( |  | ||||||
|                      cn=[domain2range1], |  | ||||||
|                      ipabaseid=[unicode(domain2range1_base_id)], |  | ||||||
| @@ -1000,12 +981,7 @@ class test_range(Declarative):
 |  | ||||||
|              command=('idrange_mod', [domain2range1], |  | ||||||
|                       dict(ipaautoprivategroups='false')), |  | ||||||
|              expected=dict( |  | ||||||
| -                messages=(
 |  | ||||||
| -                    messages.ServiceRestartRequired(
 |  | ||||||
| -                        service=services.knownservices['sssd'].systemd_name,
 |  | ||||||
| -                        server=domain2range1
 |  | ||||||
| -                    ).to_dict(),
 |  | ||||||
| -                ),
 |  | ||||||
| +                messages=fuzzy_restart_messages,
 |  | ||||||
|                  result=dict( |  | ||||||
|                      cn=[domain2range1], |  | ||||||
|                      ipabaseid=[unicode(domain2range1_base_id)], |  | ||||||
| @@ -1027,12 +1003,7 @@ class test_range(Declarative):
 |  | ||||||
|              command=('idrange_mod', [domain2range1], |  | ||||||
|                       dict(ipaautoprivategroups='hybrid')), |  | ||||||
|              expected=dict( |  | ||||||
| -                messages=(
 |  | ||||||
| -                    messages.ServiceRestartRequired(
 |  | ||||||
| -                        service=services.knownservices['sssd'].systemd_name,
 |  | ||||||
| -                        server=domain2range1
 |  | ||||||
| -                    ).to_dict(),
 |  | ||||||
| -                ),
 |  | ||||||
| +                messages=fuzzy_restart_messages,
 |  | ||||||
|                  result=dict( |  | ||||||
|                      cn=[domain2range1], |  | ||||||
|                      ipabaseid=[unicode(domain2range1_base_id)], |  | ||||||
| @@ -1054,12 +1025,7 @@ class test_range(Declarative):
 |  | ||||||
|              command=('idrange_mod', [domain2range1], |  | ||||||
|                       dict(ipaautoprivategroups='')), |  | ||||||
|              expected=dict( |  | ||||||
| -                messages=(
 |  | ||||||
| -                    messages.ServiceRestartRequired(
 |  | ||||||
| -                        service=services.knownservices['sssd'].systemd_name,
 |  | ||||||
| -                        server=domain2range1
 |  | ||||||
| -                    ).to_dict(),
 |  | ||||||
| -                ),
 |  | ||||||
| +                messages=fuzzy_restart_messages,
 |  | ||||||
|                  result=dict( |  | ||||||
|                      cn=[domain2range1], |  | ||||||
|                      ipabaseid=[unicode(domain2range1_base_id)], |  | ||||||
| @@ -1116,6 +1082,7 @@ class test_range(Declarative):
 |  | ||||||
|                  result=dict(failed=[]), |  | ||||||
|                  value=[testrange9], |  | ||||||
|                  summary=u'Deleted ID range "%s"' % testrange9, |  | ||||||
| +                messages=fuzzy_restart_messages,
 |  | ||||||
|              ), |  | ||||||
|          ), |  | ||||||
|   |  | ||||||
| @ -1,58 +0,0 @@ | |||||||
| diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py
 |  | ||||||
| index 619be83..9be1b67 100644
 |  | ||||||
| --- a/ipaserver/plugins/cert.py
 |  | ||||||
| +++ b/ipaserver/plugins/cert.py
 |  | ||||||
| @@ -55,7 +55,7 @@ from ipapython.dn import DN
 |  | ||||||
|  from ipapython.ipautil import datetime_from_utctimestamp |  | ||||||
|  from ipaserver.plugins.service import normalize_principal, validate_realm |  | ||||||
|  from ipaserver.masters import ( |  | ||||||
| -    ENABLED_SERVICE, CONFIGURED_SERVICE, is_service_enabled
 |  | ||||||
| +    ENABLED_SERVICE, CONFIGURED_SERVICE, HIDDEN_SERVICE, is_service_enabled
 |  | ||||||
|  ) |  | ||||||
|   |  | ||||||
|  try: |  | ||||||
| @@ -300,7 +300,7 @@ def caacl_check(principal, ca, profile_id):
 |  | ||||||
|  def ca_kdc_check(api_instance, hostname): |  | ||||||
|      master_dn = api_instance.Object.server.get_dn(unicode(hostname)) |  | ||||||
|      kdc_dn = DN(('cn', 'KDC'), master_dn) |  | ||||||
| -    wanted = {ENABLED_SERVICE, CONFIGURED_SERVICE}
 |  | ||||||
| +    wanted = {ENABLED_SERVICE, CONFIGURED_SERVICE, HIDDEN_SERVICE}
 |  | ||||||
|      try: |  | ||||||
|          kdc_entry = api_instance.Backend.ldap2.get_entry( |  | ||||||
|              kdc_dn, ['ipaConfigString']) |  | ||||||
| diff --git a/ipatests/test_integration/test_replica_promotion.py b/ipatests/test_integration/test_replica_promotion.py
 |  | ||||||
| index b71f2d5..7ef44c5 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_replica_promotion.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_replica_promotion.py
 |  | ||||||
| @@ -26,6 +26,7 @@ from ipalib.constants import (
 |  | ||||||
|  ) |  | ||||||
|  from ipaplatform.paths import paths |  | ||||||
|  from ipapython import certdb |  | ||||||
| +from ipatests.test_integration.test_cert import get_certmonger_fs_id
 |  | ||||||
|  from ipatests.test_integration.test_dns_locations import ( |  | ||||||
|      resolve_records_from_server, IPA_DEFAULT_MASTER_SRV_REC |  | ||||||
|  ) |  | ||||||
| @@ -1241,6 +1242,23 @@ class TestHiddenReplicaPromotion(IntegrationTest):
 |  | ||||||
|              'ipa-crlgen-manage', 'status']) |  | ||||||
|          assert "CRL generation: enabled" in result.stdout_text |  | ||||||
|   |  | ||||||
| +    def test_hidden_replica_renew_pkinit_cert(self):
 |  | ||||||
| +        """Renew the PKINIT cert on a hidden replica.
 |  | ||||||
| +
 |  | ||||||
| +        Test for https://pagure.io/freeipa/issue/9611
 |  | ||||||
| +        """
 |  | ||||||
| +        # Get Request ID
 |  | ||||||
| +        cmd = ['getcert', 'list', '-f', paths.KDC_CERT]
 |  | ||||||
| +        result = self.replicas[0].run_command(cmd)
 |  | ||||||
| +        req_id = get_certmonger_fs_id(result.stdout_text)
 |  | ||||||
| +
 |  | ||||||
| +        self.replicas[0].run_command([
 |  | ||||||
| +            'getcert', 'resubmit', '-f', paths.KDC_CERT
 |  | ||||||
| +        ])
 |  | ||||||
| +        tasks.wait_for_certmonger_status(
 |  | ||||||
| +            self.replicas[0], ('MONITORING'), req_id, timeout=600
 |  | ||||||
| +        )
 |  | ||||||
| +
 |  | ||||||
|   |  | ||||||
|  class TestHiddenReplicaKRA(IntegrationTest): |  | ||||||
|      """Test KRA & hidden replica features. |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,87 +0,0 @@ | |||||||
| diff --git a/install/updates/50-krbenctypes.update b/install/updates/50-krbenctypes.update
 |  | ||||||
| index 1058a92..1bf2bf3 100644
 |  | ||||||
| --- a/install/updates/50-krbenctypes.update
 |  | ||||||
| +++ b/install/updates/50-krbenctypes.update
 |  | ||||||
| @@ -7,3 +7,5 @@ add: krbSupportedEncSaltTypes: aes128-sha2:normal
 |  | ||||||
|  add: krbSupportedEncSaltTypes: aes128-sha2:special |  | ||||||
|  add: krbSupportedEncSaltTypes: aes256-sha2:normal |  | ||||||
|  add: krbSupportedEncSaltTypes: aes256-sha2:special |  | ||||||
| +remove: krbDefaultEncSaltTypes: des3-hmac-sha1:special
 |  | ||||||
| +remove: krbDefaultEncSaltTypes: arcfour-hmac:special
 |  | ||||||
| diff --git a/install/updates/60-trusts.update b/install/updates/60-trusts.update
 |  | ||||||
| index 56e3920..b2fdcca 100644
 |  | ||||||
| --- a/install/updates/60-trusts.update
 |  | ||||||
| +++ b/install/updates/60-trusts.update
 |  | ||||||
| @@ -54,4 +54,4 @@ add:aci: (target="ldap:///krbprincipalname=cifs/($$dn),cn=services,cn=accounts,$
 |  | ||||||
|   |  | ||||||
|  # Add the default PAC type to configuration |  | ||||||
|  dn: cn=ipaConfig,cn=etc,$SUFFIX |  | ||||||
| -addifnew: ipaKrbAuthzData: MS-PAC
 |  | ||||||
| +add: ipaKrbAuthzData: MS-PAC
 |  | ||||||
| diff --git a/ipatests/test_integration/test_installation.py b/ipatests/test_integration/test_installation.py
 |  | ||||||
| index d41c1ee..ef0727e 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_installation.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_installation.py
 |  | ||||||
| @@ -1188,6 +1188,21 @@ class TestInstallMaster(IntegrationTest):
 |  | ||||||
|                  expected_stdout=f'href="https://{self.master.hostname}/' |  | ||||||
|              ) |  | ||||||
|   |  | ||||||
| +    def test_pac_configuration_enabled(self):
 |  | ||||||
| +        """
 |  | ||||||
| +        This testcase checks that the default PAC type
 |  | ||||||
| +        is added to configuration.
 |  | ||||||
| +        """
 |  | ||||||
| +        base_dn = str(self.master.domain.basedn)
 |  | ||||||
| +        dn = DN(
 |  | ||||||
| +            ("cn", "ipaConfig"),
 |  | ||||||
| +            ("cn", "etc"),
 |  | ||||||
| +            base_dn
 |  | ||||||
| +        )
 |  | ||||||
| +        result = tasks.ldapsearch_dm(self.master, str(dn),
 |  | ||||||
| +                                     ["ipaKrbAuthzData"])
 |  | ||||||
| +        assert 'ipaKrbAuthzData: MS-PAC' in result.stdout_text
 |  | ||||||
| +
 |  | ||||||
|      def test_hostname_parameter(self, server_cleanup): |  | ||||||
|          """ |  | ||||||
|          Test that --hostname parameter is respected in interactive mode. |  | ||||||
| diff --git a/ipatests/test_integration/test_upgrade.py b/ipatests/test_integration/test_upgrade.py
 |  | ||||||
| index 182e3b5..8465cf9 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_upgrade.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_upgrade.py
 |  | ||||||
| @@ -165,7 +165,6 @@ class TestUpgrade(IntegrationTest):
 |  | ||||||
|                  ldap.update_entry(location_krb_rec) |  | ||||||
|   |  | ||||||
|          yield _setup_locations |  | ||||||
| -
 |  | ||||||
|          ldap = self.master.ldap_connect() |  | ||||||
|   |  | ||||||
|          modified = False |  | ||||||
| @@ -477,3 +476,28 @@ class TestUpgrade(IntegrationTest):
 |  | ||||||
|          self.master.run_command(['ipa-server-upgrade']) |  | ||||||
|          assert self.master.transport.file_exists( |  | ||||||
|              paths.SYSTEMD_PKI_TOMCAT_IPA_CONF) |  | ||||||
| +
 |  | ||||||
| +    def test_mspac_attribute_set(self):
 |  | ||||||
| +        """
 |  | ||||||
| +        This testcase deletes the already existing attribute
 |  | ||||||
| +        'ipaKrbAuthzData: MS-PAC'.
 |  | ||||||
| +        The test then runs ipa-server-upgrade and checks that
 |  | ||||||
| +        the attribute 'ipaKrbAuthzData: MS-PAC' is added again.
 |  | ||||||
| +        """
 |  | ||||||
| +        base_dn = str(self.master.domain.basedn)
 |  | ||||||
| +        dn = DN(
 |  | ||||||
| +            ("cn", "ipaConfig"),
 |  | ||||||
| +            ("cn", "etc"),
 |  | ||||||
| +            base_dn
 |  | ||||||
| +        )
 |  | ||||||
| +        ldif = textwrap.dedent("""
 |  | ||||||
| +             dn: cn=ipaConfig,cn=etc,{}
 |  | ||||||
| +             changetype: modify
 |  | ||||||
| +             delete: ipaKrbAuthzData
 |  | ||||||
| +        """).format(base_dn)
 |  | ||||||
| +        tasks.ldapmodify_dm(self.master, ldif)
 |  | ||||||
| +        tasks.kinit_admin(self.master)
 |  | ||||||
| +        self.master.run_command(['ipa-server-upgrade'])
 |  | ||||||
| +        result = tasks.ldapsearch_dm(self.master, str(dn),
 |  | ||||||
| +                                     ["ipaKrbAuthzData"])
 |  | ||||||
| +        assert 'ipaKrbAuthzData: MS-PAC' in result.stdout_text
 |  | ||||||
| @ -1,72 +0,0 @@ | |||||||
| From f6645ebe5c0c0c030ec2e62e007d8dacd1b4e4cf Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Erik Belko <ebelko@redhat.com> |  | ||||||
| Date: Sep 03 2024 12:54:30 +0000 |  | ||||||
| Subject: ipatests: Update ipa-adtrust-install test |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| update test_user_connects_smb_share_if_locked_specific_group with wait |  | ||||||
| for SSSD to be online after ipa-adtrust-install command |  | ||||||
| 
 |  | ||||||
| Related: https://pagure.io/freeipa/issue/9655 |  | ||||||
| 
 |  | ||||||
| Signed-off-by: Erik Belko <ebelko@redhat.com> |  | ||||||
| Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com> |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
| 
 |  | ||||||
| diff --git a/ipatests/test_integration/test_adtrust_install.py b/ipatests/test_integration/test_adtrust_install.py
 |  | ||||||
| index 72e8d87..de252db 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_adtrust_install.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_adtrust_install.py
 |  | ||||||
| @@ -853,6 +853,8 @@ class TestIpaAdTrustInstall(IntegrationTest):
 |  | ||||||
|               self.master.config.admin_password, |  | ||||||
|               "-U"] |  | ||||||
|          ) |  | ||||||
| +        # Wait for SSSD to become online before doing any other check
 |  | ||||||
| +        tasks.wait_for_sssd_domain_status_online(self.master)
 |  | ||||||
|          self.master.run_command(["mkdir", "/freeipa4234"]) |  | ||||||
|          self.master.run_command( |  | ||||||
|              ["chcon", "-t", "samba_share_t", |  | ||||||
| 
 |  | ||||||
| From 47920e78c81380c0a40986e55f05246aac132fbb Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Erik Belko <ebelko@redhat.com> |  | ||||||
| Date: May 21 2024 12:50:46 +0000 |  | ||||||
| Subject: ipatests: Update ipa-adtrust-install test |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| update after change in implementation of `krb_utils.get_principal()` now using GSSAPI |  | ||||||
| 
 |  | ||||||
| Related: https://pagure.io/freeipa/issue/9575 |  | ||||||
| 
 |  | ||||||
| Signed-off-by: Erik Belko <ebelko@redhat.com> |  | ||||||
| Reviewed-By: Michal Polovka <mpolovka@redhat.com> |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
| 
 |  | ||||||
| diff --git a/ipatests/test_integration/test_adtrust_install.py b/ipatests/test_integration/test_adtrust_install.py
 |  | ||||||
| index 86d8d20..72e8d87 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_adtrust_install.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_adtrust_install.py
 |  | ||||||
| @@ -464,18 +464,15 @@ class TestIpaAdTrustInstall(IntegrationTest):
 |  | ||||||
|          password |  | ||||||
|          """ |  | ||||||
|          password = "wrong_pwd" |  | ||||||
| -        msg = (
 |  | ||||||
| -            "Must have Kerberos credentials to setup AD trusts on server: "
 |  | ||||||
| -            "Major (458752): No credentials were supplied, or the credentials "
 |  | ||||||
| -            "were unavailable or inaccessible, Minor (2529639053): "
 |  | ||||||
| -            "No Kerberos credentials available (default cache: KCM:)\n"
 |  | ||||||
| +        expected_substring = (
 |  | ||||||
| +            "Must have Kerberos credentials to setup AD trusts on server:"
 |  | ||||||
|          ) |  | ||||||
|          self.master.run_command(["kdestroy", "-A"]) |  | ||||||
|          result = self.master.run_command( |  | ||||||
|              ["ipa-adtrust-install", "-A", "admin", "-a", |  | ||||||
|               password, "-U"], raiseonerr=False |  | ||||||
|          ) |  | ||||||
| -        assert msg in result.stderr_text
 |  | ||||||
| +        assert expected_substring in result.stderr_text
 |  | ||||||
|          assert result.returncode != 0 |  | ||||||
|   |  | ||||||
|      def test_adtrust_install_with_invalid_rid_base_value(self): |  | ||||||
| 
 |  | ||||||
| @ -1,245 +0,0 @@ | |||||||
| From 19f22cf75ae768dd2b6c0d674cf55f8d6ffafb31 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| Date: Mar 07 2025 06:48:02 +0000 |  | ||||||
| Subject: Replica CA installation: ignore time skew during initial replication |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| During a replica CA installation, the initial replication step may fail |  | ||||||
| if there is too much time skew between the server and replica. |  | ||||||
| 
 |  | ||||||
| The replica installer already takes care of this for the replication of |  | ||||||
| the domain suffix but the replica CA installer does not set |  | ||||||
| nssldapd-ignore-time-skew to on for o=ipaca suffix. |  | ||||||
| 
 |  | ||||||
| During a replica CA installation, read the initial value of |  | ||||||
| nssldapd-ignore-time-skew, force it to on, start replication and |  | ||||||
| revert to the initial value. |  | ||||||
| 
 |  | ||||||
| Apply the same logic to dsinstance and ipa-replica-manage force-sync. |  | ||||||
| 
 |  | ||||||
| Fixes: https://pagure.io/freeipa/issue/9635 |  | ||||||
| Signed-off-by: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| Reviewed-By: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| Reviewed-By: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
| 
 |  | ||||||
| diff --git a/install/share/Makefile.am b/install/share/Makefile.am
 |  | ||||||
| index e0fe4b7..4029297 100644
 |  | ||||||
| --- a/install/share/Makefile.am
 |  | ||||||
| +++ b/install/share/Makefile.am
 |  | ||||||
| @@ -38,7 +38,6 @@ dist_app_DATA =				\
 |  | ||||||
|  	default-trust-view.ldif		\ |  | ||||||
|  	delegation.ldif			\ |  | ||||||
|  	replica-acis.ldif		\ |  | ||||||
| -	replica-prevent-time-skew.ldif  \
 |  | ||||||
|  	ds-nfiles.ldif			\ |  | ||||||
|  	ds-ipa-env.conf.template	\ |  | ||||||
|  	dns.ldif			\ |  | ||||||
| diff --git a/install/share/replica-prevent-time-skew.ldif b/install/share/replica-prevent-time-skew.ldif
 |  | ||||||
| deleted file mode 100644 |  | ||||||
| index 5d301fe..0000000
 |  | ||||||
| --- a/install/share/replica-prevent-time-skew.ldif
 |  | ||||||
| +++ /dev/null
 |  | ||||||
| @@ -1,4 +0,0 @@
 |  | ||||||
| -dn: cn=config
 |  | ||||||
| -changetype: modify
 |  | ||||||
| -replace: nsslapd-ignore-time-skew
 |  | ||||||
| -nsslapd-ignore-time-skew: $SKEWVALUE
 |  | ||||||
| diff --git a/install/tools/ipa-replica-manage.in b/install/tools/ipa-replica-manage.in
 |  | ||||||
| index cebf73a..71851be 100644
 |  | ||||||
| --- a/install/tools/ipa-replica-manage.in
 |  | ||||||
| +++ b/install/tools/ipa-replica-manage.in
 |  | ||||||
| @@ -1237,12 +1237,13 @@ def force_sync(realm, thishost, fromhost, dirman_passwd, nolookup=False):
 |  | ||||||
|          repl.force_sync(repl.conn, fromhost) |  | ||||||
|      else: |  | ||||||
|          ds = dsinstance.DsInstance(realm_name=realm) |  | ||||||
| -        ds.replica_manage_time_skew(prevent=False)
 |  | ||||||
| +        ds.replica_ignore_initial_time_skew()
 |  | ||||||
|          repl = replication.ReplicationManager(realm, fromhost, dirman_passwd) |  | ||||||
|          repl.force_sync(repl.conn, thishost) |  | ||||||
|          agreement = repl.get_replication_agreement(thishost) |  | ||||||
|          repl.wait_for_repl_update(repl.conn, agreement.dn) |  | ||||||
| -        ds.replica_manage_time_skew(prevent=True)
 |  | ||||||
| +        ds.replica_revert_time_skew()
 |  | ||||||
| +
 |  | ||||||
| 
 |  | ||||||
|  def show_DNA_ranges(hostname, master, realm, dirman_passwd, nextrange=False, |  | ||||||
|                      nolookup=False): |  | ||||||
| diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
 |  | ||||||
| index 35cec89..e15e629 100644
 |  | ||||||
| --- a/ipaserver/install/cainstance.py
 |  | ||||||
| +++ b/ipaserver/install/cainstance.py
 |  | ||||||
| @@ -409,7 +409,11 @@ class CAInstance(DogtagInstance):
 |  | ||||||
|              if promote: |  | ||||||
|                  # Setup Database |  | ||||||
|                  self.step("creating certificate server db", self.__create_ds_db) |  | ||||||
| +                self.step("ignore time skew for initial replication",
 |  | ||||||
| +                          self.replica_ignore_initial_time_skew)
 |  | ||||||
|                  self.step("setting up initial replication", self.__setup_replication) |  | ||||||
| +                self.step("revert time skew after initial replication",
 |  | ||||||
| +                          self.replica_revert_time_skew)
 |  | ||||||
|                  self.step("creating ACIs for admin", self.add_ipaca_aci) |  | ||||||
|                  self.step("creating installation admin user", self.setup_admin) |  | ||||||
|              self.step("configuring certificate server instance", |  | ||||||
| diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
 |  | ||||||
| index cbacfae..ba4bf8a 100644
 |  | ||||||
| --- a/ipaserver/install/dsinstance.py
 |  | ||||||
| +++ b/ipaserver/install/dsinstance.py
 |  | ||||||
| @@ -385,11 +385,11 @@ class DsInstance(service.Service):
 |  | ||||||
|          # This helps with initial replication or force-sync because |  | ||||||
|          # the receiving side has no valuable changes itself yet. |  | ||||||
|          self.step("ignore time skew for initial replication", |  | ||||||
| -                  self.__replica_ignore_initial_time_skew)
 |  | ||||||
| +                  self.replica_ignore_initial_time_skew)
 |  | ||||||
| 
 |  | ||||||
|          self.step("setting up initial replication", self.__setup_replica) |  | ||||||
|          self.step("prevent time skew after initial replication", |  | ||||||
| -                  self.replica_manage_time_skew)
 |  | ||||||
| +                  self.replica_revert_time_skew)
 |  | ||||||
|          self.step("adding sasl mappings to the directory", self.__configure_sasl_mappings) |  | ||||||
|          self.step("updating schema", self.__update_schema) |  | ||||||
|          # See LDIFs for automember configuration during replica install |  | ||||||
| @@ -995,16 +995,6 @@ class DsInstance(service.Service):
 |  | ||||||
|      def __add_replication_acis(self): |  | ||||||
|          self._ldap_mod("replica-acis.ldif", self.sub_dict) |  | ||||||
| 
 |  | ||||||
| -    def __replica_ignore_initial_time_skew(self):
 |  | ||||||
| -        self.replica_manage_time_skew(prevent=False)
 |  | ||||||
| -
 |  | ||||||
| -    def replica_manage_time_skew(self, prevent=True):
 |  | ||||||
| -        if prevent:
 |  | ||||||
| -            self.sub_dict['SKEWVALUE'] = 'off'
 |  | ||||||
| -        else:
 |  | ||||||
| -            self.sub_dict['SKEWVALUE'] = 'on'
 |  | ||||||
| -        self._ldap_mod("replica-prevent-time-skew.ldif", self.sub_dict)
 |  | ||||||
| -
 |  | ||||||
|      def __setup_s4u2proxy(self): |  | ||||||
| 
 |  | ||||||
|          def __add_principal(last_cn, principal, self): |  | ||||||
| diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py
 |  | ||||||
| index 13ae346..15ca70b 100644
 |  | ||||||
| --- a/ipaserver/install/service.py
 |  | ||||||
| +++ b/ipaserver/install/service.py
 |  | ||||||
| @@ -811,6 +811,31 @@ class Service:
 |  | ||||||
|          self.run_getkeytab(self.api.env.ldap_uri, self.keytab, self.principal) |  | ||||||
|          self.set_keytab_owner() |  | ||||||
| 
 |  | ||||||
| +    def replica_ignore_initial_time_skew(self):
 |  | ||||||
| +        """
 |  | ||||||
| +        Set nsslapd-ignore-time-skew = on if not already set
 |  | ||||||
| +        and store the initial value in order to restore it later.
 |  | ||||||
| +
 |  | ||||||
| +        The on value allows replica initialization even if there
 |  | ||||||
| +        are excessive time skews.
 |  | ||||||
| +        """
 |  | ||||||
| +        dn = DN(('cn', 'config'))
 |  | ||||||
| +        entry_attrs = api.Backend.ldap2.get_entry(dn)
 |  | ||||||
| +        self.original_time_skew = entry_attrs['nsslapd-ignore-time-skew'][0]
 |  | ||||||
| +        if self.original_time_skew != 'on':
 |  | ||||||
| +            entry_attrs['nsslapd-ignore-time-skew'] = 'on'
 |  | ||||||
| +            api.Backend.ldap2.update_entry(entry_attrs)
 |  | ||||||
| +
 |  | ||||||
| +    def replica_revert_time_skew(self):
 |  | ||||||
| +        """
 |  | ||||||
| +        Revert nsslapd-ignore-time-skew to its previous value.
 |  | ||||||
| +        """
 |  | ||||||
| +        dn = DN(('cn', 'config'))
 |  | ||||||
| +        entry_attrs = api.Backend.ldap2.get_entry(dn)
 |  | ||||||
| +        if self.original_time_skew != 'on':
 |  | ||||||
| +            entry_attrs['nsslapd-ignore-time-skew'] = self.original_time_skew
 |  | ||||||
| +            api.Backend.ldap2.update_entry(entry_attrs)
 |  | ||||||
| +
 |  | ||||||
| 
 |  | ||||||
|  class SimpleServiceInstance(Service): |  | ||||||
|      def create_instance(self, gensvc_name=None, fqdn=None, ldap_suffix=None, |  | ||||||
| 
 |  | ||||||
| From a6bb2fa4997dd7894dbf75d1c3fd1deaebd3e05c Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Sudhir Menon <sumenon@redhat.com> |  | ||||||
| Date: Mar 07 2025 06:48:02 +0000 |  | ||||||
| Subject: ipatests: Test to check that the configured value for "nsslapd-ignore-time-skew" remains on even after a "force-sync" is done |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Related: https://pagure.io/freeipa/issue/9635 |  | ||||||
| 
 |  | ||||||
| Signed-off-by: Sudhir Menon <sumenon@redhat.com> |  | ||||||
| Reviewed-By: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| Reviewed-By: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
| 
 |  | ||||||
| diff --git a/ipatests/test_integration/test_installation.py b/ipatests/test_integration/test_installation.py
 |  | ||||||
| index ef0727e..3673f7f 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_installation.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_installation.py
 |  | ||||||
| @@ -2132,3 +2132,69 @@ class TestHostnameValidator(IntegrationTest):
 |  | ||||||
|          assert result.returncode == 1 |  | ||||||
|          assert 'hostname cannot be the same as the domain name' \ |  | ||||||
|              in result.stderr_text |  | ||||||
| +
 |  | ||||||
| +
 |  | ||||||
| +class TestNsslapdIgnoreTimeSkew(IntegrationTest):
 |  | ||||||
| +    """
 |  | ||||||
| +    Test to check nsslapd-ignore-time-skew is not disabled.
 |  | ||||||
| +    """
 |  | ||||||
| +    num_replicas = 1
 |  | ||||||
| +    topology = 'line'
 |  | ||||||
| +
 |  | ||||||
| +    @pytest.fixture
 |  | ||||||
| +    def update_time_skew(self):
 |  | ||||||
| +        """
 |  | ||||||
| +        Fixture enables nsslapd-ignore-time-skew
 |  | ||||||
| +        parameter and reverts it back
 |  | ||||||
| +        """
 |  | ||||||
| +        ldap = self.replicas[0].ldap_connect()
 |  | ||||||
| +        dn = DN(
 |  | ||||||
| +            ("cn", "config"),
 |  | ||||||
| +        )
 |  | ||||||
| +        entry = ldap.get_entry(dn)
 |  | ||||||
| +        entry.single_value["nsslapd-ignore-time-skew"] = 'on'
 |  | ||||||
| +        ldap.update_entry(entry)
 |  | ||||||
| +
 |  | ||||||
| +        yield
 |  | ||||||
| +
 |  | ||||||
| +        entry = ldap.get_entry(dn)
 |  | ||||||
| +        entry.single_value["nsslapd-ignore-time-skew"] = 'off'
 |  | ||||||
| +        ldap.update_entry(entry)
 |  | ||||||
| +
 |  | ||||||
| +    def test_check_nsslapd_ignore_time_skew(self):
 |  | ||||||
| +        """
 |  | ||||||
| +        This testcase checks that the ignore time skew parameter
 |  | ||||||
| +        is set to on during the directory server replica
 |  | ||||||
| +        installation (replication of the suffix) and during
 |  | ||||||
| +        the CA replica (replication of o=ipaca).
 |  | ||||||
| +        It also checks that the time skew is reverted during
 |  | ||||||
| +        pki_tomcat setup stage.
 |  | ||||||
| +        """
 |  | ||||||
| +        DIRSRV_LOG = (
 |  | ||||||
| +            "ignore time skew for initial replication"
 |  | ||||||
| +        )
 |  | ||||||
| +        PKI_TOMCAT_LOG = (
 |  | ||||||
| +            "revert time skew after initial replication"
 |  | ||||||
| +        )
 |  | ||||||
| +        install_msg = self.replicas[0].get_file_contents(
 |  | ||||||
| +            paths.IPAREPLICA_INSTALL_LOG, encoding="utf-8"
 |  | ||||||
| +        )
 |  | ||||||
| +        dirsrv_msg = re.findall(DIRSRV_LOG, install_msg)
 |  | ||||||
| +        assert len(dirsrv_msg) == 2
 |  | ||||||
| +        assert PKI_TOMCAT_LOG in install_msg
 |  | ||||||
| +
 |  | ||||||
| +    def test_forcesync_does_not_overwrite_ignore_time_skew(
 |  | ||||||
| +            self, update_time_skew):
 |  | ||||||
| +        """
 |  | ||||||
| +        This testcase checks that calling ipa-replica-manage
 |  | ||||||
| +        force-sync does not overwrite the value of ignore
 |  | ||||||
| +        time skew
 |  | ||||||
| +        """
 |  | ||||||
| +        result = self.replicas[0].run_command(
 |  | ||||||
| +            ["ipa-replica-manage", "force-sync",
 |  | ||||||
| +             "--from", self.master.hostname,
 |  | ||||||
| +             "--no-lookup", "-v"])
 |  | ||||||
| +        assert result.returncode == 0
 |  | ||||||
| +        conn = self.replicas[0].ldap_connect()
 |  | ||||||
| +        ldap_entry = conn.get_entry(DN("cn=config"))
 |  | ||||||
| +        assert ldap_entry.single_value['nsslapd-ignore-time-skew'] == "on"
 |  | ||||||
| 
 |  | ||||||
| @ -1,82 +0,0 @@ | |||||||
| From ac6eee670d8a753e66ba69a65eff55447fff2822 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Aleksandr Sharov <asharov@redhat.com> |  | ||||||
| Date: Mar 25 2025 09:33:06 +0000 |  | ||||||
| Subject: Add a check into ipa-cert-fix tool to avoid updating certs if CA is close to being expired. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Fixes: https://pagure.io/freeipa/issue/9760 |  | ||||||
| Signed-off-by: Aleksandr Sharov <asharov@redhat.com> |  | ||||||
| Reviewed-By: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| Reviewed-By: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
| 
 |  | ||||||
| diff --git a/ipaserver/install/ipa_cert_fix.py b/ipaserver/install/ipa_cert_fix.py
 |  | ||||||
| index 8e02d1e..960d7b9 100644
 |  | ||||||
| --- a/ipaserver/install/ipa_cert_fix.py
 |  | ||||||
| +++ b/ipaserver/install/ipa_cert_fix.py
 |  | ||||||
| @@ -69,6 +69,7 @@ logger = logging.getLogger(__name__)
 |  | ||||||
|   |  | ||||||
|   |  | ||||||
|  cert_nicknames = { |  | ||||||
| +    'ca_issuing': 'caSigningCert cert-pki-ca',
 |  | ||||||
|      'sslserver': 'Server-Cert cert-pki-ca', |  | ||||||
|      'subsystem': 'subsystemCert cert-pki-ca', |  | ||||||
|      'ca_ocsp_signing': 'ocspSigningCert cert-pki-ca', |  | ||||||
| @@ -137,6 +138,16 @@ class IPACertFix(AdminTool):
 |  | ||||||
|              print("Nothing to do.") |  | ||||||
|              return 0 |  | ||||||
|   |  | ||||||
| +        if any(key == 'ca_issuing' for key, _ in certs):
 |  | ||||||
| +            logger.debug("CA signing cert is expired, exiting!")
 |  | ||||||
| +            print(
 |  | ||||||
| +                "The CA signing certificate is expired or will expire within "
 |  | ||||||
| +                "the next two weeks.\n\nipa-cert-fix cannot proceed, please "
 |  | ||||||
| +                "refer to the ipa-cacert-manage tool to renew the CA "
 |  | ||||||
| +                "certificate before proceeding."
 |  | ||||||
| +            )
 |  | ||||||
| +            return 1
 |  | ||||||
| +
 |  | ||||||
|          print(msg) |  | ||||||
|   |  | ||||||
|          print_intentions(certs, extra_certs, non_renewed) |  | ||||||
| 
 |  | ||||||
| From cdc03d7b6233f736c51c10aa07225aac9715e4c0 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Aleksandr Sharov <asharov@redhat.com> |  | ||||||
| Date: Mar 25 2025 18:03:54 +0000 |  | ||||||
| Subject: Test fix for the update |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Fixes: https://pagure.io/freeipa/issue/9760 |  | ||||||
| Signed-off-by: Aleksandr Sharov <asharov@redhat.com> |  | ||||||
| Reviewed-By: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| Reviewed-By: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
| 
 |  | ||||||
| diff --git a/ipatests/test_integration/test_ipa_cert_fix.py b/ipatests/test_integration/test_ipa_cert_fix.py
 |  | ||||||
| index 15d8a81..d11fd3d 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_ipa_cert_fix.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_ipa_cert_fix.py
 |  | ||||||
| @@ -301,13 +301,18 @@ class TestIpaCertFix(IntegrationTest):
 |  | ||||||
|          valid. If CA cert expired, ipa-cert-fix won't work. |  | ||||||
|   |  | ||||||
|          related: https://pagure.io/freeipa/issue/8721 |  | ||||||
| +
 |  | ||||||
| +        If CA cert is close to expiry, there's no reason to issue new certs
 |  | ||||||
| +        with short validity period. So, ipa-cert-fix should fail in this case.
 |  | ||||||
| +
 |  | ||||||
| +        related: https://pagure.io/freeipa/issue/9760
 |  | ||||||
|          """ |  | ||||||
|          result = self.master.run_command(['ipa-cert-fix', '-v'], |  | ||||||
|                                           stdin_text='yes\n', |  | ||||||
|                                           raiseonerr=False) |  | ||||||
|          # check that pki-server cert-fix command fails |  | ||||||
| -        err_msg = ("ERROR: CalledProcessError(Command "
 |  | ||||||
| -                   "['pki-server', 'cert-fix'")
 |  | ||||||
| +        err_msg = ("CA signing cert is expired, exiting!")
 |  | ||||||
| +        assert result.returncode == 1
 |  | ||||||
|          assert err_msg in result.stderr_text |  | ||||||
|   |  | ||||||
|   |  | ||||||
| 
 |  | ||||||
| @ -1,84 +0,0 @@ | |||||||
| From ae37b3e6ed12bddb650bdce8e9729e81fef40840 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Julien Rische <jrische@redhat.com> |  | ||||||
| Date: May 08 2025 06:21:00 +0000 |  | ||||||
| Subject: 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 <rcritten@redhat.com> |  | ||||||
| Reviewed-By: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com> |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
| 
 |  | ||||||
| diff --git a/daemons/ipa-kdb/ipa_kdb.c b/daemons/ipa-kdb/ipa_kdb.c
 |  | ||||||
| index fcadb8e..98315a0 100644
 |  | ||||||
| --- a/daemons/ipa-kdb/ipa_kdb.c
 |  | ||||||
| +++ b/daemons/ipa-kdb/ipa_kdb.c
 |  | ||||||
| @@ -524,26 +524,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, |  | ||||||
| 
 |  | ||||||
| @ -1,189 +0,0 @@ | |||||||
| From d6d2282f9f1b93ae7fb6e074920e41e64f35ab12 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| Date: Mon, 28 Apr 2025 13:43:40 -0400 |  | ||||||
| Subject: [PATCH] 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 <rcritten@redhat.com> |  | ||||||
| ---
 |  | ||||||
|  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 7c3bba3e0..3d78c7b5a 100644
 |  | ||||||
| --- a/install/updates/90-post_upgrade_plugins.update
 |  | ||||||
| +++ b/install/updates/90-post_upgrade_plugins.update
 |  | ||||||
| @@ -25,6 +25,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 621982c4f..1526a6e0d 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_commands.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_commands.py
 |  | ||||||
| @@ -1796,6 +1796,44 @@ class TestIPACommandWithoutReplica(IntegrationTest):
 |  | ||||||
|          assert result.returncode == 1 |  | ||||||
|          assert 'cannot be deleted or disabled' in result.stderr_text |  | ||||||
|   |  | ||||||
| +    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 TestIPACommandWithoutReplica(IntegrationTest): |  | ||||||
|      """ |  | ||||||
| --
 |  | ||||||
| 2.48.1 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @ -1,106 +0,0 @@ | |||||||
| From a37de8c22976a75caf969e232229ff6521ff4936 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| Date: Thu, 10 Jul 2025 11:44:36 -0400 |  | ||||||
| Subject: [PATCH] Enforce uniqueness across krbprincipalname and |  | ||||||
|  krbcanonicalname |  | ||||||
| 
 |  | ||||||
| This relies on a fix in 389-ds that extends the uniqueness plugin |  | ||||||
| to be able to compare attributes with different matching syntax. |  | ||||||
| 
 |  | ||||||
| This will prevent privilege escalation attacks if one of the |  | ||||||
| attributes is not set on an entry if it is set elsewhere. |  | ||||||
| 
 |  | ||||||
| Signed-off-by: Rob Crittenden <rcritten@redhat.com> |  | ||||||
| ---
 |  | ||||||
|  install/share/unique-attributes.ldif | 28 +++++----------------------- |  | ||||||
|  install/updates/10-uniqueness.update | 27 +++++++++++++++++++++++---- |  | ||||||
|  2 files changed, 28 insertions(+), 27 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/install/share/unique-attributes.ldif b/install/share/unique-attributes.ldif
 |  | ||||||
| index 60f2c3470..b28d981b5 100644
 |  | ||||||
| --- a/install/share/unique-attributes.ldif
 |  | ||||||
| +++ b/install/share/unique-attributes.ldif
 |  | ||||||
| @@ -1,34 +1,16 @@
 |  | ||||||
| -dn: cn=krbPrincipalName uniqueness,cn=plugins,cn=config
 |  | ||||||
| +dn: cn=kerberos name uniqueness,cn=plugins,cn=config
 |  | ||||||
|  changetype: add |  | ||||||
|  objectClass: top |  | ||||||
|  objectClass: nsSlapdPlugin |  | ||||||
|  objectClass: extensibleObject |  | ||||||
| -cn: krbPrincipalName uniqueness
 |  | ||||||
| +cn: kerberos name uniqueness
 |  | ||||||
|  nsslapd-pluginPath: libattr-unique-plugin |  | ||||||
|  nsslapd-pluginInitfunc: NSUniqueAttr_Init |  | ||||||
|  nsslapd-pluginType: preoperation |  | ||||||
|  nsslapd-pluginEnabled: on |  | ||||||
| -uniqueness-attribute-name: krbPrincipalName
 |  | ||||||
| -nsslapd-plugin-depends-on-type: database
 |  | ||||||
| -nsslapd-pluginId: NSUniqueAttr
 |  | ||||||
| -nsslapd-pluginVersion: 1.1.0
 |  | ||||||
| -nsslapd-pluginVendor: Fedora Project
 |  | ||||||
| -nsslapd-pluginDescription: Enforce unique attribute values
 |  | ||||||
| -uniqueness-subtrees: $SUFFIX
 |  | ||||||
| -uniqueness-exclude-subtrees: cn=staged users,cn=accounts,cn=provisioning,$SUFFIX
 |  | ||||||
| -uniqueness-across-all-subtrees: on
 |  | ||||||
| -
 |  | ||||||
| -dn: cn=krbCanonicalName uniqueness,cn=plugins,cn=config
 |  | ||||||
| -changetype: add
 |  | ||||||
| -objectClass: top
 |  | ||||||
| -objectClass: nsSlapdPlugin
 |  | ||||||
| -objectClass: extensibleObject
 |  | ||||||
| -cn: krbCanonicalName uniqueness
 |  | ||||||
| -nsslapd-pluginPath: libattr-unique-plugin
 |  | ||||||
| -nsslapd-pluginInitfunc: NSUniqueAttr_Init
 |  | ||||||
| -nsslapd-pluginType: preoperation
 |  | ||||||
| -nsslapd-pluginEnabled: on
 |  | ||||||
| -uniqueness-attribute-name: krbCanonicalName
 |  | ||||||
| +uniqueness-attribute-name: krbPrincipalName:CaseIgnoreMatch:
 |  | ||||||
| +uniqueness-attribute-name: krbPrincipalAlias:CaseIgnoreMatch:
 |  | ||||||
| +uniqueness-attribute-name: krbCanonicalName:CaseIgnoreMatch:
 |  | ||||||
|  nsslapd-plugin-depends-on-type: database |  | ||||||
|  nsslapd-pluginId: NSUniqueAttr |  | ||||||
|  nsslapd-pluginVersion: 1.1.0 |  | ||||||
| diff --git a/install/updates/10-uniqueness.update b/install/updates/10-uniqueness.update
 |  | ||||||
| index fa17911f2..5c5bfd3e0 100644
 |  | ||||||
| --- a/install/updates/10-uniqueness.update
 |  | ||||||
| +++ b/install/updates/10-uniqueness.update
 |  | ||||||
| @@ -63,13 +63,32 @@ add:uniqueness-subtree-entries-oc: posixAccount
 |  | ||||||
|   |  | ||||||
|  # krbPrincipalName uniqueness scopes Active/Delete containers |  | ||||||
|  dn: cn=krbPrincipalName uniqueness,cn=plugins,cn=config |  | ||||||
| -add:uniqueness-exclude-subtrees: cn=staged users,cn=accounts,cn=provisioning,$SUFFIX
 |  | ||||||
| -add:uniqueness-across-all-subtrees: on
 |  | ||||||
| +deleteentry: cn=krbPrincipalName uniqueness,cn=plugins,cn=config
 |  | ||||||
|   |  | ||||||
|  # krbCanonicalName uniqueness scopes Active/Delete containers |  | ||||||
|  dn: cn=krbCanonicalName uniqueness,cn=plugins,cn=config |  | ||||||
| -add:uniqueness-exclude-subtrees: cn=staged users,cn=accounts,cn=provisioning,$SUFFIX
 |  | ||||||
| -add:uniqueness-across-all-subtrees: on
 |  | ||||||
| +deleteentry: dn: cn=krbCanonicalName uniqueness,cn=plugins,cn=config
 |  | ||||||
| +
 |  | ||||||
| +dn: cn=kerberos name uniqueness,cn=plugins,cn=config
 |  | ||||||
| +default:objectClass: top
 |  | ||||||
| +default:objectClass: nsSlapdPlugin
 |  | ||||||
| +default:objectClass: extensibleObject
 |  | ||||||
| +default:cn: kerberos name uniqueness
 |  | ||||||
| +default:nsslapd-pluginPath: libattr-unique-plugin
 |  | ||||||
| +default:nsslapd-pluginInitfunc: NSUniqueAttr_Init
 |  | ||||||
| +default:nsslapd-pluginType: preoperation
 |  | ||||||
| +default:nsslapd-pluginEnabled: on
 |  | ||||||
| +default:uniqueness-attribute-name: krbPrincipalName:CaseIgnoreMatch:
 |  | ||||||
| +default:uniqueness-attribute-name: krbPrincipalAlias:CaseIgnoreMatch:
 |  | ||||||
| +default:uniqueness-attribute-name: krbCanonicalName:CaseIgnoreMatch:
 |  | ||||||
| +default:nsslapd-plugin-depends-on-type: database
 |  | ||||||
| +default:nsslapd-pluginId: NSUniqueAttr
 |  | ||||||
| +default:nsslapd-pluginVersion: 1.1.0
 |  | ||||||
| +default:nsslapd-pluginVendor: Fedora Project
 |  | ||||||
| +default:nsslapd-pluginDescription: Enforce unique attribute values
 |  | ||||||
| +default:uniqueness-subtrees: $SUFFIX
 |  | ||||||
| +default:uniqueness-exclude-subtrees: cn=staged users,cn=accounts,cn=provisioning,$SUFFIX
 |  | ||||||
| +default:uniqueness-across-all-subtrees: on
 |  | ||||||
|   |  | ||||||
|  # ipaUniqueID uniqueness scopes Active/Delete containers |  | ||||||
|  dn: cn=ipaUniqueID uniqueness,cn=plugins,cn=config |  | ||||||
| -- 
 |  | ||||||
| 2.50.1 |  | ||||||
| 
 |  | ||||||
| @ -1,230 +0,0 @@ | |||||||
| From 4784cb826f7cfd01471c29cfb51bdf6d34d6d643 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Julien Rische <jrische@redhat.com> |  | ||||||
| Date: Tue, 9 Sep 2025 12:45:24 -0300 |  | ||||||
| Subject: [PATCH] ipa-kdb: enforce PAC presence on TGT for TGS-REQ |  | ||||||
| 
 |  | ||||||
| MS-KILE's PA-PAC-REQUEST sequence allows the Kerberos client to request |  | ||||||
| a TGT without a PAC. At the moment, there is no way to configure the MIT |  | ||||||
| KDC to reject such request. |  | ||||||
| 
 |  | ||||||
| This commit enforces the presence of the PAC when processing TGTs |  | ||||||
| provided by TGS-REQ. It ensures the server principal of the TGT is the |  | ||||||
| same as the one in PAC_CLIENT_INFO (i.e. enforces client principal |  | ||||||
| canonicalization) with integrity check. |  | ||||||
| 
 |  | ||||||
| Only one exception is applied: this check is skipped for local TGTs on |  | ||||||
| domain where the MS-PAC generator is not initialized (i.e. domains where |  | ||||||
| SID generation was not executed yet). |  | ||||||
| 
 |  | ||||||
| Signed-off-by: Julien Rische <jrische@redhat.com> |  | ||||||
| ---
 |  | ||||||
|  daemons/ipa-kdb/ipa_kdb.h            |  9 +++ |  | ||||||
|  daemons/ipa-kdb/ipa_kdb_common.c     | 18 ++++++ |  | ||||||
|  daemons/ipa-kdb/ipa_kdb_kdcpolicy.c  |  2 +- |  | ||||||
|  daemons/ipa-kdb/ipa_kdb_mspac.c      | 87 ++++++++++++++++++++++++++++ |  | ||||||
|  daemons/ipa-kdb/ipa_kdb_principals.c | 21 +------ |  | ||||||
|  5 files changed, 116 insertions(+), 21 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h
 |  | ||||||
| index 85cabe142..7bad8c85f 100644
 |  | ||||||
| --- a/daemons/ipa-kdb/ipa_kdb.h
 |  | ||||||
| +++ b/daemons/ipa-kdb/ipa_kdb.h
 |  | ||||||
| @@ -434,6 +434,14 @@ ipadb_check_for_bronze_bit_attack(krb5_context context,
 |  | ||||||
|  #  endif |  | ||||||
|  #endif |  | ||||||
|   |  | ||||||
| +/* Check the ticket provided in a TGS-REQ. In some situations, the ticket is
 |  | ||||||
| + * expected to contain a PAC. If it is not the case, or if the function is
 |  | ||||||
| + * enable to decode an authorization-data element, it fails.
 |  | ||||||
| + * Any failure should result in the TGS-REQ to be rejected. */
 |  | ||||||
| +krb5_error_code ipadb_enforce_pac(krb5_context kcontext,
 |  | ||||||
| +                                  const krb5_ticket *ticket,
 |  | ||||||
| +                                  const char **status);
 |  | ||||||
| +
 |  | ||||||
|  /* DELEGATION CHECKS */ |  | ||||||
|   |  | ||||||
|  krb5_error_code ipadb_check_allowed_to_delegate(krb5_context kcontext, |  | ||||||
| @@ -472,3 +480,4 @@ int ipadb_string_to_sid(const char *str, struct dom_sid *sid);
 |  | ||||||
|  void alloc_sid(struct dom_sid **sid); |  | ||||||
|  void free_sid(struct dom_sid **sid); |  | ||||||
|  bool dom_sid_check(const struct dom_sid *sid1, const struct dom_sid *sid2, bool exact_check); |  | ||||||
| +bool ipadb_is_tgs_princ(krb5_context kcontext, krb5_const_principal princ);
 |  | ||||||
| diff --git a/daemons/ipa-kdb/ipa_kdb_common.c b/daemons/ipa-kdb/ipa_kdb_common.c
 |  | ||||||
| index 42e0856d0..eb0b0d129 100644
 |  | ||||||
| --- a/daemons/ipa-kdb/ipa_kdb_common.c
 |  | ||||||
| +++ b/daemons/ipa-kdb/ipa_kdb_common.c
 |  | ||||||
| @@ -704,3 +704,21 @@ krb5_error_code ipadb_multibase_search(struct ipadb_context *ipactx,
 |  | ||||||
|      return ipadb_simple_ldap_to_kerr(ret); |  | ||||||
|  } |  | ||||||
|   |  | ||||||
| +bool
 |  | ||||||
| +ipadb_is_tgs_princ(krb5_context kcontext, krb5_const_principal princ)
 |  | ||||||
| +{
 |  | ||||||
| +    krb5_data *primary;
 |  | ||||||
| +    size_t l_tgs_name;
 |  | ||||||
| +
 |  | ||||||
| +    if (2 != krb5_princ_size(kcontext, princ))
 |  | ||||||
| +        return false;
 |  | ||||||
| +
 |  | ||||||
| +    primary = krb5_princ_component(kcontext, princ, 0);
 |  | ||||||
| +
 |  | ||||||
| +    l_tgs_name = strlen(KRB5_TGS_NAME);
 |  | ||||||
| +
 |  | ||||||
| +    if (l_tgs_name != primary->length)
 |  | ||||||
| +        return false;
 |  | ||||||
| +
 |  | ||||||
| +    return 0 == memcmp(primary->data, KRB5_TGS_NAME, l_tgs_name);
 |  | ||||||
| +}
 |  | ||||||
| diff --git a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
 |  | ||||||
| index d6d618d1d..a92a9a0ad 100644
 |  | ||||||
| --- a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
 |  | ||||||
| +++ b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
 |  | ||||||
| @@ -207,7 +207,7 @@ ipa_kdcpolicy_check_tgs(krb5_context context, krb5_kdcpolicy_moddata moddata,
 |  | ||||||
|      *lifetime_out = 0; |  | ||||||
|      *renew_lifetime_out = 0; |  | ||||||
|   |  | ||||||
| -    return 0;
 |  | ||||||
| +    return ipadb_enforce_pac(context, ticket, status);
 |  | ||||||
|  } |  | ||||||
|   |  | ||||||
|  krb5_error_code kdcpolicy_ipakdb_initvt(krb5_context context, |  | ||||||
| diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
 |  | ||||||
| index 0964d112a..c4085fca5 100644
 |  | ||||||
| --- a/daemons/ipa-kdb/ipa_kdb_mspac.c
 |  | ||||||
| +++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
 |  | ||||||
| @@ -3344,6 +3344,93 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
 |  | ||||||
|  	return KRB5_KDB_NOENTRY; |  | ||||||
|  } |  | ||||||
|   |  | ||||||
| +static krb5_error_code
 |  | ||||||
| +check_for_pac(krb5_context kcontext, krb5_authdata **authdata, bool *pac_present)
 |  | ||||||
| +{
 |  | ||||||
| +    krb5_error_code kerr = ENOENT;
 |  | ||||||
| +    size_t i, j;
 |  | ||||||
| +    krb5_authdata **ifrel = NULL;
 |  | ||||||
| +
 |  | ||||||
| +    for (i = 0; authdata && authdata[i]; ++i) {
 |  | ||||||
| +        if (authdata[i]->ad_type != KRB5_AUTHDATA_IF_RELEVANT) {
 |  | ||||||
| +            continue;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        kerr = krb5_decode_authdata_container(kcontext,
 |  | ||||||
| +                                              KRB5_AUTHDATA_IF_RELEVANT,
 |  | ||||||
| +                                              authdata[i], &ifrel);
 |  | ||||||
| +        if (kerr) {
 |  | ||||||
| +            goto end;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        for (j = 0; ifrel[j]; ++j) {
 |  | ||||||
| +            if (ifrel[j]->ad_type == KRB5_AUTHDATA_WIN2K_PAC) {
 |  | ||||||
| +                break;
 |  | ||||||
| +            }
 |  | ||||||
| +        }
 |  | ||||||
| +        if (ifrel[j]) {
 |  | ||||||
| +            break;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        krb5_free_authdata(kcontext, ifrel);
 |  | ||||||
| +        ifrel = NULL;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    *pac_present = ifrel;
 |  | ||||||
| +    kerr = 0;
 |  | ||||||
| +
 |  | ||||||
| +end:
 |  | ||||||
| +    krb5_free_authdata(kcontext, ifrel);
 |  | ||||||
| +    return kerr;
 |  | ||||||
| +}
 |  | ||||||
| +
 |  | ||||||
| +krb5_error_code
 |  | ||||||
| +ipadb_enforce_pac(krb5_context kcontext, const krb5_ticket *ticket,
 |  | ||||||
| +                  const char **status)
 |  | ||||||
| +{
 |  | ||||||
| +    struct ipadb_context *ipactx;
 |  | ||||||
| +    bool pac_present;
 |  | ||||||
| +    krb5_error_code kerr;
 |  | ||||||
| +
 |  | ||||||
| +    /* Filter TGTs only */
 |  | ||||||
| +    if (!ipadb_is_tgs_princ(kcontext, ticket->server)) {
 |  | ||||||
| +        kerr = 0;
 |  | ||||||
| +        goto end;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    /* Get IPA context */
 |  | ||||||
| +    ipactx = ipadb_get_context(kcontext);
 |  | ||||||
| +    if (!ipactx) {
 |  | ||||||
| +        kerr = KRB5_KDB_DBNOTINITED;
 |  | ||||||
| +        goto end;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    /* If local TGT but PAC generator not initialized, skip PAC enforcement */
 |  | ||||||
| +    if (krb5_realm_compare(kcontext, ipactx->local_tgs, ticket->server) &&
 |  | ||||||
| +        !ipactx->mspac)
 |  | ||||||
| +    {
 |  | ||||||
| +        krb5_klog_syslog(LOG_WARNING, "MS-PAC not available. This makes "
 |  | ||||||
| +                         "FreeIPA vulnerable to privilege escalation exploit "
 |  | ||||||
| +                         "(CVE-2025-7493). Please generate SIDs to enable PAC "
 |  | ||||||
| +                         "support.");
 |  | ||||||
| +        kerr = 0;
 |  | ||||||
| +        goto end;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    /* Search for the PAC, fail if it cannot be found */
 |  | ||||||
| +    kerr = check_for_pac(kcontext, ticket->enc_part2->authorization_data,
 |  | ||||||
| +                         &pac_present);
 |  | ||||||
| +    if (kerr) {
 |  | ||||||
| +        *status = "PAC_ENFORCEMENT_CANNOT_DECODE_TGT_AUTHDATA";
 |  | ||||||
| +    } else if (!pac_present) {
 |  | ||||||
| +        kerr = ENOENT;
 |  | ||||||
| +        *status = "PAC_ENFORCEMENT_TGT_WITHOUT_PAC";
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +end:
 |  | ||||||
| +    return kerr;
 |  | ||||||
| +}
 |  | ||||||
| +
 |  | ||||||
|  #if KRB5_KDB_DAL_MAJOR_VERSION <= 8 |  | ||||||
|  #  ifdef HAVE_KRB5_PAC_FULL_SIGN_COMPAT |  | ||||||
|  krb5_error_code |  | ||||||
| diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c
 |  | ||||||
| index 6ee274053..11e084739 100644
 |  | ||||||
| --- a/daemons/ipa-kdb/ipa_kdb_principals.c
 |  | ||||||
| +++ b/daemons/ipa-kdb/ipa_kdb_principals.c
 |  | ||||||
| @@ -183,25 +183,6 @@ done:
 |  | ||||||
|      return ret; |  | ||||||
|  } |  | ||||||
|   |  | ||||||
| -static bool
 |  | ||||||
| -is_tgs_princ(krb5_context kcontext, krb5_const_principal princ)
 |  | ||||||
| -{
 |  | ||||||
| -    krb5_data *primary;
 |  | ||||||
| -    size_t l_tgs_name;
 |  | ||||||
| -
 |  | ||||||
| -    if (2 != krb5_princ_size(kcontext, princ))
 |  | ||||||
| -        return false;
 |  | ||||||
| -
 |  | ||||||
| -    primary = krb5_princ_component(kcontext, princ, 0);
 |  | ||||||
| -
 |  | ||||||
| -    l_tgs_name = strlen(KRB5_TGS_NAME);
 |  | ||||||
| -
 |  | ||||||
| -    if (l_tgs_name != primary->length)
 |  | ||||||
| -        return false;
 |  | ||||||
| -
 |  | ||||||
| -    return 0 == memcmp(primary->data, KRB5_TGS_NAME, l_tgs_name);
 |  | ||||||
| -}
 |  | ||||||
| -
 |  | ||||||
|  static krb5_error_code ipadb_set_tl_data(krb5_db_entry *entry, |  | ||||||
|                                           krb5_int16 type, |  | ||||||
|                                           krb5_ui_2 length, |  | ||||||
| @@ -1882,7 +1863,7 @@ krb5_error_code ipadb_get_principal(krb5_context kcontext,
 |  | ||||||
|   |  | ||||||
|  #if KRB5_KDB_DAL_MAJOR_VERSION <= 8 |  | ||||||
|      /* If TGS principal, some virtual attributes may be added */ |  | ||||||
| -    if (is_tgs_princ(kcontext, (*entry)->princ)) {
 |  | ||||||
| +    if (ipadb_is_tgs_princ(kcontext, (*entry)->princ)) {
 |  | ||||||
|          kerr = krb5_dbe_set_string(kcontext, *entry, |  | ||||||
|                                     KRB5_KDB_SK_OPTIONAL_AD_SIGNEDPATH, |  | ||||||
|                                     "true"); |  | ||||||
| -- 
 |  | ||||||
| 2.51.0 |  | ||||||
| 
 |  | ||||||
| @ -1,93 +0,0 @@ | |||||||
| From d57d11974e05f84c0964ca941a6b507419b02211 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| Date: Thu, 28 Aug 2025 15:31:39 +0200 |  | ||||||
| Subject: [PATCH] ipatests: extend test for unique krbcanonicalname |  | ||||||
| 
 |  | ||||||
| Add a test ensuring that root@REALM cannot be added as |  | ||||||
| krbcanonicalname |  | ||||||
| 
 |  | ||||||
| Add a test for PAC enforcement: |  | ||||||
| try to access a service using a TGT obtained without PAC. |  | ||||||
| Should fail as PAC is now enforced. |  | ||||||
| 
 |  | ||||||
| Signed-off-by: Florence Blanc-Renaud <flo@redhat.com> |  | ||||||
| ---
 |  | ||||||
|  ipatests/test_integration/test_commands.py | 44 ++++++++++++++++++++-- |  | ||||||
|  1 file changed, 40 insertions(+), 4 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
 |  | ||||||
| index 38202c9a3fbc5e91c03a5953a5d9bec3c07117f4..c982c678aae047d5cb505889729bcb5bccbc3c20 100644
 |  | ||||||
| --- a/ipatests/test_integration/test_commands.py
 |  | ||||||
| +++ b/ipatests/test_integration/test_commands.py
 |  | ||||||
| @@ -1563,7 +1563,7 @@ def test_unique_krbcanonicalname(self):
 |  | ||||||
|          hostname = master.hostname |  | ||||||
|          realm = master.domain.realm |  | ||||||
|          principal = f'test/{hostname}@{realm}' |  | ||||||
| -        entry_ldif = textwrap.dedent("""
 |  | ||||||
| +        entry_ldif_template = textwrap.dedent("""
 |  | ||||||
|              dn: krbprincipalname={principal},cn=services,cn=accounts,{base_dn} |  | ||||||
|              changetype: add |  | ||||||
|              ipakrbprincipalalias: test/{hostname}@{realm} |  | ||||||
| @@ -1573,13 +1573,15 @@ def test_unique_krbcanonicalname(self):
 |  | ||||||
|              objectclass: krbprincipal |  | ||||||
|              objectclass: krbprincipalaux |  | ||||||
|              objectclass: top |  | ||||||
| -            krbcanonicalname: admin@{realm}
 |  | ||||||
| +            krbcanonicalname: {user}@{realm}
 |  | ||||||
|              managedby: fqdn={hostname},cn=computers,cn=accounts,{base_dn} |  | ||||||
| -        """).format(
 |  | ||||||
| +        """)
 |  | ||||||
| +        entry_ldif = entry_ldif_template.format(
 |  | ||||||
|              base_dn=base_dn, |  | ||||||
|              hostname=hostname, |  | ||||||
|              principal=principal, |  | ||||||
| -            realm=realm)
 |  | ||||||
| +            realm=realm,
 |  | ||||||
| +            user='admin')
 |  | ||||||
|          tasks.kdestroy_all(master) |  | ||||||
|          master.run_command( |  | ||||||
|              ['kinit', '-kt', '/etc/krb5.keytab', f'host/{hostname}@{realm}']) |  | ||||||
| @@ -1592,6 +1594,40 @@ def test_unique_krbcanonicalname(self):
 |  | ||||||
|                                      raiseonerr=False) |  | ||||||
|          assert "entry with the same attribute value" in result.stderr_text |  | ||||||
| 
 |  | ||||||
| +        # Now try with root@realm instead of admin@realm
 |  | ||||||
| +        entry_ldif = entry_ldif_template.format(
 |  | ||||||
| +            base_dn=base_dn,
 |  | ||||||
| +            hostname=hostname,
 |  | ||||||
| +            principal=principal,
 |  | ||||||
| +            realm=realm,
 |  | ||||||
| +            user='root')
 |  | ||||||
| +        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
 |  | ||||||
| +        tasks.kdestroy_all(master)
 |  | ||||||
| +
 |  | ||||||
| +    def test_no_request_pac(self):
 |  | ||||||
| +        # Try to use a TGT obtained without PAC
 |  | ||||||
| +        # Should fail as the presence of the PAC when processing TGTs
 |  | ||||||
| +        # provided by TGS-REQ is now enforced.
 |  | ||||||
| +        hostname = self.master.hostname
 |  | ||||||
| +        realm = self.master.domain.realm
 |  | ||||||
| +        self.master.run_command([
 |  | ||||||
| +            'kinit', '-kt', '/etc/krb5.keytab', f'host/{hostname}@{realm}',
 |  | ||||||
| +            '--no-request-pac'
 |  | ||||||
| +        ])
 |  | ||||||
| +        result = self.master.run_command(
 |  | ||||||
| +            ['kvno', f'ldap/{hostname}@{realm}'],
 |  | ||||||
| +            raiseonerr=False
 |  | ||||||
| +        )
 |  | ||||||
| +        assert result.returncode == 1
 |  | ||||||
| +        assert "PAC_ENFORCEMENT_TGT_WITHOUT_PAC" in result.stderr_text
 |  | ||||||
| +
 |  | ||||||
| 
 |  | ||||||
|  class TestIPACommandWithoutReplica(IntegrationTest): |  | ||||||
|      """ |  | ||||||
| --
 |  | ||||||
| 2.51.0 |  | ||||||
| 
 |  | ||||||
| @ -1,173 +0,0 @@ | |||||||
| --- a/ipatests/test_integration/test_commands.py	2025-09-17 10:36:00.180673487 -0300
 |  | ||||||
| +++ b/ipatests/test_integration/test_commands.py	2025-09-17 10:37:31.294681273 -0300
 |  | ||||||
| @@ -1554,80 +1554,6 @@
 |  | ||||||
|          assert result.returncode == 1 |  | ||||||
|          assert 'cannot be deleted or disabled' in result.stderr_text |  | ||||||
| 
 |  | ||||||
| -    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_template = 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: {user}@{realm}
 |  | ||||||
| -            managedby: fqdn={hostname},cn=computers,cn=accounts,{base_dn}
 |  | ||||||
| -        """)
 |  | ||||||
| -        entry_ldif = entry_ldif_template.format(
 |  | ||||||
| -            base_dn=base_dn,
 |  | ||||||
| -            hostname=hostname,
 |  | ||||||
| -            principal=principal,
 |  | ||||||
| -            realm=realm,
 |  | ||||||
| -            user='admin')
 |  | ||||||
| -        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
 |  | ||||||
| -
 |  | ||||||
| -        # Now try with root@realm instead of admin@realm
 |  | ||||||
| -        entry_ldif = entry_ldif_template.format(
 |  | ||||||
| -            base_dn=base_dn,
 |  | ||||||
| -            hostname=hostname,
 |  | ||||||
| -            principal=principal,
 |  | ||||||
| -            realm=realm,
 |  | ||||||
| -            user='root')
 |  | ||||||
| -        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
 |  | ||||||
| -        tasks.kdestroy_all(master)
 |  | ||||||
| -
 |  | ||||||
| -    def test_no_request_pac(self):
 |  | ||||||
| -        # Try to use a TGT obtained without PAC
 |  | ||||||
| -        # Should fail as the presence of the PAC when processing TGTs
 |  | ||||||
| -        # provided by TGS-REQ is now enforced.
 |  | ||||||
| -        hostname = self.master.hostname
 |  | ||||||
| -        realm = self.master.domain.realm
 |  | ||||||
| -        self.master.run_command([
 |  | ||||||
| -            'kinit', '-kt', '/etc/krb5.keytab', f'host/{hostname}@{realm}',
 |  | ||||||
| -            '--no-request-pac'
 |  | ||||||
| -        ])
 |  | ||||||
| -        result = self.master.run_command(
 |  | ||||||
| -            ['kvno', f'ldap/{hostname}@{realm}'],
 |  | ||||||
| -            raiseonerr=False
 |  | ||||||
| -        )
 |  | ||||||
| -        assert result.returncode == 1
 |  | ||||||
| -        assert "PAC_ENFORCEMENT_TGT_WITHOUT_PAC" in result.stderr_text
 |  | ||||||
| -
 |  | ||||||
| 
 |  | ||||||
|  class TestIPACommandWithoutReplica(IntegrationTest): |  | ||||||
|      """ |  | ||||||
| @@ -1749,7 +1675,7 @@
 |  | ||||||
|              api.bootstrap_with_global_options(context='server') |  | ||||||
|              api.finalize() |  | ||||||
|              api.Backend.ldap2.connect() |  | ||||||
| -            
 |  | ||||||
| +
 |  | ||||||
|              api.Command["group_add"]("testgroup1", external=True) |  | ||||||
|              api.Command["group_add"]("testgroup2", external=False) |  | ||||||
|              result1 = api.Command["group_show"]("testgroup1", all=True)["result"] # noqa: E501 |  | ||||||
| @@ -1794,6 +1720,80 @@
 |  | ||||||
|                                            '/tmp/reproducer2_code.py']) |  | ||||||
|          assert "missing attribute" not in result.stdout_text |  | ||||||
| 
 |  | ||||||
| +    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_template = 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: {user}@{realm}
 |  | ||||||
| +            managedby: fqdn={hostname},cn=computers,cn=accounts,{base_dn}
 |  | ||||||
| +        """)
 |  | ||||||
| +        entry_ldif = entry_ldif_template.format(
 |  | ||||||
| +            base_dn=base_dn,
 |  | ||||||
| +            hostname=hostname,
 |  | ||||||
| +            principal=principal,
 |  | ||||||
| +            realm=realm,
 |  | ||||||
| +            user='admin')
 |  | ||||||
| +        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
 |  | ||||||
| +
 |  | ||||||
| +        # Now try with root@realm instead of admin@realm
 |  | ||||||
| +        entry_ldif = entry_ldif_template.format(
 |  | ||||||
| +            base_dn=base_dn,
 |  | ||||||
| +            hostname=hostname,
 |  | ||||||
| +            principal=principal,
 |  | ||||||
| +            realm=realm,
 |  | ||||||
| +            user='root')
 |  | ||||||
| +        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
 |  | ||||||
| +        tasks.kdestroy_all(master)
 |  | ||||||
| +
 |  | ||||||
| +    def test_no_request_pac(self):
 |  | ||||||
| +        # Try to use a TGT obtained without PAC
 |  | ||||||
| +        # Should fail as the presence of the PAC when processing TGTs
 |  | ||||||
| +        # provided by TGS-REQ is now enforced.
 |  | ||||||
| +        hostname = self.master.hostname
 |  | ||||||
| +        realm = self.master.domain.realm
 |  | ||||||
| +        self.master.run_command([
 |  | ||||||
| +            'kinit', '-kt', '/etc/krb5.keytab', f'host/{hostname}@{realm}',
 |  | ||||||
| +            '--no-request-pac'
 |  | ||||||
| +        ])
 |  | ||||||
| +        result = self.master.run_command(
 |  | ||||||
| +            ['kvno', f'ldap/{hostname}@{realm}'],
 |  | ||||||
| +            raiseonerr=False
 |  | ||||||
| +        )
 |  | ||||||
| +        assert result.returncode == 1
 |  | ||||||
| +        assert "PAC_ENFORCEMENT_TGT_WITHOUT_PAC" in result.stderr_text
 |  | ||||||
| +
 |  | ||||||
| 
 |  | ||||||
|  class TestIPAautomount(IntegrationTest): |  | ||||||
|      @classmethod |  | ||||||
| @ -32,7 +32,7 @@ diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py | |||||||
| index 7d21367ec..42a47f1df 100644
 | index 7d21367ec..42a47f1df 100644
 | ||||||
| --- a/ipaplatform/base/paths.py
 | --- a/ipaplatform/base/paths.py
 | ||||||
| +++ b/ipaplatform/base/paths.py
 | +++ b/ipaplatform/base/paths.py
 | ||||||
| @@ -258,8 +258,7 @@ class BasePathNamespace:
 | @@ -259,7 +259,6 @@ class BasePathNamespace:
 | ||||||
|      IPA_PKI_RETRIEVE_KEY = "/usr/libexec/ipa/ipa-pki-retrieve-key" |      IPA_PKI_RETRIEVE_KEY = "/usr/libexec/ipa/ipa-pki-retrieve-key" | ||||||
|      IPA_HTTPD_PASSWD_READER = "/usr/libexec/ipa/ipa-httpd-pwdreader" |      IPA_HTTPD_PASSWD_READER = "/usr/libexec/ipa/ipa-httpd-pwdreader" | ||||||
|      IPA_PKI_WAIT_RUNNING = "/usr/libexec/ipa/ipa-pki-wait-running" |      IPA_PKI_WAIT_RUNNING = "/usr/libexec/ipa/ipa-pki-wait-running" | ||||||
| @ -41,7 +41,6 @@ index 7d21367ec..42a47f1df 100644 | |||||||
| -    DNSSEC_KEYFROMLABEL_9_17 = "/usr/bin/dnssec-keyfromlabel"
 | -    DNSSEC_KEYFROMLABEL_9_17 = "/usr/bin/dnssec-keyfromlabel"
 | ||||||
|      GETSEBOOL = "/usr/sbin/getsebool" |      GETSEBOOL = "/usr/sbin/getsebool" | ||||||
|      GROUPADD = "/usr/sbin/groupadd" |      GROUPADD = "/usr/sbin/groupadd" | ||||||
|      USERMOD = "/usr/sbin/usermod" |  | ||||||
| diff --git a/ipaplatform/fedora/paths.py b/ipaplatform/fedora/paths.py
 | diff --git a/ipaplatform/fedora/paths.py b/ipaplatform/fedora/paths.py
 | ||||||
| index 4e993c063..92a948966 100644
 | index 4e993c063..92a948966 100644
 | ||||||
| --- a/ipaplatform/fedora/paths.py
 | --- a/ipaplatform/fedora/paths.py
 | ||||||
|  | |||||||
							
								
								
									
										119
									
								
								SPECS/ipa.spec
									
									
									
									
									
								
							
							
						
						
									
										119
									
								
								SPECS/ipa.spec
									
									
									
									
									
								
							| @ -74,15 +74,14 @@ | |||||||
| %global python_ldap_version 3.1.0-1 | %global python_ldap_version 3.1.0-1 | ||||||
| %if 0%{?rhel} < 9 | %if 0%{?rhel} < 9 | ||||||
| # Bug 1929067 - PKI instance creation failed with new 389-ds-base build | # Bug 1929067 - PKI instance creation failed with new 389-ds-base build | ||||||
| %global ds_version 1.4.3.39-15 | %global ds_version 1.4.3.16-12 | ||||||
| %else | %else | ||||||
| %global ds_version 2.0.3-3 | %global ds_version 2.0.3-3 | ||||||
| %endif | %endif | ||||||
| 
 | 
 | ||||||
| # Fix for TLS 1.3 PHA, RHBZ#1775158 | # Fix for TLS 1.3 PHA, RHBZ#1775158 | ||||||
| %global httpd_version 2.4.37-21 | %global httpd_version 2.4.37-21 | ||||||
| # Fix for RHEL-25649 | %global bind_version 9.11.20-6 | ||||||
| %global bind_version 9.11.36-14 |  | ||||||
| 
 | 
 | ||||||
| %else | %else | ||||||
| # Fedora | # Fedora | ||||||
| @ -190,7 +189,7 @@ | |||||||
| 
 | 
 | ||||||
| Name:           %{package_name} | Name:           %{package_name} | ||||||
| Version:        %{IPA_VERSION} | Version:        %{IPA_VERSION} | ||||||
| Release:        20%{?rc_version:.%rc_version}%{?dist} | Release:        7%{?rc_version:.%rc_version}%{?dist} | ||||||
| Summary:        The Identity, Policy and Audit system | Summary:        The Identity, Policy and Audit system | ||||||
| 
 | 
 | ||||||
| License:        GPLv3+ | License:        GPLv3+ | ||||||
| @ -231,28 +230,6 @@ Patch0019:      0019-Vault-add-support-for-RSA-OAEP-wrapping-algo.patch | |||||||
| Patch0020:      0020-Vault-improve-vault-server-archival-retrieval-calls-.patch | Patch0020:      0020-Vault-improve-vault-server-archival-retrieval-calls-.patch | ||||||
| Patch0021:      0021-kra-set-RSA-OAEP-as-default-wrapping-algo-when-FIPS-.patch | Patch0021:      0021-kra-set-RSA-OAEP-as-default-wrapping-algo-when-FIPS-.patch | ||||||
| Patch0022:      0022-ipa-kdb-Fix-double-free-in-ipadb_reinit_mspac.patch | Patch0022:      0022-ipa-kdb-Fix-double-free-in-ipadb_reinit_mspac.patch | ||||||
| Patch0023:      0023-rpcserver-validate-Kerberos-principal-name-before-running-kinit_rhel#26153.patch |  | ||||||
| Patch0024:      0024-Vault-add-additional-fallback-to-RSA-OAEP-wrapping-algo_rhel#28259.patch |  | ||||||
| Patch0025:      0025-dcerpc-invalidate-forest-trust-intfo-cache-when-filtering-out-realm-domains_rhel#28559.patch |  | ||||||
| Patch0026:      0026-backport-test-fixes_rhel#29908.patch |  | ||||||
| Patch0027:      0027-kdb-fix-vulnerability-in-GCD-rules-handling.patch |  | ||||||
| Patch0028:      0028-kdb-apply-combinatorial-logic-for-ticket-flags.patch |  | ||||||
| Patch0029:      0029-Allow_the_admin_user_to_be_disabled_rhel#34756.patch |  | ||||||
| Patch0030:      0030-ipa-otptoken-import-open-the-key-file-in-binary-mode_rhel#39616.patch |  | ||||||
| Patch0031:      0031-ipa-crlgen-manage-manage-the-cert-status-task-execution-time_rhel#30280.patch |  | ||||||
| Patch0032:      0032-idrange-add-add-a-warning-because-389ds-restart-is-required_rhel#28996.patch |  | ||||||
| Patch0033:      0033-PKINIT-certificate-fix-renewal-on-hidden-replica_rhel#4913.patch |  | ||||||
| Patch0034:      0034-Add-ipa-idrange-fix_rhel#56920.patch |  | ||||||
| Patch0035:      0035-Unconditionally-add-MS-PAC-to-global-config-on-update_rhel#49437.patch |  | ||||||
| Patch0036:      0036-ipatests-Update-ipa-adtrust-install-test_rhel#40894.patch |  | ||||||
| Patch0037:      0037-Replica-CA-installation-ignore-skew-during-initial-replication_rhel#80995.patch |  | ||||||
| Patch0038:      0038-Add-a-check-into-ipa-cert-fix-tool-to-avoid-updating-certs-if-CA-is-close-to-being-expired_rhel#4941.patch |  | ||||||
| Patch0039:      0039-kdb-keeep-ipadb_get_connection-from-succeding-with-null-LDAP-context_rhel#58435.patch |  | ||||||
| Patch0040:      0040-Set-krbCanonicalName-admin-REALM-on-the-admin-user_rhel#89895.patch |  | ||||||
| Patch0041:      0041-Enforce-uniqueness-across-krbprincipalname-and-krbca_rhel#110061.patch |  | ||||||
| Patch0042:      0042-ipa-kdb-enforce-PAC-presence-on-TGT-for-TGS-REQ_rhel#110061.patch |  | ||||||
| Patch0043:      0043-ipatests-extend-test-for-unique-krbcanonicalname_rhel#110061.patch |  | ||||||
| Patch0044:      0044-ipatests-refactor-krb-unique-tests_rhel#110061.patch |  | ||||||
| %if 0%{?rhel} >= 8 | %if 0%{?rhel} >= 8 | ||||||
| Patch1001:      1001-Change-branding-to-IPA-and-Identity-Management.patch | Patch1001:      1001-Change-branding-to-IPA-and-Identity-Management.patch | ||||||
| Patch1002:      1002-Revert-freeipa.spec-depend-on-bind-dnssec-utils.patch | Patch1002:      1002-Revert-freeipa.spec-depend-on-bind-dnssec-utils.patch | ||||||
| @ -413,7 +390,7 @@ BuildRequires:  python3-pycodestyle | |||||||
| BuildRequires:  python3-pylint | BuildRequires:  python3-pylint | ||||||
| BuildRequires:  python3-pytest-multihost | BuildRequires:  python3-pytest-multihost | ||||||
| BuildRequires:  python3-pytest-sourceorder | BuildRequires:  python3-pytest-sourceorder | ||||||
| BuildRequires:  python3-qrcode-core >= 5.3 | BuildRequires:  python3-qrcode-core >= 5.0.0 | ||||||
| BuildRequires:  python3-samba | BuildRequires:  python3-samba | ||||||
| BuildRequires:  python3-six | BuildRequires:  python3-six | ||||||
| BuildRequires:  python3-sss | BuildRequires:  python3-sss | ||||||
| @ -1016,7 +993,10 @@ for i in *.po ; do | |||||||
| done | done | ||||||
| popd | popd | ||||||
| 
 | 
 | ||||||
| %autopatch -p1 | for p in %patches ; do | ||||||
|  |     %__patch -p1 -i $p | ||||||
|  |     UpdateTimestamps -p1 $p | ||||||
|  | done | ||||||
| 
 | 
 | ||||||
| %build | %build | ||||||
| # PATH is workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1005235 | # PATH is workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1005235 | ||||||
| @ -1411,7 +1391,6 @@ fi | |||||||
| %{_sbindir}/ipa-pkinit-manage | %{_sbindir}/ipa-pkinit-manage | ||||||
| %{_sbindir}/ipa-crlgen-manage | %{_sbindir}/ipa-crlgen-manage | ||||||
| %{_sbindir}/ipa-cert-fix | %{_sbindir}/ipa-cert-fix | ||||||
| %{_sbindir}/ipa-idrange-fix |  | ||||||
| %{_sbindir}/ipa-acme-manage | %{_sbindir}/ipa-acme-manage | ||||||
| %{_libexecdir}/certmonger/dogtag-ipa-ca-renew-agent-submit | %{_libexecdir}/certmonger/dogtag-ipa-ca-renew-agent-submit | ||||||
| %{_libexecdir}/certmonger/ipa-server-guard | %{_libexecdir}/certmonger/ipa-server-guard | ||||||
| @ -1486,7 +1465,6 @@ fi | |||||||
| %{_mandir}/man1/ipa-pkinit-manage.1* | %{_mandir}/man1/ipa-pkinit-manage.1* | ||||||
| %{_mandir}/man1/ipa-crlgen-manage.1* | %{_mandir}/man1/ipa-crlgen-manage.1* | ||||||
| %{_mandir}/man1/ipa-cert-fix.1* | %{_mandir}/man1/ipa-cert-fix.1* | ||||||
| %{_mandir}/man1/ipa-idrange-fix.1* |  | ||||||
| %{_mandir}/man1/ipa-acme-manage.1* | %{_mandir}/man1/ipa-acme-manage.1* | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -1767,87 +1745,6 @@ fi | |||||||
| %endif | %endif | ||||||
| 
 | 
 | ||||||
| %changelog | %changelog | ||||||
| * Thu Sep 11 2025 Rafael Jeffman <rjeffman@redhat.com> - 4.9.13-20 |  | ||||||
| - Refactor ipatests for unique krbcanonicalname |  | ||||||
|   Resolves: RHEL-110061 |  | ||||||
| 
 |  | ||||||
| * Thu Sep 11 2025 Rafael Jeffman <rjeffman@redhat.com> - 4.9.13-19 |  | ||||||
| - Enforce uniqueness across krbprincipalname and krbcanonicalname |  | ||||||
|   ipa-kdb: enforce PAC presence on TGT for TGS-REQ |  | ||||||
|   ipatests: extend test for unique krbcanonicalname |  | ||||||
|   Resolves: RHEL-110061 |  | ||||||
| 
 |  | ||||||
| * Tue Jun 03 2025 Rafael Jeffman <rjeffman@redhat.com> - 4.9.13-18 |  | ||||||
| - Set krbCanonicalName admin@REALM on the admin user |  | ||||||
|   Resolves: RHEL-89895 |  | ||||||
| 
 |  | ||||||
| * Mon May 19 2025 Rafael Jeffman <rjeffman@redhat.com> - 4.9.13-17 |  | ||||||
| - kdb: keeep ipadb_get_connection() from succeding with null LDAP context |  | ||||||
|   Resolves: RHEL-58453 |  | ||||||
| 
 |  | ||||||
| * Mon Mar 31 2025 Rafael Jeffman <rjeffman@redhat.com> - 4.9.13-16 |  | ||||||
| - Add a- heck into ipa-cert-fix tool to avoid updating certs if CA is close to expire |  | ||||||
|   Resolves: RHEL-4941 |  | ||||||
| - Fix rpminspect's 'patches' warnings |  | ||||||
|   Resolves: RHEL-22497 |  | ||||||
| 
 |  | ||||||
| * Mon Mar 10 2025 Rafael Jeffman <rjeffman@redhat.com> - 4.9.13-15 |  | ||||||
| - Replica CA installation: ignore skew during initial replication |  | ||||||
|   Resolves RHEL-80995 |  | ||||||
| 
 |  | ||||||
| * Wed Nov 27 2024 Rafael Jeffman <rjeffman@redhat.com> - 4.9.13-14 |  | ||||||
| - ipatests: Update ipa-adtrust-install test |  | ||||||
|   Resolves: RHEL-40894 |  | ||||||
| 
 |  | ||||||
| * Thu Nov 14 2024 Rafael Jeffman <rjeffman@redhat.com> - 4.9.13-13 |  | ||||||
| - Add ipa-idrange-fix |  | ||||||
|   Resolves: RHEL-56920 |  | ||||||
| - Unconditionally add MS-PAC to global config on update |  | ||||||
|   Resolves: RHEL-49437 |  | ||||||
| - ipatests: Update ipa-adtrust-install test |  | ||||||
|   Resolves: RHEL-40894 |  | ||||||
| - Require python-qrcode version 5.3 or later |  | ||||||
|   Related: RHEL-15090 |  | ||||||
| 
 |  | ||||||
| * Wed Jul 17 2024 Rafael Jeffman <rjeffman@redhat.com> - 4.9.13-12 |  | ||||||
| - Allow the admin user to be disabled |  | ||||||
|   Resolves: RHEL-34756 |  | ||||||
| - ipa-otptoken-import: open the key file in binary mode |  | ||||||
|   Resolves: RHEL-39616 |  | ||||||
| - ipa-crlgen-manage: manage the cert status task execution time |  | ||||||
|   Resolves: RHEL-30280 |  | ||||||
| - idrange-add: add a warning because 389ds restart is required |  | ||||||
|   Resolves: RHEL-28996 |  | ||||||
| - PKINIT certificate: fix renewal on hidden replica |  | ||||||
|   Resolves: RHEL-4913, RHEL-45908 |  | ||||||
| 
 |  | ||||||
| * Wed Jun 12 2024 Julien Rische <jrische@redhat.com> - 4.9.13-11 |  | ||||||
| - Add missing part of backported CVE-2024-3183 fix |  | ||||||
|   Resolves: RHEL-29927 |  | ||||||
| 
 |  | ||||||
| * Tue Apr 30 2024 Julien Rische <jrische@redhat.com> - 4.9.13-10 |  | ||||||
| - kdb: apply combinatorial logic for ticket flags (CVE-2024-3183) |  | ||||||
|   Resolves: RHEL-29927 |  | ||||||
| - kdb: fix vulnerability in GCD rules handling (CVE-2024-2698) |  | ||||||
|   Resolves: RHEL-29692 |  | ||||||
| 
 |  | ||||||
| * Fri Apr 12 2024 Rafael Jeffman <rjeffman@redhat.com> - 9.4.13-9 |  | ||||||
| - dcerpc: invalidate forest trust intfo cache when filtering out realm domains |  | ||||||
|   Resolves: RHEL-28559 |  | ||||||
| - Backport latests test fixes in python3-tests |  | ||||||
|   ipatests: add xfail for autoprivate group test with override |  | ||||||
|   ipatests: remove xfail thanks to sssd 2.9.4 |  | ||||||
|   ipatests: adapt for new automembership fixup behavior |  | ||||||
|   ipatests: Fixes for test_ipahealthcheck_ipansschainvalidation testcases |  | ||||||
|   test_xmlrpc: adopt to automember plugin message changes in 389-ds |  | ||||||
|   Resolves: RHEL-29908 |  | ||||||
| 
 |  | ||||||
| * Thu Mar 07 2024 Rafael Jeffman <rjeffman@redhat.com> - 4.9.13-8 |  | ||||||
| - rpcserver: validate Kerberos principal name before running kinit |  | ||||||
|   Resolves: RHEL-26153 |  | ||||||
| - Vault: add additional fallback to RSA-OAEP wrapping algo |  | ||||||
|   Resolves: RHEL-28259 |  | ||||||
| 
 |  | ||||||
| * Tue Feb 20 2024 Julien Rische <jrische@redhat.com> - 4.9.13-7 | * Tue Feb 20 2024 Julien Rische <jrische@redhat.com> - 4.9.13-7 | ||||||
| - ipa-kdb: Fix double free in ipadb_reinit_mspac() | - ipa-kdb: Fix double free in ipadb_reinit_mspac() | ||||||
|   Resolves: RHEL-25742 |   Resolves: RHEL-25742 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user