Compare commits

..

No commits in common. "c8-stream-DL1" and "c9-beta" have entirely different histories.

72 changed files with 8989 additions and 15623 deletions

2
.gitignore vendored
View File

@ -1 +1 @@
SOURCES/freeipa-4.9.13.tar.gz
SOURCES/freeipa-4.13.1.tar.gz

View File

@ -1 +1 @@
da1bb0220894d8dc06afb98dcf087fea38076a79 SOURCES/freeipa-4.9.13.tar.gz
bc82618e66b45376464cd2206bf6e7a02f766a11 SOURCES/freeipa-4.13.1.tar.gz

View File

@ -1,73 +0,0 @@
From 06b4c61b4484efe2093501caf21b03f1fc14093b Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
Date: Thu, 19 Oct 2023 12:47:03 +0200
Subject: [PATCH] group-add-member fails with an external member
The command ipa group-add-member --external aduser@addomain.test
fails with an internal error when used with samba 4.19.
The command internally calls samba.security.dom_sid(sid) which
used to raise a TypeError but now raises a ValueError
(commit 9abdd67 on https://github.com/samba-team/samba).
IPA source code needs to handle properly both exception types.
Fixes: https://pagure.io/freeipa/issue/9466
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
---
ipaserver/dcerpc.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index c1db2f9a499..ee0a229d1f0 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -303,7 +303,7 @@ def get_domain_by_sid(self, sid, exact_match=False):
# Parse sid string to see if it is really in a SID format
try:
test_sid = security.dom_sid(sid)
- except TypeError:
+ except (TypeError, ValueError):
raise errors.ValidationError(name='sid',
error=_('SID is not valid'))
From aa3397378acf1a03fc8bbe34b9fae33e84588b34 Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
Date: Fri, 20 Oct 2023 10:20:57 +0200
Subject: [PATCH] Handle samba changes in samba.security.dom_sid()
samba.security.dom_sid() in 4.19 now raises ValueError instead of
TypeError. Fix the expected exception.
Related: https://pagure.io/freeipa/issue/9466
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
ipaserver/dcerpc.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index ee0a229d1f0..3e4c71d9976 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -97,7 +97,7 @@
def is_sid_valid(sid):
try:
security.dom_sid(sid)
- except TypeError:
+ except (TypeError, ValueError):
return False
else:
return True
@@ -457,7 +457,7 @@ def get_trusted_domain_object_sid(self, object_name,
try:
test_sid = security.dom_sid(sid)
return unicode(test_sid)
- except TypeError:
+ except (TypeError, ValueError):
raise errors.ValidationError(name=_('trusted domain object'),
error=_('Trusted domain did not '
'return a valid SID for '

View File

@ -0,0 +1,238 @@
From dfeb01d5e4e8934bae8f495bbbd5b6570f3dc862 Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
Date: Tue, 4 Jun 2024 13:56:56 +0200
Subject: [PATCH] Revert "Replace netifaces with ifaddr"
This reverts commit 6c6b9354b5f970983655ca5423c726763d9015fa.
---
doc/requirements.txt | 1 -
freeipa.spec.in | 4 +--
ipaclient/install/client.py | 43 ++++++++++++++-----------------
ipapython/ipautil.py | 51 ++++++++++++++++++++-----------------
ipapython/setup.py | 2 +-
ipasetup.py.in | 2 +-
pylintrc | 1 +
7 files changed, 52 insertions(+), 52 deletions(-)
diff --git a/doc/requirements.txt b/doc/requirements.txt
index 8d91b893fbcb5de784f5abdf802b976c6b2ae277..cdaa42658c73e495379d87b7c50195fbd79533ee 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -11,7 +11,6 @@ m2r2
## ipa dependencies
dnspython
jwcrypto
-ifaddr
netaddr
qrcode
six
diff --git a/freeipa.spec.in b/freeipa.spec.in
index 6803de752bc122bf6e1eafd610d399cde994cad5..a532fe85c34a523519187b6fd1297c198d9f4a4e 100755
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -403,7 +403,7 @@ BuildRequires: python3-libipa_hbac
BuildRequires: python3-libsss_nss_idmap
BuildRequires: python3-lxml
BuildRequires: python3-netaddr >= %{python_netaddr_version}
-BuildRequires: python3-ifaddr
+BuildRequires: python3-netifaces
BuildRequires: python3-pki >= %{pki_version}
BuildRequires: python3-polib
BuildRequires: python3-pyasn1
@@ -885,7 +885,7 @@ Requires: python3-gssapi >= 1.2.0
Requires: python3-jwcrypto >= 0.4.2
Requires: python3-libipa_hbac
Requires: python3-netaddr >= %{python_netaddr_version}
-Requires: python3-ifaddr
+Requires: python3-netifaces >= 0.10.4
Requires: python3-packaging
Requires: python3-pyasn1 >= 0.3.2-2
Requires: python3-pyasn1-modules >= 0.3.2-2
diff --git a/ipaclient/install/client.py b/ipaclient/install/client.py
index 29aff5f413e7f27136e236382031171f068284c5..263dc8e302cccd76412c294e0740af9744a2c62d 100644
--- a/ipaclient/install/client.py
+++ b/ipaclient/install/client.py
@@ -18,7 +18,7 @@ import logging
import dns
import getpass
import gssapi
-import ifaddr
+import netifaces
import os
import re
import SSSDConfig
@@ -1374,36 +1374,31 @@ def unconfigure_nisdomain(statestore):
def get_iface_from_ip(ip_addr):
- for adapter in ifaddr.get_adapters():
- for ips in adapter.ips:
- # IPv6 is reported as a tuple, IPv4 is reported as str
- if ip_addr in (ips.ip[0], ips.ip):
- return adapter.name
+ for interface in netifaces.interfaces():
+ if_addrs = netifaces.ifaddresses(interface)
+ for family in [netifaces.AF_INET, netifaces.AF_INET6]:
+ for ip in if_addrs.get(family, []):
+ if ip['addr'] == ip_addr:
+ return interface
raise RuntimeError("IP %s not assigned to any interface." % ip_addr)
-def __get_ifaddr_adapters(iface=None):
+def get_local_ipaddresses(iface=None):
if iface:
- interfaces = set(iface if isinstance(iface, (list, tuple)) else [iface])
+ interfaces = [iface]
else:
- interfaces = set(adapter.name for adapter in ifaddr.get_adapters())
- return [
- adapter
- for adapter in ifaddr.get_adapters()
- if adapter.name in interfaces or adapter.nice_name in interfaces
- ]
+ interfaces = netifaces.interfaces()
-
-def get_local_ipaddresses(iface=None):
ips = []
- for adapter in __get_ifaddr_adapters(iface):
- for ifip in adapter.ips:
- try:
- ip_addr = ifip.ip[0] if isinstance(ifip.ip, tuple) else ifip.ip
- ips.append(ipautil.CheckedIPAddress(ip_addr))
- logger.debug('IP check successful: %s', ip_addr)
- except ValueError as e:
- logger.debug('IP check failed: %s', e)
+ for interface in interfaces:
+ if_addrs = netifaces.ifaddresses(interface)
+ for family in [netifaces.AF_INET, netifaces.AF_INET6]:
+ for ip in if_addrs.get(family, []):
+ try:
+ ips.append(ipautil.CheckedIPAddress(ip['addr']))
+ logger.debug('IP check successful: %s', ip['addr'])
+ except ValueError as e:
+ logger.debug('IP check failed: %s', e)
return ips
diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index b02d58839ed74215d7253fc23be94846d689f91e..6802dde574d179b2d4bf868a2c38546cc01fc46b 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -48,9 +48,9 @@ import six
from six.moves import input
try:
- import ifaddr
+ import netifaces
except ImportError:
- ifaddr = None
+ netifaces = None
from ipapython.dn import DN
from ipaplatform.paths import paths
@@ -203,37 +203,42 @@ class CheckedIPAddress(UnsafeIPAddress):
:return: InterfaceDetails named tuple or None if no interface has
this address
"""
- if ifaddr is None:
- raise ImportError("ifaddr")
+ if netifaces is None:
+ raise ImportError("netifaces")
logger.debug("Searching for an interface of IP address: %s", self)
-
if self.version == 4:
- family_ips = (
- (ip.ip, ip.network_prefix, ip.nice_name)
- for ips in [a.ips for a in ifaddr.get_adapters()]
- for ip in ips if not isinstance(ip.ip, tuple)
- )
+ family = netifaces.AF_INET
elif self.version == 6:
- family_ips = (
- (ip.ip[0], ip.network_prefix, ip.nice_name)
- for ips in [a.ips for a in ifaddr.get_adapters()]
- for ip in ips if isinstance(ip.ip, tuple)
- )
+ family = netifaces.AF_INET6
else:
raise ValueError(
"Unsupported address family ({})".format(self.version)
)
- for ip, prefix, ifname in family_ips:
- ifaddrmask = "{ip}/{prefix}".format(ip=ip, prefix=prefix)
- logger.debug(
- "Testing local IP address: %s (interface: %s)",
- ifaddrmask, ifname)
- ifnet = netaddr.IPNetwork(ifaddrmask)
+ for interface in netifaces.interfaces():
+ for ifdata in netifaces.ifaddresses(interface).get(family, []):
+
+ # link-local addresses contain '%suffix' that causes parse
+ # errors in IPNetwork
+ ifaddr = ifdata['addr'].split(u'%', 1)[0]
+
+ # newer versions of netifaces provide IPv6 netmask in format
+ # 'ffff:ffff:ffff:ffff::/64'. We have to split and use prefix
+ # or the netmask with older versions
+ ifmask = ifdata['netmask'].split(u'/')[-1]
+
+ ifaddrmask = '{addr}/{netmask}'.format(
+ addr=ifaddr,
+ netmask=ifmask
+ )
+ logger.debug(
+ "Testing local IP address: %s (interface: %s)",
+ ifaddrmask, interface)
- if ifnet.ip == self:
- return InterfaceDetails(ifname, ifnet)
+ ifnet = netaddr.IPNetwork(ifaddrmask)
+ if ifnet.ip == self:
+ return InterfaceDetails(interface, ifnet)
return None
def set_ip_net(self, ifnet):
diff --git a/ipapython/setup.py b/ipapython/setup.py
index b7b25c8b480f0dc80ae20c4027554b27fe6d87b3..ea55f5c729e8ba65a4436ebf3eb4898870558648 100644
--- a/ipapython/setup.py
+++ b/ipapython/setup.py
@@ -48,6 +48,6 @@ if __name__ == '__main__':
extras_require={
"ldap": ["python-ldap"], # ipapython.ipaldap
# CheckedIPAddress.get_matching_interface
- "ifaddr": ["ifaddr"],
+ "netifaces": ["netifaces"],
},
)
diff --git a/ipasetup.py.in b/ipasetup.py.in
index 56a1f3b066c70385949c86d0d35ce393331ad1c5..25eac3b214b4e6443fe29dd3d656c2fdbe84343a 100644
--- a/ipasetup.py.in
+++ b/ipasetup.py.in
@@ -75,7 +75,7 @@ PACKAGE_VERSION = {
'ipaserver': 'ipaserver == {}'.format(VERSION),
'jwcrypto': 'jwcrypto >= 0.4.2',
'kdcproxy': 'kdcproxy >= 0.3',
- 'ifaddr': 'ifaddr >= 0.1.7',
+ 'netifaces': 'netifaces >= 0.10.4',
'python-ldap': 'python-ldap >= 3.0.0',
'python-yubico': 'python-yubico >= 1.2.3',
'qrcode': 'qrcode >= 5.0',
diff --git a/pylintrc b/pylintrc
index 50278cc76031af68959a138f6ea185c4288c7155..22053a9b55e9dc8e71c1835a63c9393e8ad6803d 100644
--- a/pylintrc
+++ b/pylintrc
@@ -13,6 +13,7 @@ extension-pkg-allow-list=
_ldap,
cryptography,
gssapi,
+ netifaces,
lxml.etree,
pysss_murmur,
--
2.45.1

View File

@ -1,121 +0,0 @@
From ae006b436cfb4ccee5972cf1db0a309fcd80e669 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Fri, 6 Oct 2023 20:16:29 +0000
Subject: [PATCH] Check the HTTP Referer header on all requests
The referer was only checked in WSGIExecutioner classes:
- jsonserver
- KerberosWSGIExecutioner
- xmlserver
- jsonserver_kerb
This left /i18n_messages, /session/login_kerberos,
/session/login_x509, /session/login_password,
/session/change_password and /session/sync_token unprotected
against CSRF attacks.
CVE-2023-5455
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
---
ipaserver/rpcserver.py | 34 +++++++++++++++++++++++++++++++---
1 file changed, 31 insertions(+), 3 deletions(-)
diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py
index 4e8a08b66..3555014ca 100644
--- a/ipaserver/rpcserver.py
+++ b/ipaserver/rpcserver.py
@@ -156,6 +156,19 @@ _success_template = """<html>
</html>"""
class HTTP_Status(plugable.Plugin):
+ def check_referer(self, environ):
+ if "HTTP_REFERER" not in environ:
+ logger.error("Rejecting request with missing Referer")
+ return False
+ if (not environ["HTTP_REFERER"].startswith(
+ "https://%s/ipa" % self.api.env.host)
+ and not self.env.in_tree):
+ logger.error("Rejecting request with bad Referer %s",
+ environ["HTTP_REFERER"])
+ return False
+ logger.debug("Valid Referer %s", environ["HTTP_REFERER"])
+ return True
+
def not_found(self, environ, start_response, url, message):
"""
Return a 404 Not Found error.
@@ -331,9 +344,6 @@ class wsgi_dispatch(Executioner, HTTP_Status):
self.__apps[key] = app
-
-
-
class WSGIExecutioner(Executioner):
"""
Base class for execution backends with a WSGI application interface.
@@ -897,6 +907,9 @@ class jsonserver_session(jsonserver, KerberosSession):
logger.debug('WSGI jsonserver_session.__call__:')
+ if not self.check_referer(environ):
+ return self.bad_request(environ, start_response, 'denied')
+
# Redirect to login if no Kerberos credentials
ccache_name = self.get_environ_creds(environ)
if ccache_name is None:
@@ -949,6 +962,9 @@ class KerberosLogin(Backend, KerberosSession):
def __call__(self, environ, start_response):
logger.debug('WSGI KerberosLogin.__call__:')
+ if not self.check_referer(environ):
+ return self.bad_request(environ, start_response, 'denied')
+
# Redirect to login if no Kerberos credentials
user_ccache_name = self.get_environ_creds(environ)
if user_ccache_name is None:
@@ -967,6 +983,9 @@ class login_x509(KerberosLogin):
def __call__(self, environ, start_response):
logger.debug('WSGI login_x509.__call__:')
+ if not self.check_referer(environ):
+ return self.bad_request(environ, start_response, 'denied')
+
if 'KRB5CCNAME' not in environ:
return self.unauthorized(
environ, start_response, 'KRB5CCNAME not set',
@@ -1015,6 +1034,9 @@ class login_password(Backend, KerberosSession):
logger.debug('WSGI login_password.__call__:')
+ if not self.check_referer(environ):
+ return self.bad_request(environ, start_response, 'denied')
+
# Get the user and password parameters from the request
content_type = environ.get('CONTENT_TYPE', '').lower()
if not content_type.startswith('application/x-www-form-urlencoded'):
@@ -1147,6 +1169,9 @@ class change_password(Backend, HTTP_Status):
def __call__(self, environ, start_response):
logger.info('WSGI change_password.__call__:')
+ if not self.check_referer(environ):
+ return self.bad_request(environ, start_response, 'denied')
+
# Get the user and password parameters from the request
content_type = environ.get('CONTENT_TYPE', '').lower()
if not content_type.startswith('application/x-www-form-urlencoded'):
@@ -1364,6 +1389,9 @@ class xmlserver_session(xmlserver, KerberosSession):
logger.debug('WSGI xmlserver_session.__call__:')
+ if not self.check_referer(environ):
+ return self.bad_request(environ, start_response, 'denied')
+
ccache_name = environ.get('KRB5CCNAME')
# Redirect to /ipa/xml if no Kerberos credentials
--
2.41.0

View File

@ -0,0 +1,91 @@
From bf6653418aa772b47e53f1af092382df5810661c Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
Date: Wed, 5 Jun 2024 15:03:54 +0200
Subject: [PATCH] Revert "custodia: do not use deprecated jwcrypto wrappers"
This reverts commit 536812080502baa51818d9a33ea6533675800b30.
---
install/tools/ipa-custodia-check.in | 4 ++--
ipaserver/custodia/message/kem.py | 14 +++++++-------
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/install/tools/ipa-custodia-check.in b/install/tools/ipa-custodia-check.in
index f3bbf8e7f0eca6e35080fb6770c9d4b1887384ea..4f526b433f872fa7d94e827df0bb206b78a9b58d 100644
--- a/install/tools/ipa-custodia-check.in
+++ b/install/tools/ipa-custodia-check.in
@@ -192,10 +192,10 @@ class IPACustodiaTester:
usage, IPA_CUSTODIA_KEYFILE
))
- if pkey.get('kid') != self.host_spn:
+ if pkey.key_id != self.host_spn:
raise self.error( # pylint: disable=raising-bad-type, #4772
"KID '{}' != host service principal name '{}' "
- "(usage: {})".format(pkey.get('kid'), self.host_spn, usage),
+ "(usage: {})".format(pkey.key_id, self.host_spn, usage),
fatal=True
)
else:
diff --git a/ipaserver/custodia/message/kem.py b/ipaserver/custodia/message/kem.py
index c2996bc921aeac0241111d95194977f9aa630cae..fbbc3fe46f60d25fe1754af70b18bb769c127fa2 100644
--- a/ipaserver/custodia/message/kem.py
+++ b/ipaserver/custodia/message/kem.py
@@ -85,7 +85,7 @@ class KEMKeysStore(SimplePathAuthz):
if self._alg is None:
alg = self.config.get('signing_algorithm', None)
if alg is None:
- ktype = self.server_keys[KEY_USAGE_SIG]['kty']
+ ktype = self.server_keys[KEY_USAGE_SIG].key_type
if ktype == 'RSA':
alg = 'RS256'
elif ktype == 'EC':
@@ -125,9 +125,9 @@ class KEMHandler(MessageHandler):
if 'kid' not in header:
raise InvalidMessage("Missing key identifier")
- key = self.kkstore.find_key(header.get('kid'), usage)
+ key = self.kkstore.find_key(header['kid'], usage)
if key is None:
- raise UnknownPublicKey('Key found [kid:%s]' % header.get('kid'))
+ raise UnknownPublicKey('Key found [kid:%s]' % header['kid'])
return json_decode(key)
def parse(self, msg, name):
@@ -179,14 +179,14 @@ class KEMHandler(MessageHandler):
self.msg_type = 'kem'
return {'type': self.msg_type,
- 'value': {'kid': self.client_keys[KEY_USAGE_ENC].get('kid'),
+ 'value': {'kid': self.client_keys[KEY_USAGE_ENC].key_id,
'claims': claims}}
def reply(self, output):
if self.client_keys is None:
raise UnknownPublicKey("Peer key not defined")
- ktype = self.client_keys[KEY_USAGE_ENC]['kty']
+ ktype = self.client_keys[KEY_USAGE_ENC].key_type
if ktype == 'RSA':
enc = ('RSA-OAEP', 'A256CBC-HS512')
else:
@@ -224,7 +224,7 @@ class KEMClient:
def make_sig_kem(name, value, key, alg):
- header = {'kid': key.get('kid'), 'alg': alg}
+ header = {'kid': key.key_id, 'alg': alg}
claims = {'sub': name, 'exp': int(time.time() + (5 * 60))}
if value is not None:
claims['value'] = value
@@ -235,7 +235,7 @@ def make_sig_kem(name, value, key, alg):
def make_enc_kem(name, value, sig_key, alg, enc_key, enc):
plaintext = make_sig_kem(name, value, sig_key, alg)
- eprot = {'kid': enc_key.get('kid'), 'alg': enc[0], 'enc': enc[1]}
+ eprot = {'kid': enc_key.key_id, 'alg': enc[0], 'enc': enc[1]}
jwe = JWE(plaintext, json_encode(eprot))
jwe.add_recipient(enc_key)
return jwe.serialize(compact=True)
--
2.45.1

View File

@ -1,359 +0,0 @@
From f1f8b16def3e809f5773bb8aa40aefb21699347b Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Thu, 12 Oct 2023 20:34:01 +0000
Subject: [PATCH] Integration tests for verifying Referer header in the UI
Validate that the change_password and login_password endpoints
verify the HTTP Referer header. There is some overlap in the
tests: belt and suspenders.
All endpoints except session/login_x509 are covered, sometimes
having to rely on expected bad results (see the i18n endpoint).
session/login_x509 is not tested yet as it requires significant
additional setup in order to associate a user certificate with
a user entry, etc.
This can be manually verified by modifying /etc/httpd/conf.d/ipa.conf
and adding:
Satisfy Any
Require all granted
Then comment out Auth and SSLVerify, etc. and restart httpd.
With a valid Referer will fail with a 401 and log that there is no
KRB5CCNAME. This comes after the referer check.
With an invalid Referer it will fail with a 400 Bad Request as
expected.
CVE-2023-5455
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
---
ipatests/test_ipaserver/httptest.py | 7 +-
ipatests/test_ipaserver/test_changepw.py | 12 +-
.../test_ipaserver/test_login_password.py | 88 ++++++++++++
ipatests/test_ipaserver/test_referer.py | 136 ++++++++++++++++++
ipatests/util.py | 4 +-
5 files changed, 242 insertions(+), 5 deletions(-)
create mode 100644 ipatests/test_ipaserver/test_login_password.py
create mode 100644 ipatests/test_ipaserver/test_referer.py
diff --git a/ipatests/test_ipaserver/httptest.py b/ipatests/test_ipaserver/httptest.py
index 6cd034a71..8924798fc 100644
--- a/ipatests/test_ipaserver/httptest.py
+++ b/ipatests/test_ipaserver/httptest.py
@@ -36,7 +36,7 @@ class Unauthorized_HTTP_test:
content_type = 'application/x-www-form-urlencoded'
accept_language = 'en-us'
- def send_request(self, method='POST', params=None):
+ def send_request(self, method='POST', params=None, host=None):
"""
Send a request to HTTP server
@@ -45,7 +45,10 @@ class Unauthorized_HTTP_test:
if params is not None:
if self.content_type == 'application/x-www-form-urlencoded':
params = urllib.parse.urlencode(params, True)
- url = 'https://' + self.host + self.app_uri
+ if host:
+ url = 'https://' + host + self.app_uri
+ else:
+ url = 'https://' + self.host + self.app_uri
headers = {'Content-Type': self.content_type,
'Accept-Language': self.accept_language,
diff --git a/ipatests/test_ipaserver/test_changepw.py b/ipatests/test_ipaserver/test_changepw.py
index c3a47ab26..df38ddb3d 100644
--- a/ipatests/test_ipaserver/test_changepw.py
+++ b/ipatests/test_ipaserver/test_changepw.py
@@ -53,10 +53,11 @@ class test_changepw(XMLRPC_test, Unauthorized_HTTP_test):
request.addfinalizer(fin)
- def _changepw(self, user, old_password, new_password):
+ def _changepw(self, user, old_password, new_password, host=None):
return self.send_request(params={'user': str(user),
'old_password' : str(old_password),
'new_password' : str(new_password)},
+ host=host
)
def _checkpw(self, user, password):
@@ -89,6 +90,15 @@ class test_changepw(XMLRPC_test, Unauthorized_HTTP_test):
# make sure that password is NOT changed
self._checkpw(testuser, old_password)
+ def test_invalid_referer(self):
+ response = self._changepw(testuser, old_password, new_password,
+ 'attacker.test')
+
+ assert_equal(response.status, 400)
+
+ # make sure that password is NOT changed
+ self._checkpw(testuser, old_password)
+
def test_pwpolicy_error(self):
response = self._changepw(testuser, old_password, '1')
diff --git a/ipatests/test_ipaserver/test_login_password.py b/ipatests/test_ipaserver/test_login_password.py
new file mode 100644
index 000000000..9425cb797
--- /dev/null
+++ b/ipatests/test_ipaserver/test_login_password.py
@@ -0,0 +1,88 @@
+# Copyright (C) 2023 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import pytest
+import uuid
+
+from ipatests.test_ipaserver.httptest import Unauthorized_HTTP_test
+from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test
+from ipatests.util import assert_equal
+from ipalib import api, errors
+from ipapython.ipautil import run
+
+testuser = u'tuser'
+password = u'password'
+
+
+@pytest.mark.tier1
+class test_login_password(XMLRPC_test, Unauthorized_HTTP_test):
+ app_uri = '/ipa/session/login_password'
+
+ @pytest.fixture(autouse=True)
+ def login_setup(self, request):
+ ccache = os.path.join('/tmp', str(uuid.uuid4()))
+ try:
+ api.Command['user_add'](uid=testuser, givenname=u'Test', sn=u'User')
+ api.Command['passwd'](testuser, password=password)
+ run(['kinit', testuser], stdin='{0}\n{0}\n{0}\n'.format(password),
+ env={"KRB5CCNAME": ccache})
+ except errors.ExecutionError as e:
+ pytest.skip(
+ 'Cannot set up test user: %s' % e
+ )
+
+ def fin():
+ try:
+ api.Command['user_del']([testuser])
+ except errors.NotFound:
+ pass
+ os.unlink(ccache)
+
+ request.addfinalizer(fin)
+
+ def _login(self, user, password, host=None):
+ return self.send_request(params={'user': str(user),
+ 'password' : str(password)},
+ host=host)
+
+ def test_bad_options(self):
+ for params in (
+ None, # no params
+ {"user": "foo"}, # missing options
+ {"user": "foo", "password": ""}, # empty option
+ ):
+ response = self.send_request(params=params)
+ assert_equal(response.status, 400)
+ assert_equal(response.reason, 'Bad Request')
+
+ def test_invalid_auth(self):
+ response = self._login(testuser, 'wrongpassword')
+
+ assert_equal(response.status, 401)
+ assert_equal(response.getheader('X-IPA-Rejection-Reason'),
+ 'invalid-password')
+
+ def test_invalid_referer(self):
+ response = self._login(testuser, password, 'attacker.test')
+
+ assert_equal(response.status, 400)
+
+ def test_success(self):
+ response = self._login(testuser, password)
+
+ assert_equal(response.status, 200)
+ assert response.getheader('X-IPA-Rejection-Reason') is None
diff --git a/ipatests/test_ipaserver/test_referer.py b/ipatests/test_ipaserver/test_referer.py
new file mode 100644
index 000000000..4eade8bba
--- /dev/null
+++ b/ipatests/test_ipaserver/test_referer.py
@@ -0,0 +1,136 @@
+# Copyright (C) 2023 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import pytest
+import uuid
+
+from ipatests.test_ipaserver.httptest import Unauthorized_HTTP_test
+from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test
+from ipatests.util import assert_equal
+from ipalib import api, errors
+from ipapython.ipautil import run
+
+testuser = u'tuser'
+password = u'password'
+
+
+@pytest.mark.tier1
+class test_referer(XMLRPC_test, Unauthorized_HTTP_test):
+
+ @pytest.fixture(autouse=True)
+ def login_setup(self, request):
+ ccache = os.path.join('/tmp', str(uuid.uuid4()))
+ tokenid = None
+ try:
+ api.Command['user_add'](uid=testuser, givenname=u'Test', sn=u'User')
+ api.Command['passwd'](testuser, password=password)
+ run(['kinit', testuser], stdin='{0}\n{0}\n{0}\n'.format(password),
+ env={"KRB5CCNAME": ccache})
+ result = api.Command["otptoken_add"](
+ type='HOTP', description='testotp',
+ ipatokenotpalgorithm='sha512', ipatokenowner=testuser,
+ ipatokenotpdigits='6')
+ tokenid = result['result']['ipatokenuniqueid'][0]
+ except errors.ExecutionError as e:
+ pytest.skip(
+ 'Cannot set up test user: %s' % e
+ )
+
+ def fin():
+ try:
+ api.Command['user_del']([testuser])
+ api.Command['otptoken_del']([tokenid])
+ except errors.NotFound:
+ pass
+ os.unlink(ccache)
+
+ request.addfinalizer(fin)
+
+ def _request(self, params={}, host=None):
+ # implicit is that self.app_uri is set to the appropriate value
+ return self.send_request(params=params, host=host)
+
+ def test_login_password_valid(self):
+ """Valid authentication of a user"""
+ self.app_uri = "/ipa/session/login_password"
+ response = self._request(
+ params={'user': 'tuser', 'password': password})
+ assert_equal(response.status, 200, self.app_uri)
+
+ def test_change_password_valid(self):
+ """This actually changes the user password"""
+ self.app_uri = "/ipa/session/change_password"
+ response = self._request(
+ params={'user': 'tuser',
+ 'old_password': password,
+ 'new_password': 'new_password'}
+ )
+ assert_equal(response.status, 200, self.app_uri)
+
+ def test_sync_token_valid(self):
+ """We aren't testing that sync works, just that we can get there"""
+ self.app_uri = "/ipa/session/sync_token"
+ response = self._request(
+ params={'user': 'tuser',
+ 'first_code': '1234',
+ 'second_code': '5678',
+ 'password': 'password'})
+ assert_equal(response.status, 200, self.app_uri)
+
+ def test_i18n_messages_valid(self):
+ # i18n_messages requires a valid JSON request and we send
+ # nothing. If we get a 500 error then it got past the
+ # referer check.
+ self.app_uri = "/ipa/i18n_messages"
+ response = self._request()
+ assert_equal(response.status, 500, self.app_uri)
+
+ # /ipa/session/login_x509 is not tested yet as it requires
+ # significant additional setup.
+ # This can be manually verified by adding
+ # Satisfy Any and Require all granted to the configuration
+ # section and comment out all Auth directives. The request
+ # will fail and log that there is no KRB5CCNAME which comes
+ # after the referer check.
+
+ def test_endpoints_auth_required(self):
+ """Test endpoints that require pre-authorization which will
+ fail before we even get to the Referer check
+ """
+ self.endpoints = {
+ "/ipa/xml",
+ "/ipa/session/login_kerberos",
+ "/ipa/session/json",
+ "/ipa/session/xml"
+ }
+ for self.app_uri in self.endpoints:
+ response = self._request(host="attacker.test")
+
+ # referer is checked after auth
+ assert_equal(response.status, 401, self.app_uri)
+
+ def notest_endpoints_invalid(self):
+ """Pass in a bad Referer, expect a 400 Bad Request"""
+ self.endpoints = {
+ "/ipa/session/login_password",
+ "/ipa/session/change_password",
+ "/ipa/session/sync_token",
+ }
+ for self.app_uri in self.endpoints:
+ response = self._request(host="attacker.test")
+
+ assert_equal(response.status, 400, self.app_uri)
diff --git a/ipatests/util.py b/ipatests/util.py
index 5c0152b90..c69d98790 100644
--- a/ipatests/util.py
+++ b/ipatests/util.py
@@ -163,12 +163,12 @@ class ExceptionNotRaised(Exception):
return self.msg % self.expected.__name__
-def assert_equal(val1, val2):
+def assert_equal(val1, val2, msg=''):
"""
Assert ``val1`` and ``val2`` are the same type and of equal value.
"""
assert type(val1) is type(val2), '%r != %r' % (val1, val2)
- assert val1 == val2, '%r != %r' % (val1, val2)
+ assert val1 == val2, '%r != %r %r' % (val1, val2, msg)
def assert_not_equal(val1, val2):
--
2.41.0

View File

@ -0,0 +1,890 @@
From 941d6a54e163ca0d5eee6ac391c0d2f5afb6cf55 Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
Date: Tue, 9 Dec 2025 10:27:12 +0100
Subject: [PATCH] Revert "Remove NIS server support"
This reverts commit e98a80827bcc42dc16b516355077fed844220107.
---
freeipa.spec.in | 2 +
install/share/Makefile.am | 2 +
install/share/nis-update.uldif | 38 ++++
install/share/nis.uldif | 96 ++++++++++
install/tools/Makefile.am | 2 +
install/tools/ipa-compat-manage.in | 17 +-
install/tools/ipa-nis-manage.in | 205 +++++++++++++++++++++
install/tools/man/Makefile.am | 1 +
install/tools/man/ipa-nis-manage.1 | 51 +++++
install/updates/10-enable-betxn.update | 3 +
install/updates/50-nis.update | 3 +
install/updates/Makefile.am | 1 +
ipaplatform/base/paths.py | 2 +
ipaserver/install/ipa_migrate.py | 27 ++-
ipaserver/install/ipa_migrate_constants.py | 24 +++
ipaserver/install/plugins/update_nis.py | 92 +++++++++
ipatests/test_cmdline/test_cli.py | 1 +
ipatests/test_integration/test_commands.py | 87 +++++++++
18 files changed, 638 insertions(+), 16 deletions(-)
create mode 100644 install/share/nis-update.uldif
create mode 100644 install/share/nis.uldif
create mode 100644 install/tools/ipa-nis-manage.in
create mode 100644 install/tools/man/ipa-nis-manage.1
create mode 100644 install/updates/50-nis.update
create mode 100644 ipaserver/install/plugins/update_nis.py
diff --git a/freeipa.spec.in b/freeipa.spec.in
index b93199a5783a89e0ff58affd3d416556f72dd00c..6ee0f43f059dad4bba2288b1b0d3a63437371861 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -1619,6 +1619,7 @@ fi
%{_sbindir}/ipa-ldap-updater
%{_sbindir}/ipa-otptoken-import
%{_sbindir}/ipa-compat-manage
+%{_sbindir}/ipa-nis-manage
%{_sbindir}/ipa-managed-entries
%{_sbindir}/ipactl
%{_sbindir}/ipa-advise
@@ -1694,6 +1695,7 @@ fi
%{_mandir}/man1/ipa-ca-install.1*
%{_mandir}/man1/ipa-kra-install.1*
%{_mandir}/man1/ipa-compat-manage.1*
+%{_mandir}/man1/ipa-nis-manage.1*
%{_mandir}/man1/ipa-managed-entries.1*
%{_mandir}/man1/ipa-ldap-updater.1*
%{_mandir}/man8/ipactl.8*
diff --git a/install/share/Makefile.am b/install/share/Makefile.am
index bb2eb7531b083e36827ba1ee111e39c29589acaa..87578d29644f897622f46a56c06cb24c7df3efe6 100644
--- a/install/share/Makefile.am
+++ b/install/share/Makefile.am
@@ -66,6 +66,8 @@ dist_app_DATA = \
master-entry.ldif \
memberof-task.ldif \
memberof-conf.ldif \
+ nis.uldif \
+ nis-update.uldif \
opendnssec_conf.template \
opendnssec_kasp.template \
unique-attributes.ldif \
diff --git a/install/share/nis-update.uldif b/install/share/nis-update.uldif
new file mode 100644
index 0000000000000000000000000000000000000000..e602c1de061fbcece349b2d86970c4db5051473b
--- /dev/null
+++ b/install/share/nis-update.uldif
@@ -0,0 +1,38 @@
+# Updates for NIS
+
+# Correct syntax error that caused users to not appear
+dn: nis-domain=$DOMAIN+nis-map=netgroup, cn=NIS Server, cn=plugins, cn=config
+replace:nis-value-format: %merge(" ","%{memberNisNetgroup}","(%link(\"%ifeq(\\\"hostCategory\\\",\\\"all\\\",\\\"\\\",\\\"%collect(\\\\\\\"%{externalHost}\\\\\\\",\\\\\\\"%deref(\\\\\\\\\\\\\\\"memberHost\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"memberHost\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\")\\\")\",\"-\",\",\",\"%ifeq(\\\"userCategory\\\",\\\"all\\\",\\\"\\\",\\\"%collect(\\\"%deref(\\\\\\\"memberUser\\\\\\\",\\\\\\\"uid\\\\\\\")\\\",\\\"%deref_r(\\\\\\\"member\\\\\\\",\\\\\\\"uid\\\\\\\")\\\",\\\"%deref_r(\\\\\\\"memberUser\\\\\\\",\\\\\\\"member\\\\\\\",\\\\\\\"uid\\\\\\\")\\\")\\\")\",\"-\"),%{nisDomainName:-})")::%merge(" ","%{memberNisNetgroup}","(%link(\"%ifeq(\\\"hostCategory\\\",\\\"all\\\",\\\"\\\",\\\"%collect(\\\\\\\"%{externalHost}\\\\\\\",\\\\\\\"%deref(\\\\\\\\\\\\\\\"memberHost\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"memberHost\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\")\\\")\",\"-\",\",\",\"%ifeq(\\\"userCategory\\\",\\\"all\\\",\\\"\\\",\\\"%collect(\\\\\\\"%deref(\\\\\\\\\\\\\\\"memberUser\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"memberUser\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\")\\\")\",\"-\"),%{nisDomainName:-})")
+
+# Correct syntax error that caused nested netgroups to not work
+# https://bugzilla.redhat.com/show_bug.cgi?id=788625
+dn: nis-domain=$DOMAIN+nis-map=netgroup, cn=NIS Server, cn=plugins, cn=config
+replace:nis-value-format: %merge(" ","%{memberNisNetgroup}","(%link(\"%ifeq(\\\"hostCategory\\\",\\\"all\\\",\\\"\\\",\\\"%collect(\\\\\\\"%{externalHost}\\\\\\\",\\\\\\\"%deref(\\\\\\\\\\\\\\\"memberHost\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"memberHost\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\")\\\")\",\"-\",\",\",\"%ifeq(\\\"userCategory\\\",\\\"all\\\",\\\"\\\",\\\"%collect(\\\\\\\"%deref(\\\\\\\\\\\\\\\"memberUser\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"memberUser\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\")\\\")\",\"-\"),%{nisDomainName:-})")::%merge(" ","%deref_f(\"member\",\"(objectclass=ipanisNetgroup)\",\"cn\")","(%link(\"%ifeq(\\\"hostCategory\\\",\\\"all\\\",\\\"\\\",\\\"%collect(\\\\\\\"%{externalHost}\\\\\\\",\\\\\\\"%deref(\\\\\\\\\\\\\\\"memberHost\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"memberHost\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\")\\\")\",\"-\",\",\",\"%ifeq(\\\"userCategory\\\",\\\"all\\\",\\\"\\\",\\\"%collect(\\\\\\\"%deref(\\\\\\\\\\\\\\\"memberUser\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"memberUser\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\")\\\")\",\"-\"),%{nisDomainName:-})")
+
+# Make the padding an expression so usercat and hostcat always gets
+# evaluated when displaying entries.
+# https://bugzilla.redhat.com/show_bug.cgi?id=767372
+dn: nis-domain=$DOMAIN+nis-map=netgroup, cn=NIS Server, cn=plugins, cn=config
+replace:nis-value-format: %merge(" ","%deref_f(\"member\",\"(objectclass=ipanisNetgroup)\",\"cn\")","(%link(\"%ifeq(\\\"hostCategory\\\",\\\"all\\\",\\\"\\\",\\\"%collect(\\\\\\\"%{externalHost}\\\\\\\",\\\\\\\"%deref(\\\\\\\\\\\\\\\"memberHost\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"memberHost\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\")\\\")\",\"-\",\",\",\"%ifeq(\\\"userCategory\\\",\\\"all\\\",\\\"\\\",\\\"%collect(\\\\\\\"%deref(\\\\\\\\\\\\\\\"memberUser\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"memberUser\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\")\\\")\",\"-\"),%{nisDomainName:-})")::%merge(" ","%deref_f(\"member\",\"(objectclass=ipanisNetgroup)\",\"cn\")","(%link(\"%ifeq(\\\"hostCategory\\\",\\\"all\\\",\\\"\\\",\\\"%collect(\\\\\\\"%{externalHost}\\\\\\\",\\\\\\\"%deref(\\\\\\\\\\\\\\\"memberHost\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"memberHost\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\")\\\")\",\"%ifeq(\\\"hostCategory\\\",\\\"all\\\",\\\"\\\",\\\"-\\\")\",\",\",\"%ifeq(\\\"userCategory\\\",\\\"all\\\",\\\"\\\",\\\"%collect(\\\\\\\"%deref(\\\\\\\\\\\\\\\"memberUser\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"memberUser\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\")\\\")\",\"%ifeq(\\\"userCategory\\\",\\\"all\\\",\\\"\\\",\\\"-\\\")\"),%{nisDomainName:-})")
+
+dn: nis-domain=$DOMAIN+nis-map=ethers.byaddr, cn=NIS Server, cn=plugins, cn=config
+default:objectclass: top
+default:objectclass: extensibleObject
+default:nis-domain: $DOMAIN
+default:nis-map: ethers.byaddr
+default:nis-base: cn=computers, cn=accounts, $SUFFIX
+default:nis-filter: (&(macAddress=*)(fqdn=*)(objectClass=ipaHost))
+default:nis-keys-format: %mregsub("%{macAddress} %{fqdn}","(..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..) (.*)","%1:%2:%3:%4:%5:%6")
+default:nis-values-format: %mregsub("%{macAddress} %{fqdn}","(..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..) (.*)","%1:%2:%3:%4:%5:%6 %7")
+default:nis-secure: no
+
+dn: nis-domain=$DOMAIN+nis-map=ethers.byname, cn=NIS Server, cn=plugins, cn=config
+default:objectclass: top
+default:objectclass: extensibleObject
+default:nis-domain: $DOMAIN
+default:nis-map: ethers.byname
+default:nis-base: cn=computers, cn=accounts, $SUFFIX
+default:nis-filter: (&(macAddress=*)(fqdn=*)(objectClass=ipaHost))
+default:nis-keys-format: %mregsub("%{macAddress} %{fqdn}","(..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..) (.*)","%7")
+default:nis-values-format: %mregsub("%{macAddress} %{fqdn}","(..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..) (.*)","%1:%2:%3:%4:%5:%6 %7")
+default:nis-secure: no
diff --git a/install/share/nis.uldif b/install/share/nis.uldif
new file mode 100644
index 0000000000000000000000000000000000000000..1735fb55299d28ab40d864b24062309fca43241f
--- /dev/null
+++ b/install/share/nis.uldif
@@ -0,0 +1,96 @@
+dn: cn=NIS Server, cn=plugins, cn=config
+default:objectclass: top
+default:objectclass: nsSlapdPlugin
+default:objectclass: extensibleObject
+default:cn: NIS Server
+default:nsslapd-pluginpath: /usr/lib$LIBARCH/dirsrv/plugins/nisserver-plugin.so
+default:nsslapd-plugininitfunc: nis_plugin_init
+default:nsslapd-plugintype: object
+default:nsslapd-pluginbetxn: on
+default:nsslapd-pluginenabled: on
+default:nsslapd-pluginid: nis-server
+default:nsslapd-pluginversion: 0.10
+default:nsslapd-pluginvendor: redhat.com
+default:nsslapd-plugindescription: NIS Server Plugin
+default:nis-tcp-wrappers-name: nis-server
+
+dn: nis-domain=$DOMAIN+nis-map=passwd.byname, cn=NIS Server, cn=plugins, cn=config
+default:objectclass: top
+default:objectclass: extensibleObject
+default:nis-domain: $DOMAIN
+default:nis-map: passwd.byname
+default:nis-base: cn=users, cn=accounts, $SUFFIX
+default:nis-secure: no
+
+dn: nis-domain=$DOMAIN+nis-map=passwd.byuid, cn=NIS Server, cn=plugins, cn=config
+default:objectclass: top
+default:objectclass: extensibleObject
+default:nis-domain: $DOMAIN
+default:nis-map: passwd.byuid
+default:nis-base: cn=users, cn=accounts, $SUFFIX
+default:nis-secure: no
+
+dn: nis-domain=$DOMAIN+nis-map=group.byname, cn=NIS Server, cn=plugins, cn=config
+default:objectclass: top
+default:objectclass: extensibleObject
+default:nis-domain: $DOMAIN
+default:nis-map: group.byname
+default:nis-base: cn=groups, cn=accounts, $SUFFIX
+default:nis-secure: no
+
+dn: nis-domain=$DOMAIN+nis-map=group.bygid, cn=NIS Server, cn=plugins, cn=config
+default:objectclass: top
+default:objectclass: extensibleObject
+default:nis-domain: $DOMAIN
+default:nis-map: group.bygid
+default:nis-base: cn=groups, cn=accounts, $SUFFIX
+default:nis-secure: no
+
+dn: nis-domain=$DOMAIN+nis-map=netid.byname, cn=NIS Server, cn=plugins, cn=config
+default:objectclass: top
+default:objectclass: extensibleObject
+default:nis-domain: $DOMAIN
+default:nis-map: netid.byname
+default:nis-base: cn=users, cn=accounts, $SUFFIX
+default:nis-secure: no
+
+# Note that the escapes in this entry can be quite confusing. The trick
+# is that each level of nesting requires (2^n) - 1 escapes. So the
+# first level is \", the second is \\\", the third is \\\\\\\", etc.
+# (1, 3, 7, 15, more than that and you'll go insane)
+
+# Note that this configuration mirrors the Schema Compat configuration for
+# triples.
+dn: nis-domain=$DOMAIN+nis-map=netgroup, cn=NIS Server, cn=plugins, cn=config
+default:objectclass: top
+default:objectclass: extensibleObject
+default:nis-domain: $DOMAIN
+default:nis-map: netgroup
+default:nis-base: cn=ng, cn=alt, $SUFFIX
+default:nis-filter: (objectClass=ipanisNetgroup)
+default:nis-key-format: %{cn}
+default:nis-value-format:%merge(" ","%deref_f(\"member\",\"(objectclass=ipanisNetgroup)\",\"cn\")","(%link(\"%ifeq(\\\"hostCategory\\\",\\\"all\\\",\\\"\\\",\\\"%collect(\\\\\\\"%{externalHost}\\\\\\\",\\\\\\\"%deref(\\\\\\\\\\\\\\\"memberHost\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"memberHost\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"fqdn\\\\\\\\\\\\\\\")\\\\\\\")\\\")\",\"%ifeq(\\\"hostCategory\\\",\\\"all\\\",\\\"\\\",\\\"-\\\")\",\",\",\"%ifeq(\\\"userCategory\\\",\\\"all\\\",\\\"\\\",\\\"%collect(\\\\\\\"%deref(\\\\\\\\\\\\\\\"memberUser\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\",\\\\\\\"%deref_r(\\\\\\\\\\\\\\\"memberUser\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"member\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"uid\\\\\\\\\\\\\\\")\\\\\\\")\\\")\",\"%ifeq(\\\"userCategory\\\",\\\"all\\\",\\\"\\\",\\\"-\\\")\"),%{nisDomainName:-})")
+default:nis-secure: no
+
+dn: nis-domain=$DOMAIN+nis-map=ethers.byaddr, cn=NIS Server, cn=plugins, cn=config
+default:objectclass: top
+default:objectclass: extensibleObject
+default:nis-domain: $DOMAIN
+default:nis-map: ethers.byaddr
+default:nis-base: cn=computers, cn=accounts, $SUFFIX
+default:nis-filter: (&(macAddress=*)(fqdn=*)(objectClass=ipaHost))
+default:nis-keys-format: %mregsub("%{macAddress} %{fqdn}","(..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..) (.*)","%1:%2:%3:%4:%5:%6")
+default:nis-values-format: %mregsub("%{macAddress} %{fqdn}","(..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..) (.*)","%1:%2:%3:%4:%5:%6 %7")
+default:nis-secure: no
+
+dn: nis-domain=$DOMAIN+nis-map=ethers.byname, cn=NIS Server, cn=plugins, cn=config
+default:objectclass: top
+default:objectclass: extensibleObject
+default:nis-domain: $DOMAIN
+default:nis-map: ethers.byname
+default:nis-base: cn=computers, cn=accounts, $SUFFIX
+default:nis-filter: (&(macAddress=*)(fqdn=*)(objectClass=ipaHost))
+default:nis-keys-format: %mregsub("%{macAddress} %{fqdn}","(..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..) (.*)","%7")
+default:nis-values-format: %mregsub("%{macAddress} %{fqdn}","(..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..)[:\\\|-](..) (.*)","%1:%2:%3:%4:%5:%6 %7")
+default:nis-secure: no
+
diff --git a/install/tools/Makefile.am b/install/tools/Makefile.am
index 74cd11c67dd3af623dd29802935ae63fe82fcecb..ca484ec37969c9c06ae7b408b55fa30cd4e8e4fe 100644
--- a/install/tools/Makefile.am
+++ b/install/tools/Makefile.am
@@ -19,6 +19,7 @@ dist_noinst_DATA = \
ipa-server-upgrade.in \
ipactl.in \
ipa-compat-manage.in \
+ ipa-nis-manage.in \
ipa-managed-entries.in \
ipa-ldap-updater.in \
ipa-otptoken-import.in \
@@ -56,6 +57,7 @@ nodist_sbin_SCRIPTS = \
ipa-server-upgrade \
ipactl \
ipa-compat-manage \
+ ipa-nis-manage \
ipa-managed-entries \
ipa-ldap-updater \
ipa-otptoken-import \
diff --git a/install/tools/ipa-compat-manage.in b/install/tools/ipa-compat-manage.in
index fb25c22edd5b2fac123144846ffc232cb5bbecc7..9650abd6f83ebc0a8ef347fee83989d4e9f13f09 100644
--- a/install/tools/ipa-compat-manage.in
+++ b/install/tools/ipa-compat-manage.in
@@ -25,7 +25,6 @@ import sys
from ipaplatform.paths import paths
try:
from ipapython import ipautil, config
- from ipapython.ipaldap import realm_to_serverid
from ipaserver.install import installutils
from ipaserver.install.ldapupdate import LDAPUpdate
from ipalib import api, errors
@@ -153,19 +152,9 @@ def main():
try:
entry = get_entry(nis_config_dn)
# We can't disable schema compat if the NIS plugin is enabled
- if (
- entry is not None
- and entry.get("nsslapd-pluginenabled", [""])[0].lower() == "on"
- ):
- instance = realm_to_serverid(api.env.realm)
- print(
- "The NIS plugin is configured, cannot "
- "disable compatibility.", file=sys.stderr,
- )
- print(
- f"Run \"dsconf {instance} plugin set --enabled off "
- "'NIS Server'\" first.", file=sys.stderr,
- )
+ if entry is not None and entry.get('nsslapd-pluginenabled', [''])[0].lower() == 'on':
+ print("The NIS plugin is configured, cannot disable compatibility.", file=sys.stderr)
+ print("Run 'ipa-nis-manage disable' first.", file=sys.stderr)
retval = 2
except errors.ExecutionError as lde:
print("An error occurred while talking to the server.")
diff --git a/install/tools/ipa-nis-manage.in b/install/tools/ipa-nis-manage.in
new file mode 100644
index 0000000000000000000000000000000000000000..6b156ce6a80d05d9cd2d7cab48e6dba08b0954f5
--- /dev/null
+++ b/install/tools/ipa-nis-manage.in
@@ -0,0 +1,205 @@
+#!/usr/bin/python3
+# Authors: Rob Crittenden <rcritten@redhat.com>
+# Authors: Simo Sorce <ssorce@redhat.com>
+#
+# Copyright (C) 2009 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from __future__ import print_function
+
+import sys
+import os
+from ipaplatform.paths import paths
+try:
+ from optparse import OptionParser # pylint: disable=deprecated-module
+ from ipapython import ipautil, config
+ from ipaserver.install import installutils
+ from ipaserver.install.ldapupdate import LDAPUpdate
+ from ipalib import api, errors
+ from ipapython.ipa_log_manager import standard_logging_setup
+ from ipapython.dn import DN
+ from ipaplatform import services
+except ImportError as e:
+ print("""\
+There was a problem importing one of the required Python modules. The
+error was:
+
+ %s
+""" % e, file=sys.stderr)
+ sys.exit(1)
+
+nis_config_dn = DN(('cn', 'NIS Server'), ('cn', 'plugins'), ('cn', 'config'))
+compat_dn = DN(('cn', 'Schema Compatibility'), ('cn', 'plugins'), ('cn', 'config'))
+
+def parse_options():
+ usage = "%prog [options] <enable|disable|status>\n"
+ usage += "%prog [options]\n"
+ parser = OptionParser(usage=usage, formatter=config.IPAFormatter())
+
+ parser.add_option("-d", "--debug", action="store_true", dest="debug",
+ help="Display debugging information about the update(s)")
+ parser.add_option("-y", dest="password",
+ help="File containing the Directory Manager password")
+
+ config.add_standard_options(parser)
+ options, args = parser.parse_args()
+
+ return options, args
+
+def get_dirman_password():
+ """Prompt the user for the Directory Manager password and verify its
+ correctness.
+ """
+ password = installutils.read_password("Directory Manager", confirm=False, validate=False, retry=False)
+
+ return password
+
+def get_entry(dn):
+ """
+ Return the entry for the given DN. If the entry is not found return
+ None.
+ """
+ entry = None
+ try:
+ entry = api.Backend.ldap2.get_entry(dn)
+ except errors.NotFound:
+ pass
+ return entry
+
+def main():
+ retval = 0
+ files = [paths.NIS_ULDIF]
+ servicemsg = ""
+
+ if os.getegid() != 0:
+ sys.exit('Must be root to use this tool.')
+
+ installutils.check_server_configuration()
+
+ options, args = parse_options()
+
+ if len(args) != 1:
+ sys.exit("You must specify one action: enable | disable | status")
+ elif args[0] not in {"enable", "disable", "status"}:
+ sys.exit("Unrecognized action [" + args[0] + "]")
+
+ standard_logging_setup(None, debug=options.debug)
+ dirman_password = ""
+ if options.password:
+ try:
+ pw = ipautil.template_file(options.password, [])
+ except IOError:
+ sys.exit("File \"%s\" not found or not readable" % options.password)
+ dirman_password = pw.strip()
+ else:
+ dirman_password = get_dirman_password()
+ if dirman_password is None:
+ sys.exit("Directory Manager password required")
+
+ if not dirman_password:
+ sys.exit("No password supplied")
+
+ api.bootstrap(
+ context='cli', confdir=paths.ETC_IPA,
+ debug=options.debug, in_server=True)
+ api.finalize()
+ api.Backend.ldap2.connect(bind_pw=dirman_password)
+
+ if args[0] == "enable":
+ compat = get_entry(compat_dn)
+ if compat is None or compat.get('nsslapd-pluginenabled', [''])[0].lower() == 'off':
+ sys.exit("The compat plugin needs to be enabled: ipa-compat-manage enable")
+ entry = None
+ try:
+ entry = get_entry(nis_config_dn)
+ except errors.ExecutionError as lde:
+ print("An error occurred while talking to the server.")
+ print(lde)
+ retval = 1
+
+ # Enable either the portmap or rpcbind service
+ portmap = services.knownservices.portmap
+ rpcbind = services.knownservices.rpcbind
+
+ if portmap.is_installed():
+ portmap.enable()
+ servicemsg = portmap.service_name
+ elif rpcbind.is_installed():
+ rpcbind.enable()
+ servicemsg = rpcbind.service_name
+ else:
+ print("Unable to enable either %s or %s" % (portmap.service_name, rpcbind.service_name))
+ retval = 3
+
+ # The cn=config entry for the plugin may already exist but it
+ # could be turned off, handle both cases.
+ if entry is None:
+ print("Enabling plugin")
+ ld = LDAPUpdate()
+ if ld.update(files) != True:
+ retval = 1
+ elif entry.get('nsslapd-pluginenabled', [''])[0].lower() == 'off':
+ print("Enabling plugin")
+ # Already configured, just enable the plugin
+ entry['nsslapd-pluginenabled'] = ['on']
+ api.Backend.ldap2.update_entry(entry)
+ else:
+ print("Plugin already Enabled")
+ retval = 2
+
+ elif args[0] == "disable":
+ try:
+ entry = api.Backend.ldap2.get_entry(nis_config_dn, ['nsslapd-pluginenabled'])
+ entry['nsslapd-pluginenabled'] = ['off']
+ api.Backend.ldap2.update_entry(entry)
+ except (errors.NotFound, errors.EmptyModlist):
+ print("Plugin is already disabled")
+ retval = 2
+ except errors.LDAPError as lde:
+ print("An error occurred while talking to the server.")
+ print(lde)
+ retval = 1
+
+ elif args[0] == "status":
+ nis_entry = get_entry(nis_config_dn)
+ enabled = (nis_entry and
+ nis_entry.get(
+ 'nsslapd-pluginenabled', '')[0].lower() == "on")
+ if enabled:
+ print("Plugin is enabled")
+ retval = 0
+ else:
+ print("Plugin is not enabled")
+ retval = 4
+
+ else:
+ retval = 1
+
+ if retval == 0:
+ if args[0] in {"enable", "disable"}:
+ print("This setting will not take effect until you restart "
+ "Directory Server.")
+
+ if args[0] == "enable":
+ print("The %s service may need to be started." % servicemsg)
+
+ api.Backend.ldap2.disconnect()
+
+ return retval
+
+if __name__ == '__main__':
+ installutils.run_script(main, operation_name='ipa-nis-manage')
diff --git a/install/tools/man/Makefile.am b/install/tools/man/Makefile.am
index bffce402c7df428a47c1710bdc47d59a95993a0d..e9542a77bbbb88054eae1e64311d6e9ec5bee499 100644
--- a/install/tools/man/Makefile.am
+++ b/install/tools/man/Makefile.am
@@ -18,6 +18,7 @@ dist_man1_MANS = \
ipa-kra-install.1 \
ipa-ldap-updater.1 \
ipa-compat-manage.1 \
+ ipa-nis-manage.1 \
ipa-managed-entries.1 \
ipa-backup.1 \
ipa-restore.1 \
diff --git a/install/tools/man/ipa-nis-manage.1 b/install/tools/man/ipa-nis-manage.1
new file mode 100644
index 0000000000000000000000000000000000000000..1107b7790531aa695defd660040734143c02474d
--- /dev/null
+++ b/install/tools/man/ipa-nis-manage.1
@@ -0,0 +1,51 @@
+.\" A man page for ipa-nis-manage
+.\" Copyright (C) 2009 Red Hat, Inc.
+.\"
+.\" This program is free software; you can redistribute it and/or modify
+.\" it under the terms of the GNU General Public License as published by
+.\" the Free Software Foundation, either version 3 of the License, or
+.\" (at your option) any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful, but
+.\" WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+.\" General Public License for more details.
+.\"
+.\" You should have received a copy of the GNU General Public License
+.\" along with this program. If not, see <http://www.gnu.org/licenses/>.
+.\"
+.\" Author: Rob Crittenden <rcritten@redhat.com>
+.\"
+.TH "ipa-nis-manage" "1" "April 25 2016" "IPA" "IPA Manual Pages"
+.SH "NAME"
+ipa\-nis\-manage \- Enables or disables the NIS listener plugin
+.SH "SYNOPSIS"
+ipa\-nis\-manage [options] <enable|disable|status>
+.SH "DESCRIPTION"
+Run the command with the \fBenable\fR option to enable the NIS plugin.
+
+Run the command with the \fBdisable\fR option to disable the NIS plugin.
+
+Run the command with the \fBstatus\fR option to read status of the NIS plugin. Return code 0 indicates enabled plugin, return code 4 indicates disabled plugin.
+
+In all cases the user will be prompted to provide the Directory Manager's password unless option \fB\-y\fR is used.
+
+Directory Server will need to be restarted after the NIS listener plugin has been enabled.
+
+.SH "OPTIONS"
+.TP
+\fB\-d\fR, \fB\-\-debug\fR
+Enable debug logging when more verbose output is needed
+.TP
+\fB\-y\fR \fIfile\fR
+File containing the Directory Manager password
+.SH "EXIT STATUS"
+0 if the command was successful
+
+1 if an error occurred
+
+2 if the plugin is already in the required status (enabled or disabled)
+
+3 if RPC services cannot be enabled.
+
+4 if status command detected plugin in disabled state.
diff --git a/install/updates/10-enable-betxn.update b/install/updates/10-enable-betxn.update
index 9525292cb2d66a738a2dec9cd881dbf960d2d944..1f89341c7f68616161d03b8a2e0c34f499bc3317 100644
--- a/install/updates/10-enable-betxn.update
+++ b/install/updates/10-enable-betxn.update
@@ -44,3 +44,6 @@ only: nsslapd-pluginbetxn: on
dn: cn=Schema Compatibility, cn=plugins, cn=config
onlyifexist: nsslapd-pluginbetxn: on
+
+dn: cn=NIS Server, cn=plugins, cn=config
+onlyifexist: nsslapd-pluginbetxn: on
diff --git a/install/updates/50-nis.update b/install/updates/50-nis.update
new file mode 100644
index 0000000000000000000000000000000000000000..05a166f003aefc50fc25f10f01f7364d752425bc
--- /dev/null
+++ b/install/updates/50-nis.update
@@ -0,0 +1,3 @@
+# Updates are applied only if NIS plugin has been configured
+# update definitions are located in install/share/nis-update.uldif
+plugin: update_nis_configuration
diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am
index cce2670a6c31803dc534ac5b0093ec0aa317233e..fd96831d8fd4904b51d933847865d37c58a3508c 100644
--- a/install/updates/Makefile.am
+++ b/install/updates/Makefile.am
@@ -52,6 +52,7 @@ app_DATA = \
50-groupuuid.update \
50-hbacservice.update \
50-krbenctypes.update \
+ 50-nis.update \
50-ipaconfig.update \
55-pbacmemberof.update \
59-trusts-sysacount.update \
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index 5bc0e3ac3eed257fd7fa6a0830e3e2dbeb3d01d2..b84925bfc7d9af8584d9d4b185aa04b635c1e325 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -297,6 +297,8 @@ class BasePathNamespace:
CA_TOPOLOGY_ULDIF = "/usr/share/ipa/ca-topology.uldif"
IPA_HTML_DIR = "/usr/share/ipa/html"
CA_CRT = "/usr/share/ipa/html/ca.crt"
+ NIS_ULDIF = "/usr/share/ipa/nis.uldif"
+ NIS_UPDATE_ULDIF = "/usr/share/ipa/nis-update.uldif"
SCHEMA_COMPAT_ULDIF = "/usr/share/ipa/updates/91-schema_compat.update"
SCHEMA_COMPAT_POST_ULDIF = "/usr/share/ipa/schema_compat_post.uldif"
SUBID_GENERATORS_ULDIF = "/usr/share/ipa/subid-generators.uldif"
diff --git a/ipaserver/install/ipa_migrate.py b/ipaserver/install/ipa_migrate.py
index f07a878e739002346df894f93745ea60345e0557..b0a27de5dd40aa661271faa3a8a0855226b5892f 100644
--- a/ipaserver/install/ipa_migrate.py
+++ b/ipaserver/install/ipa_migrate.py
@@ -32,7 +32,7 @@ from ipapython.admintool import admin_cleanup_global_argv
from ipaserver.install.ipa_migrate_constants import (
DS_CONFIG, DB_OBJECTS, DS_INDEXES, BIND_DN, LOG_FILE_NAME,
STRIP_OP_ATTRS, STRIP_ATTRS, STRIP_OC, PROD_ATTRS,
- DNA_REGEN_VAL, DNA_REGEN_ATTRS, IGNORE_ATTRS,
+ DNA_REGEN_VAL, DNA_REGEN_ATTRS, NIS_PLUGIN, IGNORE_ATTRS,
DB_EXCLUDE_TREES, POLICY_OP_ATTRS, STATE_OPTIONS
)
@@ -749,7 +749,8 @@ class IPAMigrate():
self.log_info(title)
self.log_info('-' * (len(title) - 1))
logged_something = self.log_stats(DS_CONFIG)
- if self.args.verbose:
+ if self.args.verbose or NIS_PLUGIN['count'] > 0:
+ self.log_info(f" - NIS Server Plugin: {NIS_PLUGIN['count']}")
logged_something = True
if not self.log_stats(DS_INDEXES) and not logged_something:
self.log_info(" - No updates")
@@ -1983,6 +1984,28 @@ class IPAMigrate():
add_missing=True)
stats['config_processed'] += 1
+ # Slapi NIS Plugin
+ if DN(NIS_PLUGIN['dn']) == DN(entry['dn']):
+ # Parent plugin entry
+ self.process_config_entry(
+ entry['dn'], entry['attrs'], NIS_PLUGIN,
+ add_missing=True)
+ stats['config_processed'] += 1
+ elif DN(NIS_PLUGIN['dn']) in DN(entry['dn']):
+ # Child NIS plugin entry
+ nis_dn = entry['dn']
+ lc_remote_realm = self.remote_realm.lower()
+ lc_realm = self.realm.lower()
+ nis_dn = nis_dn.replace(lc_remote_realm, lc_realm)
+ if 'nis-domain' in entry['attrs']:
+ value = entry['attrs']['nis-domain'][0]
+ value = value.replace(lc_remote_realm, lc_realm)
+ entry['attrs']['nis-domain'][0] = value
+ # Process the entry
+ self.process_config_entry(nis_dn, entry['attrs'], NIS_PLUGIN,
+ add_missing=True)
+ stats['config_processed'] += 1
+
#
# Migration
#
diff --git a/ipaserver/install/ipa_migrate_constants.py b/ipaserver/install/ipa_migrate_constants.py
index 65e920d4bdc7511adb60bbdf87db88beb2630645..4fe8b467978936e4f8d621b7297ff577127c7298 100644
--- a/ipaserver/install/ipa_migrate_constants.py
+++ b/ipaserver/install/ipa_migrate_constants.py
@@ -527,6 +527,30 @@ DS_CONFIG = {
},
}
+#
+# Slpai NIS is an optional plugin. It requires special handling
+#
+NIS_PLUGIN = {
+ 'dn': 'cn=NIS Server,cn=plugins,cn=config',
+ 'attrs': [
+ 'nis-domain',
+ 'nis-base',
+ 'nis-map',
+ 'nis-filter',
+ 'nis-key-format:',
+ 'nis-values-format:',
+ 'nis-secure',
+ 'nis-disallowed-chars',
+ # Parent plugin entry
+ 'nsslapd-pluginarg0',
+ 'nsslapd-pluginenabled'
+ ],
+ 'multivalued': [],
+ 'label': 'NIS Server Plugin',
+ 'mode': 'all',
+ 'count': 0,
+}
+
#
# This mapping is simliar to above but it handles container entries
# This could be built into the above mapping using the "comma" approach
diff --git a/ipaserver/install/plugins/update_nis.py b/ipaserver/install/plugins/update_nis.py
new file mode 100644
index 0000000000000000000000000000000000000000..c02eb5f838a99b27cdd21e4aee17e5104f0e22e2
--- /dev/null
+++ b/ipaserver/install/plugins/update_nis.py
@@ -0,0 +1,92 @@
+#
+# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
+#
+
+from __future__ import absolute_import
+
+import logging
+
+from ipalib.plugable import Registry
+from ipalib import errors
+from ipalib import Updater
+from ipaplatform.paths import paths
+from ipapython.dn import DN
+from ipaserver.install import sysupgrade
+from ipaserver.install.ldapupdate import LDAPUpdate
+
+logger = logging.getLogger(__name__)
+
+register = Registry()
+
+
+@register()
+class update_nis_configuration(Updater):
+ """Update NIS configuration
+
+ NIS configuration can be updated only if NIS Server was configured via
+ ipa-nis-manage command.
+ """
+
+ def __recover_from_missing_maps(self, ldap):
+ # https://fedorahosted.org/freeipa/ticket/5507
+ # if all following DNs are missing, but 'NIS Server' container exists
+ # we are experiencig bug and maps should be fixed
+
+ if sysupgrade.get_upgrade_state('nis',
+ 'done_recover_from_missing_maps'):
+ # this recover must be done only once, a user may deleted some
+ # maps, we do not want to restore them again
+ return
+
+ logger.debug("Recovering from missing NIS maps bug")
+
+ suffix = "cn=NIS Server,cn=plugins,cn=config"
+ domain = self.api.env.domain
+ missing_dn_list = [
+ DN(nis_map.format(domain=domain, suffix=suffix)) for nis_map in [
+ "nis-domain={domain}+nis-map=passwd.byname,{suffix}",
+ "nis-domain={domain}+nis-map=passwd.byuid,{suffix}",
+ "nis-domain={domain}+nis-map=group.byname,{suffix}",
+ "nis-domain={domain}+nis-map=group.bygid,{suffix}",
+ "nis-domain={domain}+nis-map=netid.byname,{suffix}",
+ "nis-domain={domain}+nis-map=netgroup,{suffix}",
+ ]
+ ]
+
+ for dn in missing_dn_list:
+ try:
+ ldap.get_entry(dn, attrs_list=['cn'])
+ except errors.NotFound:
+ pass
+ else:
+ # bug is not effective, at least one of 'possible missing'
+ # maps was detected
+ return
+
+ sysupgrade.set_upgrade_state('nis', 'done_recover_from_missing_maps',
+ True)
+
+ # bug is effective run update to recreate missing maps
+ ld = LDAPUpdate(api=self.api)
+ ld.update([paths.NIS_ULDIF])
+
+ def execute(self, **options):
+ ldap = self.api.Backend.ldap2
+ dn = DN(('cn', 'NIS Server'), ('cn', 'plugins'), ('cn', 'config'))
+ try:
+ ldap.get_entry(dn, attrs_list=['cn'])
+ except errors.NotFound:
+ # NIS is not configured on system, do not execute update
+ logger.debug("Skipping NIS update, NIS Server is not configured")
+
+ # container does not exist, bug #5507 is not effective
+ sysupgrade.set_upgrade_state(
+ 'nis', 'done_recover_from_missing_maps', True)
+ else:
+ self.__recover_from_missing_maps(ldap)
+
+ logger.debug("Executing NIS Server update")
+ ld = LDAPUpdate(api=self.api)
+ ld.update([paths.NIS_UPDATE_ULDIF])
+
+ return False, ()
diff --git a/ipatests/test_cmdline/test_cli.py b/ipatests/test_cmdline/test_cli.py
index 6c86bbb657a0d9a7b74ef34ad20a796a10073315..4acf8dd5290c687723a9c43a21e194bf663b6a3b 100644
--- a/ipatests/test_cmdline/test_cli.py
+++ b/ipatests/test_cmdline/test_cli.py
@@ -372,6 +372,7 @@ IPA_CLIENT_NOT_CONFIGURED = b'IPA client is not configured on this system'
'/usr/share/ipa/updates/05-pre_upgrade_plugins.update'],
2, None, IPA_NOT_CONFIGURED),
(['ipa-managed-entries'], 2, None, IPA_NOT_CONFIGURED),
+ (['ipa-nis-manage'], 2, None, IPA_NOT_CONFIGURED),
(['ipa-pkinit-manage'], 2, None, IPA_NOT_CONFIGURED),
(['ipa-replica-manage', 'list'], 1, IPA_NOT_CONFIGURED, None),
(['ipa-server-certinstall'], 2, None, IPA_NOT_CONFIGURED),
diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
index 5bc871ab71bc05ec1c26df8352e996a9e627b466..8f8548494eaec3afd00961c096772b9d0b1ab3a4 100644
--- a/ipatests/test_integration/test_commands.py
+++ b/ipatests/test_integration/test_commands.py
@@ -1382,6 +1382,93 @@ class TestIPACommand(IntegrationTest):
serverid = realm_to_serverid(self.master.domain.realm)
return ("dirsrv@%s.service" % serverid)
+ def test_ipa_nis_manage_enable(self):
+ """
+ This testcase checks if ipa-nis-manage enable
+ command enables plugin on an IPA master
+ """
+ dirsrv_service = self.get_dirsrv_id()
+ console_msg = (
+ "Enabling plugin\n"
+ "This setting will not take effect until "
+ "you restart Directory Server.\n"
+ "The rpcbind service may need to be started"
+ )
+ status_msg = "Plugin is enabled"
+ tasks.kinit_admin(self.master)
+ result = self.master.run_command(
+ ["ipa-nis-manage", "enable"],
+ stdin_text=self.master.config.admin_password,
+ )
+ assert console_msg in result.stdout_text
+ # verify using backend
+ conn = self.master.ldap_connect()
+ dn = DN(('cn', 'NIS Server'), ('cn', 'plugins'), ('cn', 'config'))
+ entry = conn.get_entry(dn)
+ nispluginstring = entry.get('nsslapd-pluginEnabled')
+ assert 'on' in nispluginstring
+ # restart for changes to take effect
+ self.master.run_command(["systemctl", "restart", dirsrv_service])
+ self.master.run_command(["systemctl", "restart", "rpcbind"])
+ time.sleep(DIRSRV_SLEEP)
+ # check status msg on the console
+ result = self.master.run_command(
+ ["ipa-nis-manage", "status"],
+ stdin_text=self.master.config.admin_password,
+ )
+ assert status_msg in result.stdout_text
+
+ def test_ipa_nis_manage_disable(self):
+ """
+ This testcase checks if ipa-nis-manage disable
+ command disable plugin on an IPA Master
+ """
+ dirsrv_service = self.get_dirsrv_id()
+ msg = (
+ "This setting will not take effect "
+ "until you restart Directory Server."
+ )
+ status_msg = "Plugin is not enabled"
+ tasks.kinit_admin(self.master)
+ result = self.master.run_command(
+ ["ipa-nis-manage", "disable"],
+ stdin_text=self.master.config.admin_password,
+ )
+ assert msg in result.stdout_text
+ # verify using backend
+ conn = self.master.ldap_connect()
+ dn = DN(('cn', 'NIS Server'), ('cn', 'plugins'), ('cn', 'config'))
+ entry = conn.get_entry(dn)
+ nispluginstring = entry.get('nsslapd-pluginEnabled')
+ assert 'off' in nispluginstring
+ # restart dirsrv for changes to take effect
+ self.master.run_command(["systemctl", "restart", dirsrv_service])
+ time.sleep(DIRSRV_SLEEP)
+ # check status msg on the console
+ result = self.master.run_command(
+ ["ipa-nis-manage", "status"],
+ stdin_text=self.master.config.admin_password,
+ raiseonerr=False,
+ )
+ assert result.returncode == 4
+ assert status_msg in result.stdout_text
+
+ def test_ipa_nis_manage_enable_incorrect_password(self):
+ """
+ This testcase checks if ipa-nis-manage enable
+ command throws error on console for invalid DS admin password
+ """
+ msg1 = "Insufficient access: "
+ msg2 = "Invalid credentials"
+ result = self.master.run_command(
+ ["ipa-nis-manage", "enable"],
+ stdin_text='Invalid_pwd',
+ raiseonerr=False,
+ )
+ assert result.returncode == 1
+ assert msg1 in result.stderr_text
+ assert msg2 in result.stderr_text
+
def test_pkispawn_log_is_present(self):
"""
This testcase checks if pkispawn logged properly.
--
2.52.0

View File

@ -1,265 +0,0 @@
From 013be398bced31f567ef01ac2471cb7529789b4a Mon Sep 17 00:00:00 2001
From: Julien Rische <jrische@redhat.com>
Date: Mon, 9 Oct 2023 15:47:03 +0200
Subject: [PATCH] ipa-kdb: Detect and block Bronze-Bit attacks
The C8S/RHEL8 version of FreeIPA is vulnerable to the Bronze-Bit attack
because it does not implement PAC ticket signature to protect the
"forwardable" flag. However, it does implement the PAC extended KDC
signature, which protects against PAC spoofing.
Based on information available in the PAC and the
"ok-to-auth-as-delegate" attribute in the database. It is possible to
detect and reject requests where the "forwardable" flag was flipped by
the attacker in the evidence ticket.
---
daemons/ipa-kdb/ipa_kdb.h | 13 +++
daemons/ipa-kdb/ipa_kdb_kdcpolicy.c | 6 +
daemons/ipa-kdb/ipa_kdb_mspac.c | 173 ++++++++++++++++++++++++++++
ipaserver/install/server/install.py | 8 ++
4 files changed, 200 insertions(+)
diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h
index 7aa5be494..02b2cb631 100644
--- a/daemons/ipa-kdb/ipa_kdb.h
+++ b/daemons/ipa-kdb/ipa_kdb.h
@@ -367,6 +367,19 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
const char *test_realm, size_t size,
char **trusted_realm);
+/* Try to detect a Bronze-Bit attack based on the content of the request and
+ * data from the KDB.
+ *
+ * context krb5 context
+ * request KDB request
+ * detected Set to "true" if a bronze bit attack is detected and the
+ * pointer is not NULL. Remains unset otherwise.
+ * status If the call fails and the pointer is not NULL, set it with a
+ * message describing the cause of the failure. */
+krb5_error_code
+ipadb_check_for_bronze_bit_attack(krb5_context context, krb5_kdc_req *request,
+ bool *detected, const char **status);
+
/* DELEGATION CHECKS */
krb5_error_code ipadb_check_allowed_to_delegate(krb5_context kcontext,
diff --git a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
index f2804c9b2..1032dff0b 100644
--- a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
+++ b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
@@ -185,6 +185,12 @@ ipa_kdcpolicy_check_tgs(krb5_context context, krb5_kdcpolicy_moddata moddata,
const char **status, krb5_deltat *lifetime_out,
krb5_deltat *renew_lifetime_out)
{
+ krb5_error_code kerr;
+
+ kerr = ipadb_check_for_bronze_bit_attack(context, request, NULL, status);
+ if (kerr)
+ return KRB5KDC_ERR_POLICY;
+
*status = NULL;
*lifetime_out = 0;
*renew_lifetime_out = 0;
diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
index 83cb9914d..b4e22d431 100644
--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
+++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
@@ -3298,3 +3298,176 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
return KRB5_KDB_NOENTRY;
}
+
+krb5_error_code
+ipadb_check_for_bronze_bit_attack(krb5_context context, krb5_kdc_req *request,
+ bool *detected, const char **status)
+{
+ krb5_error_code kerr;
+ const char *st = NULL;
+ size_t i, j;
+ krb5_ticket *evidence_tkt;
+ krb5_authdata **authdata, **ifrel = NULL;
+ krb5_pac pac = NULL;
+ TALLOC_CTX *tmpctx = NULL;
+ krb5_data fullsign = { 0, 0, NULL }, linfo_blob = { 0, 0, NULL };
+ DATA_BLOB linfo_data;
+ struct PAC_LOGON_INFO_CTR linfo;
+ enum ndr_err_code ndr_err;
+ struct dom_sid asserted_identity_sid;
+ bool evtkt_is_s4u2self = false;
+ krb5_db_entry *proxy_entry = NULL;
+
+ /* If no additional ticket, this is not a constrained delegateion request.
+ * Skip checks. */
+ if (!(request->kdc_options & KDC_OPT_CNAME_IN_ADDL_TKT)) {
+ kerr = 0;
+ goto end;
+ }
+
+ evidence_tkt = request->second_ticket[0];
+
+ /* No need to check the Forwardable flag. If it was not set, this request
+ * would have failed earlier. */
+
+ /* We only support general constrained delegation (not RBCD), which is not
+ * available for cross-realms. */
+ if (!krb5_realm_compare(context, evidence_tkt->server, request->server)) {
+ st = "S4U2PROXY_NOT_SUPPORTED_FOR_CROSS_REALMS";
+ kerr = ENOTSUP;
+ goto end;
+ }
+
+ authdata = evidence_tkt->enc_part2->authorization_data;
+
+ /* Search for the PAC. */
+ for (i = 0; authdata != NULL && authdata[i] != NULL; i++) {
+ if (authdata[i]->ad_type != KRB5_AUTHDATA_IF_RELEVANT)
+ continue;
+
+ kerr = krb5_decode_authdata_container(context,
+ KRB5_AUTHDATA_IF_RELEVANT,
+ authdata[i], &ifrel);
+ if (kerr) {
+ st = "S4U2PROXY_CANNOT_DECODE_EVIDENCE_TKT_AUTHDATA";
+ goto end;
+ }
+
+ for (j = 0; ifrel[j] != NULL; j++) {
+ if (ifrel[j]->ad_type == KRB5_AUTHDATA_WIN2K_PAC)
+ break;
+ }
+ if (ifrel[j] != NULL)
+ break;
+
+ krb5_free_authdata(context, ifrel);
+ ifrel = NULL;
+ }
+
+ if (ifrel == NULL) {
+ st = "S4U2PROXY_EVIDENCE_TKT_WITHOUT_PAC";
+ kerr = ENOENT;
+ goto end;
+ }
+
+ /* Parse the PAC. */
+ kerr = krb5_pac_parse(context, ifrel[j]->contents, ifrel[j]->length, &pac);
+ if (kerr) {
+ st = "S4U2PROXY_CANNOT_DECODE_EVICENCE_TKT_PAC";
+ goto end;
+ }
+
+ /* Check that the PAC extanded KDC signature is present. If it is, it was
+ * already tested.
+ * If absent, the context of the PAC cannot be trusted. */
+ kerr = krb5_pac_get_buffer(context, pac, KRB5_PAC_FULL_CHECKSUM, &fullsign);
+ if (kerr) {
+ st = "S4U2PROXY_MISSING_EXTENDED_KDC_SIGN_IN_EVIDENCE_TKT_PAC";
+ goto end;
+ }
+
+ /* Get the PAC Logon Info. */
+ kerr = krb5_pac_get_buffer(context, pac, KRB5_PAC_LOGON_INFO, &linfo_blob);
+ if (kerr) {
+ st = "S4U2PROXY_NO_PAC_LOGON_INFO_IN_EVIDENCE_TKT";
+ goto end;
+ }
+
+ /* Parse the PAC Logon Info. */
+ tmpctx = talloc_new(NULL);
+ if (!tmpctx) {
+ st = "OUT_OF_MEMORY";
+ kerr = ENOMEM;
+ goto end;
+ }
+
+ linfo_data.length = linfo_blob.length;
+ linfo_data.data = (uint8_t *)linfo_blob.data;
+ ndr_err = ndr_pull_union_blob(&linfo_data, tmpctx, &linfo,
+ PAC_TYPE_LOGON_INFO,
+ (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ st = "S4U2PROXY_CANNOT_PARSE_ENVIDENCE_TKT_PAC_LOGON_INFO";
+ kerr = EINVAL;
+ goto end;
+ }
+
+ /* Check that the extra SIDs array is not empty. */
+ if (linfo.info->info3.sidcount == 0) {
+ st = "S4U2PROXY_NO_EXTRA_SID";
+ kerr = ENOENT;
+ goto end;
+ }
+
+ /* Search for the S-1-18-2 domain SID, which indicates the ticket was
+ * obtained using S4U2Self */
+ kerr = ipadb_string_to_sid("S-1-18-2", &asserted_identity_sid);
+ if (kerr) {
+ st = "S4U2PROXY_CANNOT_CREATE_ASSERTED_IDENTITY_SID";
+ goto end;
+ }
+
+ for (i = 0; i < linfo.info->info3.sidcount; i++) {
+ if (dom_sid_check(&asserted_identity_sid,
+ linfo.info->info3.sids[0].sid, true)) {
+ evtkt_is_s4u2self = true;
+ break;
+ }
+ }
+
+ /* If the ticket was obtained using S4U2Self, the proxy principal entry must
+ * have the "ok_to_auth_as_delegate" attribute set to true. */
+ if (evtkt_is_s4u2self) {
+ kerr = ipadb_get_principal(context, evidence_tkt->server, 0,
+ &proxy_entry);
+ if (kerr) {
+ st = "S4U2PROXY_CANNOT_FIND_PROXY_PRINCIPAL";
+ goto end;
+ }
+
+ if (!(proxy_entry->attributes & KRB5_KDB_OK_TO_AUTH_AS_DELEGATE)) {
+ /* This evidence ticket cannot be forwardable given the privileges
+ * of the proxy principal.
+ * This is a Bronze Bit attack. */
+ if (detected)
+ *detected = true;
+ st = "S4U2PROXY_BRONZE_BIT_ATTACK_DETECTED";
+ kerr = EBADE;
+ goto end;
+ }
+ }
+
+ kerr = 0;
+
+end:
+ if (st && status)
+ *status = st;
+
+ krb5_free_authdata(context, ifrel);
+ krb5_pac_free(context, pac);
+ krb5_free_data_contents(context, &linfo_blob);
+ krb5_free_data_contents(context, &fullsign);
+ talloc_free(tmpctx);
+ ipadb_free_principal(context, proxy_entry);
+ return kerr;
+}
diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
index 4e4076410..bfbb83bcb 100644
--- a/ipaserver/install/server/install.py
+++ b/ipaserver/install/server/install.py
@@ -981,6 +981,14 @@ def install(installer):
# Set the admin user kerberos password
ds.change_admin_password(admin_password)
+ # Force KDC to refresh the cached value of ipaKrbAuthzData by restarting.
+ # ipaKrbAuthzData has to be set with "MS-PAC" to trigger PAC generation,
+ # which is required to handle S4U2Proxy with the Bronze-Bit fix.
+ # Not doing so would cause API malfunction for around a minute, which is
+ # long enough to cause the hereafter client installation to fail.
+ service.print_msg("Restarting the KDC")
+ krb.restart()
+
# Call client install script
service.print_msg("Configuring client side components")
try:
--
2.41.0

View File

@ -1,212 +0,0 @@
From 3add9ba03a0af913d03b1f5ecaa8e48e46a93f91 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Jan 15 2024 13:42:08 +0000
Subject: Server affinity: Retain user-requested remote server
We want to avoid splitting a replica server installation between
two hosts where possible so if a CA or KRA is requested then
we only try to install against a remote server that also provides
those capabilities. This avoids race conditions.
If a CA or KRA is not requested and the user has provided a
server to install against then use that instead of overriding it.
Extend the logic of picking the remote Custodia mode
(KRA, CA, *MASTER*) to include considering whether the
CA and KRA services are requested. If the service(s) are
not requested the the associated hostname may not be
reliable.
Fixes: https://pagure.io/freeipa/issue/9491
Related: https://pagure.io/freeipa/issue/9289
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
---
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
index 27fbdef..8096b6a 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -782,6 +782,7 @@ def promotion_check_host_principal_auth_ind(conn, hostdn):
def remote_connection(config):
+ logger.debug("Creating LDAP connection to %s", config.master_host_name)
ldapuri = 'ldaps://%s' % ipautil.format_netloc(config.master_host_name)
xmlrpc_uri = 'https://{}/ipa/xml'.format(
ipautil.format_netloc(config.master_host_name))
@@ -1087,7 +1088,7 @@ def promote_check(installer):
'CA', conn, preferred_cas
)
if ca_host is not None:
- if config.master_host_name != ca_host:
+ if options.setup_ca and config.master_host_name != ca_host:
conn.disconnect()
del remote_api
config.master_host_name = ca_host
@@ -1096,8 +1097,7 @@ def promote_check(installer):
conn = remote_api.Backend.ldap2
conn.connect(ccache=installer._ccache)
config.ca_host_name = ca_host
- config.master_host_name = ca_host
- ca_enabled = True
+ ca_enabled = True # There is a CA somewhere in the topology
if options.dirsrv_cert_files:
logger.error("Certificates could not be provided when "
"CA is present on some master.")
@@ -1135,7 +1135,7 @@ def promote_check(installer):
'KRA', conn, preferred_kras
)
if kra_host is not None:
- if config.master_host_name != kra_host:
+ if options.setup_kra and config.master_host_name != kra_host:
conn.disconnect()
del remote_api
config.master_host_name = kra_host
@@ -1143,10 +1143,9 @@ def promote_check(installer):
installer._remote_api = remote_api
conn = remote_api.Backend.ldap2
conn.connect(ccache=installer._ccache)
- config.kra_host_name = kra_host
- config.ca_host_name = kra_host
- config.master_host_name = kra_host
- kra_enabled = True
+ config.kra_host_name = kra_host
+ config.ca_host_name = kra_host
+ kra_enabled = True # There is a KRA somewhere in the topology
if options.setup_kra and options.server and \
kra_host != options.server:
# Installer was provided with a specific master
@@ -1372,10 +1371,10 @@ def install(installer):
otpd.create_instance('OTPD', config.host_name,
ipautil.realm_to_suffix(config.realm_name))
- if kra_enabled:
+ if options.setup_kra and kra_enabled:
# A KRA peer always provides a CA, too.
mode = custodiainstance.CustodiaModes.KRA_PEER
- elif ca_enabled:
+ elif options.setup_ca and ca_enabled:
mode = custodiainstance.CustodiaModes.CA_PEER
else:
mode = custodiainstance.CustodiaModes.MASTER_PEER
From 701339d4fed539713eb1a13495992879f56a6daa Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Jan 18 2024 14:53:28 +0000
Subject: Server affinity: Don't rely just on [ca|kra]_enabled for installs
ca_enable and kra_enabled are intended to be used to identify that
a CA or KRA is available in the topology. It was also being used
to determine whether a CA or KRA service is desired on a replica
install, rather than options.setup_[ca|kra]
Fixes: https://pagure.io/freeipa/issue/9510
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
---
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
index 8096b6a..191913d 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -1143,7 +1143,8 @@ def promote_check(installer):
installer._remote_api = remote_api
conn = remote_api.Backend.ldap2
conn.connect(ccache=installer._ccache)
- config.kra_host_name = kra_host
+ config.kra_host_name = kra_host
+ if options.setup_kra: # only reset ca_host if KRA is requested
config.ca_host_name = kra_host
kra_enabled = True # There is a KRA somewhere in the topology
if options.setup_kra and options.server and \
@@ -1381,7 +1382,7 @@ def install(installer):
custodia = custodiainstance.get_custodia_instance(config, mode)
custodia.create_instance()
- if ca_enabled:
+ if options.setup_ca and ca_enabled:
options.realm_name = config.realm_name
options.domain_name = config.domain_name
options.host_name = config.host_name
@@ -1397,7 +1398,7 @@ def install(installer):
service.print_msg("Finalize replication settings")
ds.finalize_replica_config()
- if kra_enabled:
+ if options.setup_kra and kra_enabled:
kra.install(api, config, options, custodia=custodia)
service.print_msg("Restarting the KDC")
From e6014a5c1996528b255480b67fe2937203bff81b Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Jan 23 2024 15:32:58 +0000
Subject: Server affinity: call ca.install() if there is a CA in the topology
This should not have been gated on options.setup_ca because we need
the RA agent on all servers if there is a CA in the topology otherwise
the non-CA servers won't be able to communicate with the CA.
Fixes: https://pagure.io/freeipa/issue/9510
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
---
diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py
index c93ae1f..187f803 100644
--- a/ipaserver/install/ca.py
+++ b/ipaserver/install/ca.py
@@ -387,9 +387,10 @@ def install_step_0(standalone, replica_config, options, custodia):
promote = False
else:
cafile = os.path.join(replica_config.dir, 'cacert.p12')
- custodia.get_ca_keys(
- cafile,
- replica_config.dirman_password)
+ if replica_config.setup_ca:
+ custodia.get_ca_keys(
+ cafile,
+ replica_config.dirman_password)
ca_signing_algorithm = None
ca_type = None
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
index f8d4733..4c1c07c 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -1359,11 +1359,13 @@ def install(installer):
custodia = custodiainstance.get_custodia_instance(config, mode)
custodia.create_instance()
- if options.setup_ca and ca_enabled:
+ if ca_enabled:
options.realm_name = config.realm_name
options.domain_name = config.domain_name
options.host_name = config.host_name
options.dm_password = config.dirman_password
+ # Always call ca.install() if there is a CA in the topology
+ # to ensure the RA agent is present.
ca.install(False, config, options, custodia=custodia)
# configure PKINIT now that all required services are in place
@@ -1375,7 +1377,8 @@ def install(installer):
service.print_msg("Finalize replication settings")
ds.finalize_replica_config()
- if options.setup_kra and kra_enabled:
+ if kra_enabled:
+ # The KRA installer checks for itself the status of setup_kra
kra.install(api, config, options, custodia=custodia)
service.print_msg("Restarting the KDC")

View File

@ -0,0 +1,321 @@
From 257740b66daf004a7333bfaeddece4732be5a48c Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
Date: Tue, 9 Dec 2025 17:52:36 +0100
Subject: [PATCH] Revert "Stop using deprecated pkg_resources"
This reverts commit ac791f7372d32d25c75eb61f949f1db38fe2f0d6.
---
freeipa.spec.in | 6 +++++-
ipaplatform/base/tasks.py | 2 +-
ipapython/version.py.in | 2 +-
ipaserver/custodia/server/__init__.py | 13 +++++++++----
ipaserver/install/cainstance.py | 2 +-
ipaserver/install/krbinstance.py | 2 +-
ipaserver/install/server/replicainstall.py | 2 +-
ipaserver/install/server/upgrade.py | 2 +-
.../pytest_ipa/integration/create_caless_pki.py | 2 +-
ipatests/pytest_ipa/integration/tasks.py | 2 +-
ipatests/test_custodia/test_plugins.py | 13 ++++++++-----
ipatests/test_integration/test_adtrust_install.py | 2 +-
ipatests/test_integration/test_cert.py | 2 +-
ipatests/test_integration/test_commands.py | 2 +-
ipatests/test_integration/test_idp.py | 2 +-
ipatests/test_integration/test_ipahealthcheck.py | 2 +-
ipatests/test_webui/ui_driver.py | 2 +-
ipatests/test_xmlrpc/test_automember_plugin.py | 1 +
18 files changed, 37 insertions(+), 24 deletions(-)
diff --git a/freeipa.spec.in b/freeipa.spec.in
index b93199a5783a89e0ff58affd3d416556f72dd00c..bf6fa216dcc4cfbac903aad43c6219f1e57db4dd 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -983,7 +983,6 @@ Requires: python3-jwcrypto >= 0.4.2
Requires: python3-libipa_hbac
Requires: python3-netaddr >= %{python_netaddr_version}
Requires: python3-netifaces >= 0.10.4
-Requires: python3-packaging
Requires: python3-pyasn1 >= 0.3.2-2
Requires: python3-pyasn1-modules >= 0.3.2-2
Requires: python3-pyusb
@@ -992,6 +991,11 @@ Requires: python3-requests
Requires: python3-six
Requires: python3-sss-murmur
Requires: python3-yubico >= 1.3.2-7
+%if 0%{?rhel} && 0%{?rhel} == 8
+Requires: platform-python-setuptools
+%else
+Requires: python3-setuptools
+%endif
%if 0%{?rhel}
Requires: python3-urllib3 >= 1.24.2-3
%else
diff --git a/ipaplatform/base/tasks.py b/ipaplatform/base/tasks.py
index 9e221d872e7ca9ac0607ff29e1b51dedcf688d75..ab3563aba3decf370a72c9ec273c8bccc2d85ca2 100644
--- a/ipaplatform/base/tasks.py
+++ b/ipaplatform/base/tasks.py
@@ -29,7 +29,7 @@ import os
import logging
import textwrap
-from packaging.version import parse as parse_version
+from pkg_resources import parse_version
from ipaplatform.paths import paths
from ipapython import ipautil
diff --git a/ipapython/version.py.in b/ipapython/version.py.in
index eee8900be5fa72759fd7ee87f72952599231e138..a8f4218a7bac2a2f52389ad3dc5f31a98a822e82 100644
--- a/ipapython/version.py.in
+++ b/ipapython/version.py.in
@@ -17,7 +17,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
-from packaging.version import parse as parse_version
+from pkg_resources import parse_version
# The full version including strings
VERSION = "@VERSION@"
diff --git a/ipaserver/custodia/server/__init__.py b/ipaserver/custodia/server/__init__.py
index 26ca0a481559da101891034798e0434f063367b8..e713de20d0e9ff42e65ed3d2170d6446baa8d4e8 100644
--- a/ipaserver/custodia/server/__init__.py
+++ b/ipaserver/custodia/server/__init__.py
@@ -2,9 +2,10 @@
from __future__ import absolute_import
import importlib
-import importlib.metadata
import os
+import pkg_resources
+
import six
from ipaserver.custodia import log
@@ -36,13 +37,17 @@ def _load_plugin_class(menu, name):
Entry points are preferred over dotted import path.
"""
group = 'custodia.{}'.format(menu)
- eps = importlib.metadata.entry_points(group=group, name=name)
+ eps = list(pkg_resources.iter_entry_points(group, name))
if len(eps) > 1:
raise ValueError(
"Multiple entry points for {} {}: {}".format(menu, name, eps))
elif len(eps) == 1:
- ep, *_ = eps
- return ep.load(require=False)
+ # backwards compatibility with old setuptools
+ ep = eps[0]
+ if hasattr(ep, 'resolve'):
+ return ep.resolve()
+ else:
+ return ep.load(require=False)
elif '.' in name:
# fall back to old style dotted name
module, classname = name.rsplit('.', 1)
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index b8267a625554f9375d27160f39b67ee2e64a2dbb..a92b3ed6f01db97a86464e5cb77a084f8a1de90f 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -35,7 +35,7 @@ import syslog
import time
import tempfile
from configparser import RawConfigParser
-from packaging.version import parse as parse_version
+from pkg_resources import parse_version
from ipalib import api
from ipalib import x509
diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py
index 4a521a611bd1bebf925061ea88a60876dff48467..0f568122c94aab8e55604179003276d29580de76 100644
--- a/ipaserver/install/krbinstance.py
+++ b/ipaserver/install/krbinstance.py
@@ -27,7 +27,7 @@ import tempfile
import dbus
import dns.name
-from packaging.version import parse as parse_version
+from pkg_resources import parse_version
from ipalib import x509
from ipalib.install import certstore
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
index b4d06d8e24097e12c97a8b881dfcc9b028467272..5b4f0161083bbf65ac423ebacad30c5743ffab8e 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -18,7 +18,7 @@ import tempfile
import textwrap
import traceback
-from packaging.version import parse as parse_version
+from pkg_resources import parse_version
import six
from ipaclient.install.client import check_ldap_conf, sssd_enable_ifp
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
index 548ee02e1e8524ce0002dca1764d48728eb0509a..d9cb6656b4731b5cb803b870469635862f4269e5 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -17,7 +17,7 @@ import sys
import tempfile
from contextlib import contextmanager
from augeas import Augeas
-from packaging.version import parse as parse_version
+from pkg_resources import parse_version
from ipalib import api, x509
from ipalib.constants import RENEWAL_CA_NAME, RA_AGENT_PROFILE, IPA_CA_RECORD
diff --git a/ipatests/pytest_ipa/integration/create_caless_pki.py b/ipatests/pytest_ipa/integration/create_caless_pki.py
index d06f1dd8c328628bd692c2abf3acfc88ba6a7408..01d64462ce76eb76cf7efdbeb850955ca4990535 100644
--- a/ipatests/pytest_ipa/integration/create_caless_pki.py
+++ b/ipatests/pytest_ipa/integration/create_caless_pki.py
@@ -26,7 +26,7 @@ from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
-from packaging.version import parse as parse_version
+from pkg_resources import parse_version
from pyasn1.type import univ, char, namedtype, tag
from pyasn1.codec.der import encoder as der_encoder
from pyasn1.codec.native import decoder as native_decoder
diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py
index 3ef361807ee0e1c858f4bef8b7d9e26c5f0fa20a..c6e0cf3937a12c7bb13da4b83250ecced470b543 100755
--- a/ipatests/pytest_ipa/integration/tasks.py
+++ b/ipatests/pytest_ipa/integration/tasks.py
@@ -36,7 +36,7 @@ import time
from shlex import quote
import configparser
from contextlib import contextmanager
-from packaging.version import parse as parse_version
+from pkg_resources import parse_version
import uuid
import dns
diff --git a/ipatests/test_custodia/test_plugins.py b/ipatests/test_custodia/test_plugins.py
index 9d3f6c24ff2eecad9e9a1c574aaae2edd30e4921..be8aa936db2bc553e0a13bb0975e88d186e3da1c 100644
--- a/ipatests/test_custodia/test_plugins.py
+++ b/ipatests/test_custodia/test_plugins.py
@@ -1,6 +1,5 @@
# Copyright (C) 2016 Custodia Project Contributors - see LICENSE file
-import importlib.metadata
-
+import pkg_resources
import pytest
from ipaserver.custodia.plugin import (
@@ -13,8 +12,8 @@ class TestCustodiaPlugins:
def get_entry_points(self, group):
eps = []
- for e in importlib.metadata.entry_points(group=group):
- if e.dist.name != self.project_name:
+ for e in pkg_resources.iter_entry_points(group):
+ if e.dist.project_name != self.project_name:
# only interested in our own entry points
continue
eps.append(e)
@@ -22,7 +21,11 @@ class TestCustodiaPlugins:
def assert_ep(self, ep, basecls):
try:
- cls = ep.load(require=False)
+ # backwards compatibility with old setuptools
+ if hasattr(ep, "resolve"):
+ cls = ep.resolve()
+ else:
+ cls = ep.load(require=False)
except Exception as e: # pylint: disable=broad-except
pytest.fail("Failed to load %r: %r" % (ep, e))
if not issubclass(cls, basecls):
diff --git a/ipatests/test_integration/test_adtrust_install.py b/ipatests/test_integration/test_adtrust_install.py
index 09e227ec8125e90b37d1d92f0512f9819f5b48c3..ac7e5663aa1c70184f5a09ea5e14cca8c671d78e 100644
--- a/ipatests/test_integration/test_adtrust_install.py
+++ b/ipatests/test_integration/test_adtrust_install.py
@@ -13,7 +13,7 @@ from ipaplatform.paths import paths
from ipapython.dn import DN
from ipatests.pytest_ipa.integration import tasks
from ipatests.test_integration.base import IntegrationTest
-from packaging.version import parse as parse_version
+from pkg_resources import parse_version
import pytest
diff --git a/ipatests/test_integration/test_cert.py b/ipatests/test_integration/test_cert.py
index 21568c2421c21855df06bcf5fbb4d52b3651a523..03ea5cfb000200d4d77b97d8808e30dbd5392871 100644
--- a/ipatests/test_integration/test_cert.py
+++ b/ipatests/test_integration/test_cert.py
@@ -20,7 +20,7 @@ from ipapython.dn import DN
from cryptography import x509
from cryptography.x509.oid import ExtensionOID
from cryptography.hazmat.backends import default_backend
-from packaging.version import parse as parse_version
+from pkg_resources import parse_version
from ipatests.pytest_ipa.integration import tasks
from ipatests.test_integration.base import IntegrationTest
diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
index 5bc871ab71bc05ec1c26df8352e996a9e627b466..04d8f71ff922f98c71331fa9a5f0f6508dd3fc12 100644
--- a/ipatests/test_integration/test_commands.py
+++ b/ipatests/test_integration/test_commands.py
@@ -41,7 +41,7 @@ from ipatests.test_ipalib.test_x509 import good_pkcs7, badcert
from ipapython.ipautil import realm_to_suffix, ipa_generate_password
from ipatests.test_integration.test_topology import find_segment
from ipaserver.install.installutils import realm_to_serverid
-from packaging.version import parse as parse_version
+from pkg_resources import parse_version
logger = logging.getLogger(__name__)
diff --git a/ipatests/test_integration/test_idp.py b/ipatests/test_integration/test_idp.py
index 9983b3c9d62826afa395c2739b4fb9945afed80d..a29333ef232b0ae8229484e404866d8bb720554a 100644
--- a/ipatests/test_integration/test_idp.py
+++ b/ipatests/test_integration/test_idp.py
@@ -12,7 +12,7 @@ from ipatests.pytest_ipa.integration import tasks, create_keycloak
user_code_script = textwrap.dedent("""
from selenium import webdriver
from datetime import datetime
-from packaging.version import parse as parse_version
+from pkg_resources import parse_version
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
index 47392cec9345b6d2447b728fc21050edabe8f0cd..15fc38a6e155b343259ee61c6c7c5c561c7f6410 100644
--- a/ipatests/test_integration/test_ipahealthcheck.py
+++ b/ipatests/test_integration/test_ipahealthcheck.py
@@ -28,7 +28,7 @@ from ipaplatform.osinfo import osinfo
from ipaserver.install.installutils import resolve_ip_addresses_nss
from ipatests.test_integration.test_caless import CALessBase
from ipatests.test_integration.base import IntegrationTest
-from packaging.version import parse as parse_version
+from pkg_resources import parse_version
from ipatests.test_integration.test_cert import get_certmonger_fs_id
from ipatests.test_integration.test_external_ca import (
install_server_external_ca_step1,
diff --git a/ipatests/test_webui/ui_driver.py b/ipatests/test_webui/ui_driver.py
index 356c4a0998cf01a6dbf1f207f38db87504aa5522..5dcea8979346119b2c11af7cf3f0b8c9b438cb40 100644
--- a/ipatests/test_webui/ui_driver.py
+++ b/ipatests/test_webui/ui_driver.py
@@ -29,7 +29,7 @@ import re
import time
from datetime import datetime
from functools import wraps
-from packaging.version import parse as parse_version
+from pkg_resources import parse_version
from urllib.error import URLError
import pytest
diff --git a/ipatests/test_xmlrpc/test_automember_plugin.py b/ipatests/test_xmlrpc/test_automember_plugin.py
index aa7c1d65a7059a6a1e911f31204c05a42b6d9c3f..94f1b10985de61046bb65c092ab3d31c6f99e17c 100644
--- a/ipatests/test_xmlrpc/test_automember_plugin.py
+++ b/ipatests/test_xmlrpc/test_automember_plugin.py
@@ -35,6 +35,7 @@ from ipaserver.plugins.automember import REBUILD_TASK_CONTAINER
import time
import pytest
+from pkg_resources import parse_version
try:
from ipaserver.plugins.ldap2 import ldap2
--
2.52.0

View File

@ -1,97 +0,0 @@
From 3842116185de6ae8714f30b57bd75c7eddde53d8 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
Date: Jan 15 2024 13:50:10 +0000
Subject: host: update System: Manage Host Keytab permission
Since commit 5c0e7a5fb420377dcc06a956695afdcb35196444, a new extended
operation to get a keytab is supposed to be used. This keytab
setting/retrieval extended operation checks access rights of the bound
DN to write to a virtual attribute 'ipaProtectedOperation;write_keys'.
If the write isn't allowed, the operation is rejected and ipa-getkeytab
tool falls back to an older code that generates the keytab on the client
and forcibly sets to the LDAP entry. For the latter, a check is done to
make sure the bound DN is allowed to write to 'krbPrincipalKey' attribute.
This fallback should never happen for newer deployments. When enrollemnt
operation is delegated to non-administrative user with the help of 'Host
Enrollment' role, a host can be pre-created or created at enrollment
time, if this non-administrative user has 'Host Administrators' role. In
the latter case a system permission 'System: Manage Host Keytab' grants
write access to 'krbPrincipalKey' attribute but lacks any access to the
virtual attributes expected by the new extended operation.
There is a second virtual attribute, 'ipaProtectedOperation;read_keys',
that allows to retrieve existing keys for a host. However, during
initial enrollment we do not allow to retrieve and reuse existing
Kerberos key: while 'ipa-getkeytab -r' would give ability to retrieve
the existing key, 'ipa-join' has no way to trigger that operation.
Hence, permission 'System: Manage Host Keytab' will not grant the right
to read the Kerberos key via extended operation used by 'ipa-getkeytab
-r'. Such operation can be done later by utilizing 'ipa
service/host-allow-retrieve-keytab' commands.
Fix 'System: Manage Host Keytab' permission and extend a permission test
to see that we do not fallback to the old extended operation.
Fixes: https://pagure.io/freeipa/issue/9496
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
---
diff --git a/ACI.txt b/ACI.txt
index e6d6e3d..236bb43 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -147,7 +147,7 @@ aci: (targetattr = "usercertificate")(targetfilter = "(objectclass=ipahost)")(ve
dn: cn=computers,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "userpassword")(targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Manage Host Enrollment Password";allow (write) groupdn = "ldap:///cn=System: Manage Host Enrollment Password,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=computers,cn=accounts,dc=ipa,dc=example
-aci: (targetattr = "krblastpwdchange || krbprincipalkey")(targetfilter = "(&(!(memberOf=cn=ipaservers,cn=hostgroups,cn=accounts,dc=ipa,dc=example))(objectclass=ipahost))")(version 3.0;acl "permission:System: Manage Host Keytab";allow (write) groupdn = "ldap:///cn=System: Manage Host Keytab,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+aci: (targetattr = "ipaprotectedoperation;write_keys || krblastpwdchange || krbprincipalkey")(targetfilter = "(&(!(memberOf=cn=ipaservers,cn=hostgroups,cn=accounts,dc=ipa,dc=example))(objectclass=ipahost))")(version 3.0;acl "permission:System: Manage Host Keytab";allow (write) groupdn = "ldap:///cn=System: Manage Host Keytab,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=computers,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "createtimestamp || entryusn || ipaallowedtoperform;read_keys || ipaallowedtoperform;write_keys || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Manage Host Keytab Permissions";allow (compare,read,search,write) groupdn = "ldap:///cn=System: Manage Host Keytab Permissions,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=computers,cn=accounts,dc=ipa,dc=example
diff --git a/ipaserver/plugins/host.py b/ipaserver/plugins/host.py
index 3ef510e..b02c8b5 100644
--- a/ipaserver/plugins/host.py
+++ b/ipaserver/plugins/host.py
@@ -409,7 +409,8 @@ class host(LDAPObject):
api.env.container_hostgroup,
api.env.basedn),
],
- 'ipapermdefaultattr': {'krblastpwdchange', 'krbprincipalkey'},
+ 'ipapermdefaultattr': {'krblastpwdchange', 'krbprincipalkey',
+ 'ipaprotectedoperation;write_keys'},
'replaces': [
'(targetattr = "krbprincipalkey || krblastpwdchange")(target = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Manage host keytab";allow (write) groupdn = "ldap:///cn=Manage host keytab,cn=permissions,cn=pbac,$SUFFIX";)',
],
diff --git a/ipatests/test_integration/test_user_permissions.py b/ipatests/test_integration/test_user_permissions.py
index 3333a4f..cd1096f 100644
--- a/ipatests/test_integration/test_user_permissions.py
+++ b/ipatests/test_integration/test_user_permissions.py
@@ -277,6 +277,9 @@ class TestInstallClientNoAdmin(IntegrationTest):
self.master.run_command(['ipa', 'privilege-add-permission',
'--permissions', 'System: Add Hosts',
'Add Hosts'])
+ self.master.run_command(['ipa', 'privilege-add-permission',
+ '--permissions', 'System: Manage Host Keytab',
+ 'Add Hosts'])
self.master.run_command(['ipa', 'role-add-privilege', 'useradmin',
'--privileges', 'Host Enrollment'])
@@ -301,6 +304,10 @@ class TestInstallClientNoAdmin(IntegrationTest):
encoding='utf-8')
assert msg in install_log
+ # Make sure we do not fallback to an old keytab retrieval method anymore
+ msg = "Retrying with pre-4.0 keytab retrieval method..."
+ assert msg not in install_log
+
# check that user is able to request a host cert, too
result = tasks.run_certutil(client, ['-L'], paths.IPA_NSSDB_DIR)
assert 'Local IPA host' in result.stdout_text

View File

@ -1,32 +0,0 @@
From 2f17319df6147832dceff7c06154363f8d58b194 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
Date: Jan 18 2024 09:07:31 +0000
Subject: adtrustinstance: make sure NetBIOS name defaults are set properly
Some tools may pass None as NetBIOS name if not put explicitly by a
user. This meant to use default NetBIOS name generator based on the
domain (realm) name. However, this wasn't done properly, so None is
passed later to python-ldap and it rejects such LDAP entry.
Fixes: https://pagure.io/freeipa/issue/9514
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
---
diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py
index bf0cc3b..bb5b61a 100644
--- a/ipaserver/install/adtrustinstance.py
+++ b/ipaserver/install/adtrustinstance.py
@@ -189,6 +189,8 @@ class ADTRUSTInstance(service.Service):
self.fqdn = self.fqdn or api.env.host
self.host_netbios_name = make_netbios_name(self.fqdn)
self.realm = self.realm or api.env.realm
+ if not self.netbios_name:
+ self.netbios_name = make_netbios_name(self.realm)
self.suffix = ipautil.realm_to_suffix(self.realm)
self.ldapi_socket = "%%2fvar%%2frun%%2fslapd-%s.socket" % \

View File

@ -0,0 +1,120 @@
From 1c86c973c8dc778a71c8e2abf54ff37ececdd696 Mon Sep 17 00:00:00 2001
From: PRANAV THUBE <pthube@redhat.com>
Date: Tue, 13 Jan 2026 19:30:09 +0530
Subject: [PATCH] ipatests: Move expire_password fixture into TestIPACommand
class
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
---
.../nightly_ipa-4-13_latest.yaml | 2 +-
.../nightly_ipa-4-13_latest_selinux.yaml | 2 +-
ipatests/test_integration/test_commands.py | 59 +++++++++----------
3 files changed, 31 insertions(+), 32 deletions(-)
diff --git a/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml
index 210739d4b576882ee43e06d85bce819ff30d2357..aff55727e463207fb235ff340989491e62162149 100644
--- a/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml
+++ b/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml
@@ -1914,7 +1914,7 @@ jobs:
class: RunPytest
args:
build_url: '{fedora-latest-ipa-4-13/build_url}'
- test_suite: test_integration/test_random_serial_numbers.py::TestIPACommand_RSN::test_certificate_out_write_to_file
+ test_suite: test_integration/test_random_serial_numbers.py::TestIPACommand_RSN
template: *ci-ipa-4-13-latest
timeout: 5400
topology: *master_1repl_1client
diff --git a/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml
index 0fb7c050b97bf66645599fbff46b53c048211f96..e6c57ea060b3bb8bfdf8b6f981f8fd28e4a7d320 100644
--- a/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml
+++ b/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml
@@ -2066,7 +2066,7 @@ jobs:
args:
build_url: '{fedora-latest-ipa-4-13/build_url}'
selinux_enforcing: True
- test_suite: test_integration/test_random_serial_numbers.py::TestIPACommand_RSN::test_certificate_out_write_to_file
+ test_suite: test_integration/test_random_serial_numbers.py::TestIPACommand_RSN
template: *ci-ipa-4-13-latest
timeout: 5400
topology: *master_1repl_1client
diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
index 01001e4c92037599b075e9bca3ddda7d0c8e8ffa..eda98e92b6b494752d74c6381bdcb6d5c47a26d1 100644
--- a/ipatests/test_integration/test_commands.py
+++ b/ipatests/test_integration/test_commands.py
@@ -199,36 +199,6 @@ duplicatesubject = (
duplicate_serial = "4097"
-@pytest.fixture()
-def expire_password():
- """
- Fixture to expire a user's password far into the future past
- 2038, then revert time back.
- """
- hosts = dict()
-
- def _expire_password(host):
- hosts['host'] = host
- tasks.move_date(host, 'stop', '+20Years')
- host.run_command(
- ['ipactl', 'restart', '--ignore-service-failures']
- )
-
- yield _expire_password
-
- host = hosts.pop('host')
- # Prior to uninstall remove all the cert tracking to prevent
- # errors from certmonger trying to check the status of certs
- # that don't matter because we are uninstalling.
- host.run_command(['systemctl', 'stop', 'certmonger'])
- # Important: run_command with a str argument is able to
- # perform shell expansion but run_command with a list of
- # arguments is not
- host.run_command('rm -fv ' + paths.CERTMONGER_REQUESTS_DIR + '*')
- tasks.uninstall_master(host)
- tasks.move_date(host, 'start', '-20Years')
-
-
class TestIPACommand(IntegrationTest):
"""
A lot of commands can be executed against a single IPA installation
@@ -239,6 +209,35 @@ class TestIPACommand(IntegrationTest):
num_replicas = 1
num_clients = 1
+ @pytest.fixture
+ def expire_password(self):
+ """
+ Fixture to expire a user's password far into the future past
+ 2038, then revert time back.
+ """
+ hosts = dict()
+
+ def _expire_password(host):
+ hosts['host'] = host
+ tasks.move_date(host, 'stop', '+20Years')
+ host.run_command(
+ ['ipactl', 'restart', '--ignore-service-failures']
+ )
+
+ yield _expire_password
+
+ host = hosts.pop('host')
+ # Prior to uninstall remove all the cert tracking to prevent
+ # errors from certmonger trying to check the status of certs
+ # that don't matter because we are uninstalling.
+ host.run_command(['systemctl', 'stop', 'certmonger'])
+ # Important: run_command with a str argument is able to
+ # perform shell expansion but run_command with a list of
+ # arguments is not
+ host.run_command('rm -fv ' + paths.CERTMONGER_REQUESTS_DIR + '*')
+ tasks.uninstall_master(host)
+ tasks.move_date(host, 'start', '-20Years')
+
@pytest.fixture
def pwpolicy_global(self):
"""Fixture to change global password history policy and reset it"""
--
2.52.0

View File

@ -1,175 +0,0 @@
From 5afda72afc6fd626359411b55f092989fdd7d82d Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Jan 15 2024 13:39:21 +0000
Subject: ipatests: ignore nsslapd-accesslog-logbuffering WARN in healthcheck
Log buffering is disabled in the integration tests so we can have all
the logs at the end. This is causing a warning to show in the 389-ds
checks and causing tests to fail that expect all SUCCESS.
Add an exclude for this specific key so tests will pass again.
We may eventually want a more sophisiticated mechanism to handle
excludes, or updating the config in general, but this is fine for now.
Fixes: https://pagure.io/freeipa/issue/9400
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
Reviewed-By: Michal Polovka <mpolovka@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
Reviewed-By: Michal Polovka <mpolovka@redhat.com>
---
diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
index 7fb8e40..14fba26 100644
--- a/ipatests/test_integration/test_ipahealthcheck.py
+++ b/ipatests/test_integration/test_ipahealthcheck.py
@@ -9,6 +9,7 @@ from __future__ import absolute_import
from configparser import RawConfigParser, NoOptionError
from datetime import datetime, timedelta
+import io
import json
import os
import re
@@ -208,6 +209,28 @@ def run_healthcheck(host, source=None, check=None, output_type="json",
return result.returncode, data
+def set_excludes(host, option, value,
+ config_file='/etc/ipahealthcheck/ipahealthcheck.conf'):
+ """Mark checks that should be excluded from the results
+
+ This will set in the [excludes] section on host:
+ option=value
+ """
+ EXCLUDES = "excludes"
+
+ conf = host.get_file_contents(config_file, encoding='utf-8')
+ cfg = RawConfigParser()
+ cfg.read_string(conf)
+ if not cfg.has_section(EXCLUDES):
+ cfg.add_section(EXCLUDES)
+ if not cfg.has_option(EXCLUDES, option):
+ cfg.set(EXCLUDES, option, value)
+ out = io.StringIO()
+ cfg.write(out)
+ out.seek(0)
+ host.put_file_contents(config_file, out.read())
+
+
@pytest.fixture
def restart_service():
"""Shut down and restart a service as a fixture"""
@@ -265,6 +288,7 @@ class TestIpaHealthCheck(IntegrationTest):
setup_dns=True,
extra_args=['--no-dnssec-validation']
)
+ set_excludes(cls.master, "key", "DSCLE0004")
def test_ipa_healthcheck_install_on_master(self):
"""
@@ -552,6 +576,7 @@ class TestIpaHealthCheck(IntegrationTest):
setup_dns=True,
extra_args=['--no-dnssec-validation']
)
+ set_excludes(self.replicas[0], "key", "DSCLE0004")
# Init a user on replica to assign a DNA range
tasks.kinit_admin(self.replicas[0])
@@ -692,6 +717,7 @@ class TestIpaHealthCheck(IntegrationTest):
'output_type=human'
])
)
+ set_excludes(self.master, "key", "DSCLE0004", config_file)
returncode, output = run_healthcheck(
self.master, failures_only=True, config=config_file
)
@@ -707,6 +733,7 @@ class TestIpaHealthCheck(IntegrationTest):
'output_file=%s' % HC_LOG,
])
)
+ set_excludes(self.master, "key", "DSCLE0004")
returncode, _unused = run_healthcheck(
self.master, config=config_file
)
@@ -2396,6 +2423,7 @@ class TestIpaHealthCLI(IntegrationTest):
cls.master, setup_dns=True, extra_args=['--no-dnssec-validation']
)
tasks.install_packages(cls.master, HEALTHCHECK_PKG)
+ set_excludes(cls.master, "key", "DSCLE0004")
def test_indent(self):
"""
diff --git a/ipatests/test_integration/test_replica_promotion.py b/ipatests/test_integration/test_replica_promotion.py
index d477c3a..b71f2d5 100644
--- a/ipatests/test_integration/test_replica_promotion.py
+++ b/ipatests/test_integration/test_replica_promotion.py
@@ -13,7 +13,7 @@ import pytest
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration.test_ipahealthcheck import (
- run_healthcheck, HEALTHCHECK_PKG
+ run_healthcheck, set_excludes, HEALTHCHECK_PKG
)
from ipatests.pytest_ipa.integration import tasks
from ipatests.pytest_ipa.integration.tasks import (
@@ -983,6 +983,9 @@ class TestHiddenReplicaPromotion(IntegrationTest):
# manually install KRA to verify that hidden state is synced
tasks.install_kra(cls.replicas[0])
+ set_excludes(cls.master, "key", "DSCLE0004")
+ set_excludes(cls.replicas[0], "key", "DSCLE0004")
+
def _check_dnsrecords(self, hosts_expected, hosts_unexpected=()):
domain = DNSName(self.master.domain.name).make_absolute()
rset = [
From f1cfe7d9ff2489dbb6cad70999b0e1bd433c0537 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Jan 15 2024 13:39:21 +0000
Subject: ipatests: fix expected output for ipahealthcheck.ipa.host
ipa-healthcheck commit e69589d5 changed the output when a service
keytab is missing to not report the GSSAPI error but to report
that the keytab doesn't exist at all. This distinguishes from real
Kerberos issues like kvno.
Fixes: https://pagure.io/freeipa/issue/9482
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
Reviewed-By: Michal Polovka <mpolovka@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
Reviewed-By: Michal Polovka <mpolovka@redhat.com>
---
diff --git a/ipatests/test_integration/test_ipahealthcheck.py b/ipatests/test_integration/test_ipahealthcheck.py
index 14fba26..8aae9fa 100644
--- a/ipatests/test_integration/test_ipahealthcheck.py
+++ b/ipatests/test_integration/test_ipahealthcheck.py
@@ -629,9 +629,15 @@ class TestIpaHealthCheck(IntegrationTest):
ipahealthcheck.ipa.host when GSSAPI credentials cannot be obtained
from host's keytab.
"""
- msg = (
- "Minor (2529639107): No credentials cache found"
- )
+ version = tasks.get_healthcheck_version(self.master)
+ if parse_version(version) >= parse_version("0.15"):
+ msg = (
+ "Service {service} keytab {path} does not exist."
+ )
+ else:
+ msg = (
+ "Minor (2529639107): No credentials cache found"
+ )
with tasks.FileBackup(self.master, paths.KRB5_KEYTAB):
self.master.run_command(["rm", "-f", paths.KRB5_KEYTAB])

View File

@ -0,0 +1,48 @@
From ecc0efa96e3c446586425d470657de7f2d5376bf Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
Date: Mon, 19 Jan 2026 17:00:41 +0100
Subject: [PATCH] ipatests: Fix xfail assertion for sssd 2.12.0
SSSD 2.12.0 provides fixes for
https://github.com/SSSD/sssd/issues/5989
https://github.com/SSSD/sssd/issues/7169
Update the condition expecting test failures in test_trust.py
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: David Hanina <dhanina@redhat.com>
---
ipatests/test_integration/test_trust.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/ipatests/test_integration/test_trust.py b/ipatests/test_integration/test_trust.py
index 13ad0afa4c1fb032d50f40cf7cb9b79283203225..0cab277c910a6d35f35b57e3068ee6f38706af59 100644
--- a/ipatests/test_integration/test_trust.py
+++ b/ipatests/test_integration/test_trust.py
@@ -1219,9 +1219,10 @@ class TestNonPosixAutoPrivateGroup(BaseTestTrust):
assert (uid == self.uid_override and gid == self.gid_override)
test_group = self.clients[0].run_command(
["id", nonposixuser]).stdout_text
- cond2 = ((type == 'false'
- and sssd_version >= tasks.parse_version("2.9.4"))
- or type == 'hybrid')
+ cond2 = (((type == 'false'
+ and sssd_version >= tasks.parse_version("2.9.4"))
+ or type == 'hybrid')
+ and sssd_version < tasks.parse_version("2.12.0"))
with xfail_context(cond2,
'https://github.com/SSSD/sssd/issues/5989 '
'and 7169'):
@@ -1347,7 +1348,8 @@ class TestPosixAutoPrivateGroup(BaseTestTrust):
and gid == self.gid_override)
result = self.clients[0].run_command(['id', posixuser])
sssd_version = tasks.get_sssd_version(self.clients[0])
- bad_version = sssd_version >= tasks.parse_version("2.9.4")
+ bad_version = (tasks.parse_version("2.9.4") <= sssd_version
+ < tasks.parse_version("2.12.0"))
with xfail_context(bad_version and type in ('false', 'hybrid'),
"https://github.com/SSSD/sssd/issues/7169"):
assert "10047(testgroup@{0})".format(
--
2.52.0

File diff suppressed because it is too large Load Diff

View File

@ -1,45 +0,0 @@
From dcb9d6edc7ae4278cd552e87f644705faa13d558 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
Date: Jan 31 2024 08:31:13 +0000
Subject: kdb: PAC generator: do not fail if canonical principal is missing
krbCanonicalName is mandatory for services but IPA services created
before commit e6ff83e (FreeIPA 4.4.0, ~2016) had no normalization done
to set krbCanonicalName; services created after that version were
upgraded to do have krbCanonicalName.
Accept krbPrincipalName alone since they have no alias either */
Fixes: https://pagure.io/freeipa/issue/9465
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: Thierry Bordaz <tbordaz@redhat.com>
---
diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
index 9e1431c..8035036 100644
--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
+++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
@@ -496,8 +496,16 @@ static krb5_error_code ipadb_fill_info3(struct ipadb_context *ipactx,
ret = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry,
"krbCanonicalName", &strres);
if (ret) {
- /* krbCanonicalName is mandatory for services */
- return ret;
+ /* krbCanonicalName is mandatory for services but IPA services
+ * created before commit e6ff83e (FreeIPA 4.4.0, ~2016) had no
+ * normalization to set krbCanonicalName; services created after
+ * that version were upgraded to do have krbCanonicalName.
+ *
+ * Accept krbPrincipalName alone since they have no alias either */
+ ret = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry,
+ "krbPrincipalName", &strres);
+ if (ret)
+ return ret;
}
ret = krb5_parse_name(ipactx->kcontext, strres, &princ);

View File

@ -0,0 +1,99 @@
From 5756ed2af940378c16d9d52e083b8c4005d41a13 Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
Date: Wed, 21 Jan 2026 17:19:18 +0100
Subject: [PATCH] ipa-advise: smart card client script does not need krb ticket
The script generated by ipa-advise config-client-for-smart-card-auth
currently requires a kerberos ticket because it calls ipa-certupdate.
Since IPA 4.9.0 and commit 1a09ce9, ipa-certupdate can be called
without a ticket. Update the script so that it detects if it gets
executed on a client recent enough to skip that requirement.
Update the test for config-client-for-smart-card-auth, do not
call kinit admin on the client.
Fixes: https://pagure.io/freeipa/issue/9923
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
---
ipaserver/advise/plugins/smart_card_auth.py | 22 ++++++++++++++++++++-
ipatests/test_integration/test_advise.py | 10 +++++++---
2 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/ipaserver/advise/plugins/smart_card_auth.py b/ipaserver/advise/plugins/smart_card_auth.py
index b79797dcaee0c881d3ef752a268ed520d96b433b..a0e50e9806f7843d2981141d8941d5e37f53c0cd 100644
--- a/ipaserver/advise/plugins/smart_card_auth.py
+++ b/ipaserver/advise/plugins/smart_card_auth.py
@@ -34,6 +34,26 @@ class common_smart_card_auth_config(Advice):
'Use kinit as privileged user to obtain Kerberos credentials'
])
+ def check_ccache_not_empty_if_old_version(self):
+ self.log.comment("On version before IPA 4.9, "
+ "check that the credential cache is not empty")
+ self.log.command(
+ "python3 -c \"from ipapython.version import VERSION;"
+ "from ipaplatform.tasks import tasks;"
+ "exit(tasks.parse_ipa_version(VERSION) >= "
+ "tasks.parse_ipa_version('4.9.0'))\"")
+ with self.log.if_branch('[ "$?" -eq "0" ]'):
+ self.log.exit_on_failed_command(
+ 'klist',
+ [
+ "Credential cache is empty",
+ 'Use kinit as privileged user to obtain Kerberos '
+ 'credentials'
+ ])
+ with self.log.else_branch():
+ self.log.command(
+ "echo 'Version 4.9.0+ does not require Kerberos credentials'")
+
def check_and_set_ca_cert_paths(self):
ca_paths_variable = self.smart_card_ca_certs_variable_name
single_ca_path_variable = self.single_ca_cert_variable_name
@@ -260,7 +280,7 @@ class config_client_for_smart_card_auth(common_smart_card_auth_config):
def get_info(self):
self.log.exit_on_nonroot_euid()
self.check_and_set_ca_cert_paths()
- self.check_ccache_not_empty()
+ self.check_ccache_not_empty_if_old_version()
self.check_and_remove_pam_pkcs11()
self.install_opensc_and_dconf_packages()
self.install_krb5_client_dependencies()
diff --git a/ipatests/test_integration/test_advise.py b/ipatests/test_integration/test_advise.py
index 3d5cadee319ebba14ebc43ebb1dc90a502e5d3b8..a336634ae9627133c5ad4dea4b1c43ffd726df10 100644
--- a/ipatests/test_integration/test_advise.py
+++ b/ipatests/test_integration/test_advise.py
@@ -60,13 +60,17 @@ class TestAdvice(IntegrationTest):
)
tasks.install_client(cls.master, cls.clients[0])
- def execute_advise(self, host, advice_id, *args):
+ def execute_advise(self, host, advice_id, *args, kinit=True):
# ipa-advise script is only available on a server
tasks.kinit_admin(self.master)
advice = self.master.run_command(['ipa-advise', advice_id])
# execute script on host (client or master)
if host is not self.master:
- tasks.kinit_admin(host)
+ if kinit:
+ tasks.kinit_admin(host)
+ else:
+ # Make sure we don't have any ticket
+ tasks.kdestroy_all(host)
filename = tasks.upload_temp_contents(host, advice.stdout_text)
cmd = ['sh', filename]
cmd.extend(args)
@@ -181,7 +185,7 @@ class TestAdvice(IntegrationTest):
ca_pem = ExternalCA().create_ca()
ca_file = tasks.upload_temp_contents(client, ca_pem)
try:
- self.execute_advise(client, advice_id, ca_file)
+ self.execute_advise(client, advice_id, ca_file, kinit=False)
finally:
client.run_command(['rm', '-f', ca_file])
--
2.52.0

View File

@ -1,89 +0,0 @@
From bac601b7f35827236a106f7137f378e4888260da Mon Sep 17 00:00:00 2001
From: Julien Rische <jrische@redhat.com>
Date: Jan 30 2024 15:17:44 +0000
Subject: ipa-kdb: Fix memory leak during PAC verification
Commit 0022bd70d93708d325855d5271516d6cd894d6e8 introduced a memory leak
during the copy of some PAC buffers, because of an unfreed memory
allocation context.
Fixes: https://pagure.io/freeipa/issue/9520
Signed-off-by: Julien Rische <jrische@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
index a18beff..9e1431c 100644
--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
+++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
@@ -2316,6 +2316,7 @@ krb5_error_code ipadb_common_verify_pac(krb5_context context,
size_t i;
struct dom_sid *requester_sid = NULL;
struct dom_sid req_sid;
+ TALLOC_CTX *tmpctx = NULL;
if (signing_krbtgt != NULL &&
ipadb_is_cross_realm_krbtgt(signing_krbtgt->princ)) {
@@ -2371,6 +2372,12 @@ krb5_error_code ipadb_common_verify_pac(krb5_context context,
goto done;
}
+ tmpctx = talloc_new(NULL);
+ if (tmpctx == NULL) {
+ kerr = ENOMEM;
+ goto done;
+ }
+
for (i = 0; i < num_buffers; i++) {
if (types[i] == KRB5_PAC_SERVER_CHECKSUM ||
types[i] == KRB5_PAC_PRIVSVR_CHECKSUM ||
@@ -2395,32 +2402,21 @@ krb5_error_code ipadb_common_verify_pac(krb5_context context,
DATA_BLOB pac_attrs_data;
krb5_boolean pac_requested;
- TALLOC_CTX *tmpctx = talloc_new(NULL);
- if (tmpctx == NULL) {
- kerr = ENOMEM;
- goto done;
- }
-
kerr = ipadb_client_requested_pac(context, old_pac, tmpctx, &pac_requested);
- if (kerr != 0) {
- talloc_free(tmpctx);
+ if (kerr)
goto done;
- }
kerr = ipadb_get_pac_attrs_blob(tmpctx, &pac_requested, &pac_attrs_data);
- if (kerr) {
- talloc_free(tmpctx);
+ if (kerr)
goto done;
- }
+
data.magic = KV5M_DATA;
data.data = (char *)pac_attrs_data.data;
data.length = pac_attrs_data.length;
kerr = krb5_pac_add_buffer(context, new_pac, PAC_TYPE_ATTRIBUTES_INFO, &data);
- if (kerr) {
- talloc_free(tmpctx);
+ if (kerr)
goto done;
- }
continue;
}
@@ -2467,6 +2463,8 @@ done:
if (kerr != 0 && (new_pac != *pac)) {
krb5_pac_free(context, new_pac);
}
+ if (tmpctx)
+ talloc_free(tmpctx);
krb5_free_data_contents(context, &pac_blob);
free(types);
return kerr;

View File

@ -1,238 +0,0 @@
From 381af470779ea87335f57038dcbe72cd042ae6bb Mon Sep 17 00:00:00 2001
From: Stanislav Levin <slev@altlinux.org>
Date: Jan 30 2024 15:11:05 +0000
Subject: ipapython: Clean up krb5_error
`krb5_error` has different definition in MIT krb.
https://web.mit.edu/kerberos/krb5-latest/doc/appdev/refs/types/krb5_error.html
> Error message structure.
>
> Declaration:
> typedef struct _krb5_error krb5_error
While `krb5_error_code`
https://web.mit.edu/kerberos/www/krb5-latest/doc/appdev/refs/types/krb5_error_code.html#c.krb5_error_code
> krb5_error_code
> Used to convey an operation status.
>
> The value 0 indicates success; any other values are com_err codes. Use krb5_get_error_message() to obtain a string describing the error.
>
> Declaration
> typedef krb5_int32 krb5_error_code
And this is what was actually used.
To prevent confusion of types `krb5_error` was replaced with
`krb5_error_code`.
Fixes: https://pagure.io/freeipa/issue/9519
Signed-off-by: Stanislav Levin <slev@altlinux.org>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
diff --git a/ipapython/session_storage.py b/ipapython/session_storage.py
index c43ef7d..371cf15 100644
--- a/ipapython/session_storage.py
+++ b/ipapython/session_storage.py
@@ -111,7 +111,7 @@ class KRB5Error(Exception):
def krb5_errcheck(result, func, arguments):
- """Error checker for krb5_error return value"""
+ """Error checker for krb5_error_code return value"""
if result != 0:
raise KRB5Error(result, func.__name__, arguments)
@@ -119,14 +119,13 @@ def krb5_errcheck(result, func, arguments):
krb5_context = ctypes.POINTER(_krb5_context)
krb5_ccache = ctypes.POINTER(_krb5_ccache)
krb5_data_p = ctypes.POINTER(_krb5_data)
-krb5_error = ctypes.c_int32
krb5_creds = _krb5_creds
krb5_pointer = ctypes.c_void_p
krb5_cc_cursor = krb5_pointer
krb5_init_context = LIBKRB5.krb5_init_context
krb5_init_context.argtypes = (ctypes.POINTER(krb5_context), )
-krb5_init_context.restype = krb5_error
+krb5_init_context.restype = krb5_error_code
krb5_init_context.errcheck = krb5_errcheck
krb5_free_context = LIBKRB5.krb5_free_context
@@ -143,30 +142,30 @@ krb5_free_data_contents.restype = None
krb5_cc_default = LIBKRB5.krb5_cc_default
krb5_cc_default.argtypes = (krb5_context, ctypes.POINTER(krb5_ccache), )
-krb5_cc_default.restype = krb5_error
+krb5_cc_default.restype = krb5_error_code
krb5_cc_default.errcheck = krb5_errcheck
krb5_cc_close = LIBKRB5.krb5_cc_close
krb5_cc_close.argtypes = (krb5_context, krb5_ccache, )
-krb5_cc_close.restype = krb5_error
+krb5_cc_close.restype = krb5_error_code
krb5_cc_close.errcheck = krb5_errcheck
krb5_parse_name = LIBKRB5.krb5_parse_name
krb5_parse_name.argtypes = (krb5_context, ctypes.c_char_p,
ctypes.POINTER(krb5_principal), )
-krb5_parse_name.restype = krb5_error
+krb5_parse_name.restype = krb5_error_code
krb5_parse_name.errcheck = krb5_errcheck
krb5_cc_set_config = LIBKRB5.krb5_cc_set_config
krb5_cc_set_config.argtypes = (krb5_context, krb5_ccache, krb5_principal,
ctypes.c_char_p, krb5_data_p, )
-krb5_cc_set_config.restype = krb5_error
+krb5_cc_set_config.restype = krb5_error_code
krb5_cc_set_config.errcheck = krb5_errcheck
krb5_cc_get_principal = LIBKRB5.krb5_cc_get_principal
krb5_cc_get_principal.argtypes = (krb5_context, krb5_ccache,
ctypes.POINTER(krb5_principal), )
-krb5_cc_get_principal.restype = krb5_error
+krb5_cc_get_principal.restype = krb5_error_code
krb5_cc_get_principal.errcheck = krb5_errcheck
# krb5_build_principal is a variadic function but that can't be expressed
@@ -177,26 +176,26 @@ krb5_build_principal.argtypes = (krb5_context, ctypes.POINTER(krb5_principal),
ctypes.c_uint, ctypes.c_char_p,
ctypes.c_char_p, ctypes.c_char_p,
ctypes.c_char_p, ctypes.c_char_p, )
-krb5_build_principal.restype = krb5_error
+krb5_build_principal.restype = krb5_error_code
krb5_build_principal.errcheck = krb5_errcheck
krb5_cc_start_seq_get = LIBKRB5.krb5_cc_start_seq_get
krb5_cc_start_seq_get.argtypes = (krb5_context, krb5_ccache,
ctypes.POINTER(krb5_cc_cursor), )
-krb5_cc_start_seq_get.restype = krb5_error
+krb5_cc_start_seq_get.restype = krb5_error_code
krb5_cc_start_seq_get.errcheck = krb5_errcheck
krb5_cc_next_cred = LIBKRB5.krb5_cc_next_cred
krb5_cc_next_cred.argtypes = (krb5_context, krb5_ccache,
ctypes.POINTER(krb5_cc_cursor),
ctypes.POINTER(krb5_creds), )
-krb5_cc_next_cred.restype = krb5_error
+krb5_cc_next_cred.restype = krb5_error_code
krb5_cc_next_cred.errcheck = krb5_errcheck
krb5_cc_end_seq_get = LIBKRB5.krb5_cc_end_seq_get
krb5_cc_end_seq_get.argtypes = (krb5_context, krb5_ccache,
ctypes.POINTER(krb5_cc_cursor), )
-krb5_cc_end_seq_get.restype = krb5_error
+krb5_cc_end_seq_get.restype = krb5_error_code
krb5_cc_end_seq_get.errcheck = krb5_errcheck
krb5_free_cred_contents = LIBKRB5.krb5_free_cred_contents
@@ -212,7 +211,7 @@ krb5_principal_compare.restype = krb5_boolean
krb5_unparse_name = LIBKRB5.krb5_unparse_name
krb5_unparse_name.argtypes = (krb5_context, krb5_principal,
ctypes.POINTER(ctypes.c_char_p), )
-krb5_unparse_name.restype = krb5_error
+krb5_unparse_name.restype = krb5_error_code
krb5_unparse_name.errcheck = krb5_errcheck
krb5_free_unparsed_name = LIBKRB5.krb5_free_unparsed_name
From 2a4bad8bb3295c5c0f5a760ecd41871c4c5a0c56 Mon Sep 17 00:00:00 2001
From: Stanislav Levin <slev@altlinux.org>
Date: Jan 30 2024 15:11:05 +0000
Subject: ipapython: Correct return type of krb5_free_cred_contents
According to https://web.mit.edu/kerberos/krb5-latest/doc/appdev/refs/api/krb5_free_cred_contents.html
> krb5_free_cred_contents - Free the contents of a krb5_creds structure.
>
> void krb5_free_cred_contents(krb5_context context, krb5_creds * val)
> param:
> [in] context - Library context
>
> [in] val - Credential structure to free contents of
>
> This function frees the contents of val , but not the structure itself.
https://github.com/krb5/krb5/blob/5b00197227231943bd2305328c8260dd0b0dbcf0/src/lib/krb5/krb/kfree.c#L166
This leads to undefined behavior and `krb5_free_cred_contents` can
raise KRB5Error (because of garbage data) while actually its foreign
function doesn't.
Fixes: https://pagure.io/freeipa/issue/9519
Signed-off-by: Stanislav Levin <slev@altlinux.org>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
diff --git a/ipapython/session_storage.py b/ipapython/session_storage.py
index 371cf15..dc36f54 100644
--- a/ipapython/session_storage.py
+++ b/ipapython/session_storage.py
@@ -200,8 +200,7 @@ krb5_cc_end_seq_get.errcheck = krb5_errcheck
krb5_free_cred_contents = LIBKRB5.krb5_free_cred_contents
krb5_free_cred_contents.argtypes = (krb5_context, ctypes.POINTER(krb5_creds))
-krb5_free_cred_contents.restype = krb5_error
-krb5_free_cred_contents.errcheck = krb5_errcheck
+krb5_free_cred_contents.restype = None
krb5_principal_compare = LIBKRB5.krb5_principal_compare
krb5_principal_compare.argtypes = (krb5_context, krb5_principal,
From beb402afdbf32c01eed860e9416356f7b492ad74 Mon Sep 17 00:00:00 2001
From: Stanislav Levin <slev@altlinux.org>
Date: Jan 30 2024 15:11:05 +0000
Subject: ipapython: Propagate KRB5Error exceptions on iterating ccache
`ipapython.session_storage.get_data` iterates over
credentials in a credential cache till `krb5_cc_next_cred` returns
an error. This function doesn't expect any error on calling
other kerberos foreign functions during iteration. But that can
actually happen and KRB5Error exceptions stop an iteration while
they should be propagated.
With this change iteration will exactly stop on `krb5_cc_next_cred`
error as it was supposed to be.
Fixes: https://pagure.io/freeipa/issue/9519
Signed-off-by: Stanislav Levin <slev@altlinux.org>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
diff --git a/ipapython/session_storage.py b/ipapython/session_storage.py
index dc36f54..e890dc9 100644
--- a/ipapython/session_storage.py
+++ b/ipapython/session_storage.py
@@ -312,8 +312,12 @@ def get_data(princ_name, key):
checkcreds = krb5_creds()
# the next function will throw an error and break out of the
# while loop when we try to access past the last cred
- krb5_cc_next_cred(context, ccache, ctypes.byref(cursor),
- ctypes.byref(checkcreds))
+ try:
+ krb5_cc_next_cred(context, ccache, ctypes.byref(cursor),
+ ctypes.byref(checkcreds))
+ except KRB5Error:
+ break
+
if (krb5_principal_compare(context, principal,
checkcreds.client) == 1 and
krb5_principal_compare(context, srv_princ,
@@ -328,8 +332,6 @@ def get_data(princ_name, key):
else:
krb5_free_cred_contents(context,
ctypes.byref(checkcreds))
- except KRB5Error:
- pass
finally:
krb5_cc_end_seq_get(context, ccache, ctypes.byref(cursor))

View File

@ -0,0 +1,177 @@
From fd84cec77d18e0e608285ab712f8211b0b49a7fe Mon Sep 17 00:00:00 2001
From: PRANAV THUBE <pthube@redhat.com>
Date: Thu, 18 Dec 2025 17:13:29 +0530
Subject: [PATCH] ipatests: Fix resolver state tracking in enforced DNS policy
tests.
Use resolver.setup_resolver() instead of direct file manipulation to
prevent state tracking failures in IDM-CI. Remove redundant manual
resolv.conf changes and fix operation order.
Related: https://pagure.io/freeipa/issue/9904
Reviewed-By: Antonio Torres <antorres@redhat.com>
---
ipatests/test_integration/test_edns.py | 70 ++++++++++++++++++--------
1 file changed, 50 insertions(+), 20 deletions(-)
diff --git a/ipatests/test_integration/test_edns.py b/ipatests/test_integration/test_edns.py
index d02ce45a1f9c0b04332e4ff4a055ab4c61b35eb4..54b7859da1b9fa320c07bdf29f79d2fdb292c337 100644
--- a/ipatests/test_integration/test_edns.py
+++ b/ipatests/test_integration/test_edns.py
@@ -15,6 +15,9 @@ from ipatests.test_integration.test_dns import TestDNS
from ipatests.pytest_ipa.integration.firewall import Firewall
from ipaplatform.osinfo import osinfo
from ipaplatform.paths import paths
+from ipatests.pytest_ipa.integration.resolver import (
+ resolver as detect_resolver
+)
def apply_enforced_dns_preconfig(host, master_ip, master_host,
@@ -30,6 +33,9 @@ def apply_enforced_dns_preconfig(host, master_ip, master_host,
ca_cert_path: Path to the CA certificate on the master
dest_cert_name: Destination certificate filename in trust anchors
"""
+ # Backup resolver before making any changes (with original resolver type)
+ host.resolver.backup()
+
# Get the network interface for routing
iface_cmd = host.run_command([
"bash", "-c",
@@ -43,7 +49,8 @@ def apply_enforced_dns_preconfig(host, master_ip, master_host,
host.put_file_contents(dest_ca_path, ca_cert_data)
host.run_command(["update-ca-trust", "extract"])
- if osinfo.id in ['rhel', 'centos']:
+ platform = tasks.get_platform(host)
+ if platform in ['rhel', 'centos']:
# RHEL/CentOS configuration
# Install and configure dnsconfd
tasks.install_packages(host, ['dnsconfd'])
@@ -51,31 +58,60 @@ def apply_enforced_dns_preconfig(host, master_ip, master_host,
host.run_command(["systemctl", "enable", "--now", "dnsconfd"])
host.run_command(["nmcli", "g", "reload"])
- # Configure DNS over TLS via NetworkManager
+ # Configure DNS over TLS via NetworkManager device-specific settings.
+ # Device-specific DNS settings work alongside global resolver config.
host.run_command([
"nmcli", "device", "modify", iface,
"ipv4.dns", f"dns+tls://{master_ip}"
])
- elif osinfo.id == 'fedora':
- # Fedora configuration
+ elif platform == 'fedora':
# Configure systemd-resolved for DNS over TLS
host.run_command([
- "ln", "-sf", "../run/systemd/resolve/stub-resolv.conf",
+ "ln", "-sf", "/run/systemd/resolve/stub-resolv.conf",
"/etc/resolv.conf"
])
+ # Restart systemd-resolved to ensure DoT settings are fully applied
+ # and resolv.conf is updated
host.run_command(["systemctl", "restart", "systemd-resolved"])
-
- # Configure DNS over TLS via systemd-resolve
+ # Configure DNS over TLS via systemd-resolve per-interface settings
+ # Per-interface DNS settings work alongside the global resolver config
host.run_command([
"systemd-resolve", "--set-dns", master_ip,
"--set-dnsovertls=yes", f"--interface={iface}"
])
else:
raise ValueError(
- f"Unsupported OS for enforced DNS policy: {osinfo.id}"
+ f"Unsupported OS for enforced DNS policy: {platform}"
)
+ # Re-detect resolver since we may have changed the resolver type
+ # (e.g., from PlainFileResolver to ResolvedResolver on Fedora)
+ # This ensures state tracking works correctly with the new resolver type
+ # Store the original resolver so we can restore from it during uninstall
+ host._enforced_dns_original_resolver = host.resolver
+ host.resolver = detect_resolver(host)
+ host.resolver.current_state = host.resolver._get_state()
+
+
+def restore_enforced_dns_resolver(host):
+ """
+ Restore resolver state after enforced DNS preconfig.
+
+ This restores the original resolver backup created by
+ apply_enforced_dns_preconfig() and syncs host.resolver state.
+ """
+ if not hasattr(host, '_enforced_dns_original_resolver'):
+ return
+ original_resolver = host._enforced_dns_original_resolver
+ if original_resolver.has_backups():
+ original_resolver.current_state = original_resolver._get_state()
+ original_resolver.restore()
+ # Sync host.resolver state since original_resolver changed the config
+ host.resolver.backups.clear()
+ host.resolver.current_state = host.resolver._get_state()
+ delattr(host, '_enforced_dns_original_resolver')
+
def setup_dns_over_tls_environment(cls):
"""
@@ -451,17 +487,12 @@ class TestDNSOverTLS_EnforcedPolicy_IPA_CA(IntegrationTest):
tasks.install_master(self.master, extra_args=args)
# Apply pre-configuration on client before installation
+ # This configures the resolver with master IP and domain
apply_enforced_dns_preconfig(
self.clients[0], self.master.ip, self.master,
paths.IPA_CA_CRT, "ca.crt"
)
- # Configure client nameserver
- self.clients[0].put_file_contents(
- paths.RESOLV_CONF,
- "nameserver %s" % self.master.ip
- )
-
# Install client with enforced policy
args = [
"--dns-over-tls",
@@ -506,6 +537,8 @@ class TestDNSOverTLS_EnforcedPolicy_IPA_CA(IntegrationTest):
"""
This test ensures that all hosts can be uninstalled correctly.
"""
+ for host in [self.clients[0], self.replicas[0]]:
+ restore_enforced_dns_resolver(host)
tasks.uninstall_client(self.clients[0])
tasks.uninstall_replica(self.master, self.replicas[0])
tasks.uninstall_master(self.master)
@@ -559,17 +592,12 @@ class TestDNSOverTLS_EnforcedPolicy_External_CA(IntegrationTest):
# Apply pre-configuration on client before installation
# (includes CA cert setup)
+ # This configures the resolver with master IP and domain
apply_enforced_dns_preconfig(
self.clients[0], self.master.ip, self.master,
cert_dest, "certificate.pem"
)
- # Configure client nameserver
- self.clients[0].put_file_contents(
- paths.RESOLV_CONF,
- "nameserver %s" % self.master.ip
- )
-
# Install client with enforced policy
args = [
"--dns-over-tls",
@@ -615,6 +643,8 @@ class TestDNSOverTLS_EnforcedPolicy_External_CA(IntegrationTest):
"""
This test ensures that all hosts can be uninstalled correctly.
"""
+ for host in [self.clients[0], self.replicas[0]]:
+ restore_enforced_dns_resolver(host)
tasks.uninstall_client(self.clients[0])
tasks.uninstall_replica(self.master, self.replicas[0])
tasks.uninstall_master(self.master)
--
2.52.0

View File

@ -1,109 +0,0 @@
From b56a80581ef388e19d5761020454e51463036cd6 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
Date: Tue, 23 Jan 2024 14:47:50 +0200
Subject: [PATCH] sidgen: ignore staged users when generating SIDs
Staged users have
uidNumber: -1
gidNumber: -1
ipaUniqueID: autogenerate
We cannot generate ipaSecurityIdentifier based on those UID/GID numbers.
However, '-1' value will trigger an error
find_sid_for_ldap_entry - [file ipa_sidgen_common.c, line 483]: ID value too large.
And that, in turn, will cause stopping SID generation for all users.
Detect 'ipaUniqueID: autogenerate' situation and ignore these entries.
Fixes: https://pagure.io/freeipa/issue/9517
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: Thierry Bordaz <tbordaz@redhat.com>
---
daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.h | 2 ++
.../ipa-slapi-plugins/ipa-sidgen/ipa_sidgen_common.c | 12 ++++++++++++
2 files changed, 14 insertions(+)
diff --git a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.h b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.h
index 0feff7eec..bd46982d0 100644
--- a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.h
+++ b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.h
@@ -45,6 +45,8 @@
#define UID_NUMBER "uidnumber"
#define GID_NUMBER "gidnumber"
#define IPA_SID "ipantsecurityidentifier"
+#define IPA_UNIQUEID "ipauniqueid"
+#define IPA_UNIQUEID_AUTOGENERATE "autogenerate"
#define DOM_ATTRS_FILTER OBJECTCLASS"=ipantdomainattrs"
#define DOMAIN_ID_RANGE_FILTER OBJECTCLASS"=ipadomainidrange"
#define POSIX_ACCOUNT "posixaccount"
diff --git a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen_common.c b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen_common.c
index 6f784804c..cb763ebf8 100644
--- a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen_common.c
+++ b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen_common.c
@@ -454,6 +454,7 @@ int find_sid_for_ldap_entry(struct slapi_entry *entry,
uint32_t id;
char *sid = NULL;
char **objectclasses = NULL;
+ char *uniqueid = NULL;
Slapi_PBlock *mod_pb = NULL;
Slapi_Mods *smods = NULL;
int result;
@@ -479,6 +480,16 @@ int find_sid_for_ldap_entry(struct slapi_entry *entry,
goto done;
}
+ uniqueid = slapi_entry_attr_get_charptr(entry, IPA_UNIQUEID);
+ if (uniqueid != NULL &&
+ strncmp(IPA_UNIQUEID_AUTOGENERATE, uniqueid,
+ sizeof(IPA_UNIQUEID_AUTOGENERATE)) == 0) {
+ LOG("Staged entry [%s] does not have Posix IDs, nothing to do.\n",
+ dn_str);
+ ret = 0;
+ goto done;
+ }
+
if (uid_number >= UINT32_MAX || gid_number >= UINT32_MAX) {
LOG_FATAL("ID value too large.\n");
ret = LDAP_CONSTRAINT_VIOLATION;
@@ -554,6 +565,7 @@ int find_sid_for_ldap_entry(struct slapi_entry *entry,
}
done:
+ slapi_ch_free_string(&uniqueid);
slapi_ch_free_string(&sid);
slapi_pblock_destroy(mod_pb);
slapi_mods_free(&smods);
--
2.43.0
From 07150b71537744f491d022c737ef04775c72a10a Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
Date: Tue, 23 Jan 2024 14:53:39 +0200
Subject: [PATCH] sidgen: fix missing prototypes
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: Thierry Bordaz <tbordaz@redhat.com>
---
daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.h | 3 +++
1 file changed, 3 insertions(+)
diff --git a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.h b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.h
index bd46982d0..aec862796 100644
--- a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.h
+++ b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.h
@@ -106,3 +106,6 @@ int find_sid_for_ldap_entry(struct slapi_entry *entry,
const char *base_dn,
const char *dom_sid,
struct range_info **ranges);
+
+int sidgen_task_init(Slapi_PBlock *pb);
+int ipa_sidgen_init(Slapi_PBlock *pb);
--
2.43.0

View File

@ -0,0 +1,224 @@
From 0800065ac5555dba102f05c947ca47b5dc9a81af Mon Sep 17 00:00:00 2001
From: Rafael Guterres Jeffman <rjeffman@redhat.com>
Date: Fri, 23 Jan 2026 16:49:31 -0300
Subject: [PATCH] freeipa.spec.in: Use systemd-sysusers to setup users and
groups
System accounts for `kdcproxy` and `ipaapi` are now created with
sysusers configuration and macros. User `apache` is updated, by
adding it to group `ipaapi` using sysusers configuration.
Fixes: https://pagure.io/freeipa/issue/9572
AI agent usage info:
The initial changes were created by Claude by providing the following
context:
>> Add support for creating users through systemd-sysusers by creating
>> a folder init/sysusersd, similar to init/tmpfilesd, changing install
>> paths in init/sysusersd/Makefile.am, adding configure option
>> --with-systemdsysusersdir similar to --with-systemdtmpfilesdir, and
>> adding a new file init/sysusersd/freeeipo.sysusers.in with the
>> contents:
>> ```
>> # system accounts for IPA
>> u! kdcproxy - "IPA KDC Proxy Uer"
>> u! ipaapi - "IPA Framework User"
>> # - add Apache HTTPd user to ipaapi group
>> m apache ipaapi
>> ```
>> and updating de spec file freeipa.spec.in
LLM model used was Claude Sonnet 4.5, and a CLAUDE.md file was
automatically created by claude based on the freeipa repository.
No custom context was available for the agent.
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Rafael Guterres Jeffman <rjeffman@redhat.com>
Reviewed-By: David Hanina <dhanina@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
configure.ac | 42 ++++++++++++++++++++++++++------------
freeipa.spec.in | 16 +++------------
init/Makefile.am | 2 +-
init/sysusersd/Makefile.am | 12 +++++++++++
init/sysusersd/ipa.conf.in | 8 ++++++++
5 files changed, 53 insertions(+), 27 deletions(-)
create mode 100644 init/sysusersd/Makefile.am
create mode 100644 init/sysusersd/ipa.conf.in
diff --git a/configure.ac b/configure.ac
index 8b9adec1559c8831ef39c27860c1d31496ec5474..b0462bf779dedb7c2fe59494d4eb64a6dd121b1a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -267,6 +267,13 @@ AC_ARG_WITH([systemdtmpfilesdir],
[systemdtmpfilesdir=$($PKG_CONFIG --define-variable=prefix='${prefix}' --variable=tmpfilesdir systemd)])
AC_SUBST([systemdtmpfilesdir])
+AC_ARG_WITH([systemdsysusersdir],
+ AS_HELP_STRING([--with-systemdsysusersdir=DIR],
+ [Directory for systemd-sysusers configuration files]),
+ [systemdsysusersdir=$with_systemdsysusersdir],
+ [systemdsysusersdir=$($PKG_CONFIG --define-variable=prefix='${prefix}' --variable=sysusersdir systemd)])
+AC_SUBST([systemdsysusersdir])
+
AC_ARG_WITH([systemdcatalogdir],
AS_HELP_STRING([--with-systemdcatalogdir=DIR],
[Directory for systemd journal catalog files]),
@@ -398,22 +405,29 @@ AC_SUBST([IPAPLATFORM])
AC_MSG_RESULT([${IPAPLATFORM}])
if test "x${IPAPLATFORM}" == "xdebian"; then
- HTTPD_GROUP="www-data"
- KRB5KDC_SERVICE="krb5-kdc.service"
- NAMED_GROUP="bind"
- ODS_USER="opendnssec"
- ODS_GROUP="opendnssec"
- # see https://www.debian.org/doc/packaging-manuals/python-policy/ap-packaging_tools.html
- PYTHON_INSTALL_EXTRA_OPTIONS="--install-layout=deb"
+ dnl Ubuntu http user is www-data
+ HTTPD_USER="www-data"
+ HTTPD_GROUP="www-data"
+ KRB5KDC_SERVICE="krb5-kdc.service"
+ NAMED_GROUP="bind"
+ ODS_USER="opendnssec"
+ ODS_GROUP="opendnssec"
+ # see https://www.debian.org/doc/packaging-manuals/python-policy/ap-packaging_tools.html
+ PYTHON_INSTALL_EXTRA_OPTIONS="--install-layout=deb"
else
- HTTPD_GROUP="apache"
- KRB5KDC_SERVICE="krb5kdc.service"
- NAMED_GROUP="named"
- ODS_USER="ods"
- ODS_GROUP="ods"
- PYTHON_INSTALL_EXTRA_OPTIONS=""
+ HTTPD_USER="apache"
+ HTTPD_GROUP="apache"
+ KRB5KDC_SERVICE="krb5kdc.service"
+ NAMED_GROUP="named"
+ ODS_USER="ods"
+ ODS_GROUP="ods"
+ PYTHON_INSTALL_EXTRA_OPTIONS=""
fi
+AC_MSG_CHECKING([HTTPD_USER])
+AC_SUBST([HTTPD_USER])
+AC_MSG_RESULT([${HTTPD_USER}])
+
AC_MSG_CHECKING([HTTPD_GROUP])
AC_SUBST([HTTPD_GROUP])
AC_MSG_RESULT([${HTTPD_GROUP}])
@@ -654,6 +668,7 @@ AC_CONFIG_FILES([
daemons/ipa-slapi-plugins/topology/Makefile
init/systemd/Makefile
init/tmpfilesd/Makefile
+ init/sysusersd/Makefile
init/Makefile
install/Makefile
install/certmonger/Makefile
@@ -736,6 +751,7 @@ AM_COND_IF([ENABLE_SERVER], [
KRAD libs: ${KRAD_LIBS}
krb5rundir: ${krb5rundir}
systemdtmpfilesdir: ${systemdtmpfilesdir}
+ systemdsysusersdir: ${systemdsysusersdir}
build mode: server & client"
], [
echo "\
diff --git a/freeipa.spec.in b/freeipa.spec.in
index f3b45a5308f93928a4d4bb4cbb2ae96c487cf88a..48912185073472c11f08d000dacf3a0b7f2ec668 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -620,7 +620,7 @@ Requires: systemd-units >= %{systemd_version}
Requires: system-logos-ipa >= 80.4
%endif
-# The list below is automatically generated by `fix-spec.sh -i`
+# The list below is automatically generated by `fix-spec.sh -i`
# from the install/freeipa-webui
Provides: bundled(npm(attr-accept)) = 2.2.5
Provides: bundled(npm(cookie)) = 1.0.2
@@ -1274,6 +1274,7 @@ fi
/bin/systemctl reload-or-try-restart dbus
/bin/systemctl reload-or-try-restart oddjobd
+%sysusers_create %{_sysusersdir}/ipa.conf
%tmpfiles_create ipa.conf
%journal_catalog_update
@@ -1331,18 +1332,6 @@ if [ -e /usr/sbin/ipa_kpasswd ]; then
fi
-%pre server-common
-# create users and groups
-# create kdcproxy group and user
-getent group kdcproxy >/dev/null || groupadd -f -r kdcproxy
-getent passwd kdcproxy >/dev/null || useradd -r -g kdcproxy -s /sbin/nologin -d / -c "IPA KDC Proxy User" kdcproxy
-# create ipaapi group and user
-getent group ipaapi >/dev/null || groupadd -f -r ipaapi
-getent passwd ipaapi >/dev/null || useradd -r -g ipaapi -s /sbin/nologin -d / -c "IPA Framework User" ipaapi
-# add apache to ipaaapi group
-id -Gn apache | grep '\bipaapi\b' >/dev/null || usermod apache -a -G ipaapi
-
-
%post server-dns
%systemd_post ipa-dnskeysyncd.service ipa-ods-exporter.socket ipa-ods-exporter.service
@@ -1729,6 +1718,7 @@ fi
%dir %attr(0755,root,root) %{_sysconfdir}/ipa/kdcproxy
%config(noreplace) %{_sysconfdir}/ipa/kdcproxy/kdcproxy.conf
# NOTE: systemd specific section
+%{_sysusersdir}/ipa.conf
%{_tmpfilesdir}/ipa.conf
%attr(644,root,root) %{_unitdir}/ipa-custodia.service
%ghost %attr(644,root,root) %{etc_systemd_dir}/httpd.d/ipa.conf
diff --git a/init/Makefile.am b/init/Makefile.am
index 8f4d1d0a8f7e9739cf7587de6e000dd027a85146..1d4a85ab20e892c8a7c428b84a6393d29e9616e5 100644
--- a/init/Makefile.am
+++ b/init/Makefile.am
@@ -2,7 +2,7 @@
#
AUTOMAKE_OPTIONS = 1.7
-SUBDIRS = systemd tmpfilesd
+SUBDIRS = systemd tmpfilesd sysusersd
dist_sysconfenv_DATA = \
ipa-dnskeysyncd \
diff --git a/init/sysusersd/Makefile.am b/init/sysusersd/Makefile.am
new file mode 100644
index 0000000000000000000000000000000000000000..8577255a61ac796353995d3d1f99de195f9bd7c0
--- /dev/null
+++ b/init/sysusersd/Makefile.am
@@ -0,0 +1,12 @@
+dist_noinst_DATA = \
+ ipa.conf.in
+
+systemdsysusers_DATA = \
+ ipa.conf
+
+CLEANFILES = $(systemdsysusers_DATA)
+
+%: %.in Makefile
+ sed \
+ -e 's|@HTTPD_USER[@]|$(HTTPD_USER)|g' \
+ '$(srcdir)/$@.in' >$@
diff --git a/init/sysusersd/ipa.conf.in b/init/sysusersd/ipa.conf.in
new file mode 100644
index 0000000000000000000000000000000000000000..dcddfc2fc7969b86913ffcd8c397152e4f800fda
--- /dev/null
+++ b/init/sysusersd/ipa.conf.in
@@ -0,0 +1,8 @@
+# IPA KDC Proxy user and group
+u kdcproxy - "IPA KDC Proxy User"
+
+# IPA API user and group
+u ipaapi - "IPA API User"
+
+# - add Apache system account to ipaapi group (platform-specific)
+m @HTTPD_USER@ ipaapi
--
2.52.0

View File

@ -1,310 +0,0 @@
From 67ca47ba4092811029eec02f8af9c34ba7662924 Mon Sep 17 00:00:00 2001
From: Julien Rische <jrische@redhat.com>
Date: Mon, 9 Oct 2023 15:47:03 +0200
Subject: [PATCH] ipa-kdb: Ensure Bronze-Bit check can be enabled
MIT krb5 1.19 and older do not implement support for PAC ticket
signature to protect the encrypted part of tickets. This is the cause of
the Bronze-Bit vulnerability (CVE-2020-17043). The Bronze-Bit attack
detection mechanism introduced in a847e248 relies on the content of the
PAC.
However, since CVE-2022-37967, the content of the PAC can no longer be
trusted if the KDC does not support PAC extended KDC signature (aka.
PAC full checksum). This signature is supported in MIT krb5 since
version 1.21.
Support for the PAC extended KDC signature was backported downstream to
krb5 1.18.2 for CentOS 8 Stream (dist-git commit 7d215a54). This makes
the content of the PAC still trustworthy there.
This commit disables the Bronze-Bit attack detection mechanism at build
time in case krb5 does not provide the krb5_pac_full_sign_compat()
function.
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
daemons/ipa-kdb/ipa_kdb.h | 4 ++++
daemons/ipa-kdb/ipa_kdb_kdcpolicy.c | 7 +++++++
daemons/ipa-kdb/ipa_kdb_mspac.c | 4 ++++
3 files changed, 15 insertions(+)
diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h
index 02b2cb631..c6926f7d5 100644
--- a/daemons/ipa-kdb/ipa_kdb.h
+++ b/daemons/ipa-kdb/ipa_kdb.h
@@ -367,6 +367,8 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
const char *test_realm, size_t size,
char **trusted_realm);
+#if KRB5_KDB_DAL_MAJOR_VERSION <= 8
+# ifdef HAVE_KRB5_PAC_FULL_SIGN_COMPAT
/* Try to detect a Bronze-Bit attack based on the content of the request and
* data from the KDB.
*
@@ -379,6 +381,8 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
krb5_error_code
ipadb_check_for_bronze_bit_attack(krb5_context context, krb5_kdc_req *request,
bool *detected, const char **status);
+# endif
+#endif
/* DELEGATION CHECKS */
diff --git a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
index 1032dff0b..ee0546c01 100644
--- a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
+++ b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
@@ -185,11 +185,18 @@ ipa_kdcpolicy_check_tgs(krb5_context context, krb5_kdcpolicy_moddata moddata,
const char **status, krb5_deltat *lifetime_out,
krb5_deltat *renew_lifetime_out)
{
+#if KRB5_KDB_DAL_MAJOR_VERSION <= 8
+# ifdef HAVE_KRB5_PAC_FULL_SIGN_COMPAT
krb5_error_code kerr;
kerr = ipadb_check_for_bronze_bit_attack(context, request, NULL, status);
if (kerr)
return KRB5KDC_ERR_POLICY;
+# else
+# warning Support for Kerberos PAC extended KDC signature is missing.\
+ This makes FreeIPA vulnerable to the Bronze-Bit exploit (CVE-2020-17049).
+# endif
+#endif
*status = NULL;
*lifetime_out = 0;
diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
index b4e22d431..05d5b407d 100644
--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
+++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
@@ -3299,6 +3299,8 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
return KRB5_KDB_NOENTRY;
}
+#if KRB5_KDB_DAL_MAJOR_VERSION <= 8
+# ifdef HAVE_KRB5_PAC_FULL_SIGN_COMPAT
krb5_error_code
ipadb_check_for_bronze_bit_attack(krb5_context context, krb5_kdc_req *request,
bool *detected, const char **status)
@@ -3471,3 +3473,5 @@ end:
ipadb_free_principal(context, proxy_entry);
return kerr;
}
+# endif
+#endif
--
2.43.0
From 27b96c17dd51d076e04d97662b7c788658a5094a Mon Sep 17 00:00:00 2001
From: Julien Rische <jrische@redhat.com>
Date: Jan 26 2024 09:35:57 +0000
Subject: ipa-kdb: Disable Bronze-Bit check if PAC not available
The Bronze-Bit check introduced in commit
a847e2483b4c4832ee5129901da169f4eb0d1392 requires the MS-PAC to be
present in the evidence ticket in order for S4U2Proxy requests to be
accepted. This actually requires SIDs to be set.
However, domains that were initialized before commit
e527857d000e558b3288a7a210400abaf2171237 may still not have SIDs
configured. This would results in all S4U2Proxy requests to fail
(including all the HTTP API requests).
This present commit disables the check for the Bronze-Bit exploit
(CVE-2020-17049) in case the domain is not able to generate PACs.
Instead, it prints a warning message in the KDC logs each time a
S4U2Proxy request is processed.
Fixes: https://pagure.io/freeipa/issue/9521
Signed-off-by: Julien Rische <jrische@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h
index c6926f7..621c235 100644
--- a/daemons/ipa-kdb/ipa_kdb.h
+++ b/daemons/ipa-kdb/ipa_kdb.h
@@ -370,17 +370,21 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
#if KRB5_KDB_DAL_MAJOR_VERSION <= 8
# ifdef HAVE_KRB5_PAC_FULL_SIGN_COMPAT
/* Try to detect a Bronze-Bit attack based on the content of the request and
- * data from the KDB.
+ * data from the KDB. This check will work only if the domain supports MS-PAC.
*
* context krb5 context
* request KDB request
- * detected Set to "true" if a bronze bit attack is detected and the
- * pointer is not NULL. Remains unset otherwise.
+ * supported If not NULL, set to "false" in case the Bronze-Bit exploit
+ * detection process silently failed to complete because the
+ * domain does not meet requirements. Set to "true" otherwise.
+ * detected If not NULL, set to "true" if a Bronze-Bit attack is detected.
+ * Set to "false" otherwise.
* status If the call fails and the pointer is not NULL, set it with a
* message describing the cause of the failure. */
krb5_error_code
ipadb_check_for_bronze_bit_attack(krb5_context context, krb5_kdc_req *request,
- bool *detected, const char **status);
+ bool *supported, bool *detected,
+ const char **status);
# endif
#endif
diff --git a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
index ee0546c..713e9a0 100644
--- a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
+++ b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
@@ -188,10 +188,18 @@ ipa_kdcpolicy_check_tgs(krb5_context context, krb5_kdcpolicy_moddata moddata,
#if KRB5_KDB_DAL_MAJOR_VERSION <= 8
# ifdef HAVE_KRB5_PAC_FULL_SIGN_COMPAT
krb5_error_code kerr;
+ bool supported;
- kerr = ipadb_check_for_bronze_bit_attack(context, request, NULL, status);
+ kerr = ipadb_check_for_bronze_bit_attack(context, request, supported, NULL,
+ status);
if (kerr)
return KRB5KDC_ERR_POLICY;
+
+ if (!supported)
+ krb5_klog_syslog(LOG_WARNING, "MS-PAC not available. This makes "
+ "FreeIPA vulnerable to the Bronze-Bit exploit "
+ "(CVE-2020-17049). Please generate SIDs to enable "
+ "PAC support.");
# else
# warning Support for Kerberos PAC extended KDC signature is missing.\
This makes FreeIPA vulnerable to the Bronze-Bit exploit (CVE-2020-17049).
diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
index 05d5b40..a18beff 100644
--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
+++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
@@ -3303,11 +3303,14 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
# ifdef HAVE_KRB5_PAC_FULL_SIGN_COMPAT
krb5_error_code
ipadb_check_for_bronze_bit_attack(krb5_context context, krb5_kdc_req *request,
- bool *detected, const char **status)
+ bool *supported, bool *detected,
+ const char **status)
{
krb5_error_code kerr;
const char *st = NULL;
size_t i, j;
+ bool in_supported = true, in_detected = false;
+ struct ipadb_context *ipactx;
krb5_ticket *evidence_tkt;
krb5_authdata **authdata, **ifrel = NULL;
krb5_pac pac = NULL;
@@ -3327,6 +3330,21 @@ ipadb_check_for_bronze_bit_attack(krb5_context context, krb5_kdc_req *request,
goto end;
}
+ ipactx = ipadb_get_context(context);
+ if (!ipactx) {
+ kerr = KRB5_KDB_DBNOTINITED;
+ goto end;
+ }
+
+ /* Handle the case where the domain is not able to generate PACs (probably
+ * because SIDs are not set). In this case, we just skip the Bronze-Bit
+ * check. */
+ if (!ipactx->mspac) {
+ in_supported = false;
+ kerr = 0;
+ goto end;
+ }
+
evidence_tkt = request->second_ticket[0];
/* No need to check the Forwardable flag. If it was not set, this request
@@ -3451,8 +3469,7 @@ ipadb_check_for_bronze_bit_attack(krb5_context context, krb5_kdc_req *request,
/* This evidence ticket cannot be forwardable given the privileges
* of the proxy principal.
* This is a Bronze Bit attack. */
- if (detected)
- *detected = true;
+ in_detected = true;
st = "S4U2PROXY_BRONZE_BIT_ATTACK_DETECTED";
kerr = EBADE;
goto end;
@@ -3464,6 +3481,10 @@ ipadb_check_for_bronze_bit_attack(krb5_context context, krb5_kdc_req *request,
end:
if (st && status)
*status = st;
+ if (supported)
+ *supported = in_supported;
+ if (detected)
+ *detected = in_detected;
krb5_free_authdata(context, ifrel);
krb5_pac_free(context, pac);
From 81aa6ef695838a4b2fb5a53e773ea379a492913d Mon Sep 17 00:00:00 2001
From: Julien Rische <jrische@redhat.com>
Date: Fri, 9 Feb 2024 16:36:03 +0100
Subject: [PATCH] ipd-kdb: Fix some mistakes in
ipadb_check_for_bronze_bit_attack()
Fixes: https://pagure.io/freeipa/issue/9521
Signed-off-by: Julien Rische <jrische@redhat.com>
Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
---
daemons/ipa-kdb/ipa_kdb.h | 3 ++-
daemons/ipa-kdb/ipa_kdb_kdcpolicy.c | 2 +-
daemons/ipa-kdb/ipa_kdb_mspac.c | 5 +++--
3 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h
index 621c23591..5de5ea7a5 100644
--- a/daemons/ipa-kdb/ipa_kdb.h
+++ b/daemons/ipa-kdb/ipa_kdb.h
@@ -382,7 +382,8 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
* status If the call fails and the pointer is not NULL, set it with a
* message describing the cause of the failure. */
krb5_error_code
-ipadb_check_for_bronze_bit_attack(krb5_context context, krb5_kdc_req *request,
+ipadb_check_for_bronze_bit_attack(krb5_context context,
+ const krb5_kdc_req *request,
bool *supported, bool *detected,
const char **status);
# endif
diff --git a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
index 713e9a0c8..44959f3de 100644
--- a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
+++ b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
@@ -190,7 +190,7 @@ ipa_kdcpolicy_check_tgs(krb5_context context, krb5_kdcpolicy_moddata moddata,
krb5_error_code kerr;
bool supported;
- kerr = ipadb_check_for_bronze_bit_attack(context, request, supported, NULL,
+ kerr = ipadb_check_for_bronze_bit_attack(context, request, &supported, NULL,
status);
if (kerr)
return KRB5KDC_ERR_POLICY;
diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
index 80350364a..886ed7785 100644
--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
+++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
@@ -3308,13 +3308,14 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
#if KRB5_KDB_DAL_MAJOR_VERSION <= 8
# ifdef HAVE_KRB5_PAC_FULL_SIGN_COMPAT
krb5_error_code
-ipadb_check_for_bronze_bit_attack(krb5_context context, krb5_kdc_req *request,
+ipadb_check_for_bronze_bit_attack(krb5_context context,
+ const krb5_kdc_req *request,
bool *supported, bool *detected,
const char **status)
{
krb5_error_code kerr;
const char *st = NULL;
- size_t i, j;
+ size_t i, j = 0;
bool in_supported = true, in_detected = false;
struct ipadb_context *ipactx;
krb5_ticket *evidence_tkt;
--
2.43.0

View File

@ -0,0 +1,196 @@
From a55f9185c96457bdffe9099ddde39ec696f1f998 Mon Sep 17 00:00:00 2001
From: Anuja More <amore@redhat.com>
Date: Tue, 6 Jan 2026 18:30:06 +0530
Subject: [PATCH] ipatests: add Random Password based replica promotion
coverage
Added missing test coverage for :
- Installing IPA replica server using random password.
- Installing IPA replica server using random password installed client
- Automated with Cursor+Claude
Fixes: https://pagure.io/freeipa/issue/9922
Signed-off-by: Anuja More <amore@redhat.com>
Reviewed-By: David Hanina <dhanina@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
Reviewed-By: David Hanina <dhanina@redhat.com>
---
.../nightly_ipa-4-13_latest.yaml | 12 +++
.../nightly_ipa-4-13_latest_selinux.yaml | 13 +++
ipatests/pytest_ipa/integration/tasks.py | 15 ++++
.../test_replica_promotion.py | 87 +++++++++++++++++++
4 files changed, 127 insertions(+)
diff --git a/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml
index aff55727e463207fb235ff340989491e62162149..c61701ef5f88760f1d6fc36d4acce453a22b6f8f 100644
--- a/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml
+++ b/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml
@@ -1000,6 +1000,18 @@ jobs:
timeout: 7200
topology: *ad_master_1repl_1client
+ fedora-latest-ipa-4-13/test_replica_promotion_TestReplicaPromotionRandomPassword:
+ requires: [fedora-latest-ipa-4-13/build]
+ priority: 50
+ job:
+ class: RunPytest
+ args:
+ build_url: '{fedora-latest-ipa-4-13/build_url}'
+ test_suite: test_integration/test_replica_promotion.py::TestReplicaPromotionRandomPassword
+ template: *ci-ipa-4-13-latest
+ timeout: 7200
+ topology: *master_1repl
+
fedora-latest-ipa-4-13/test_upgrade:
requires: [fedora-latest-ipa-4-13/build]
priority: 50
diff --git a/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml
index e6c57ea060b3bb8bfdf8b6f981f8fd28e4a7d320..9b96f3e857e2125478b45632d8d58e42b6e92668 100644
--- a/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml
+++ b/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml
@@ -1078,6 +1078,19 @@ jobs:
timeout: 7200
topology: *ad_master_1repl_1client
+ fedora-latest-ipa-4-13/test_replica_promotion_TestReplicaPromotionRandomPassword:
+ requires: [fedora-latest-ipa-4-13/build]
+ priority: 50
+ job:
+ class: RunPytest
+ args:
+ build_url: '{fedora-latest-ipa-4-13/build_url}'
+ selinux_enforcing: True
+ test_suite: test_integration/test_replica_promotion.py::TestReplicaPromotionRandomPassword
+ template: *ci-ipa-4-13-latest
+ timeout: 7200
+ topology: *master_1repl
+
fedora-latest-ipa-4-13/test_upgrade:
requires: [fedora-latest-ipa-4-13/build]
priority: 50
diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py
index 32ac5cbc2c6fe87850dfb15c1d5beae6fa648dfb..ff2ea9792d04ebd2e6bd7bb3b51d97f35cb3fbfb 100755
--- a/ipatests/pytest_ipa/integration/tasks.py
+++ b/ipatests/pytest_ipa/integration/tasks.py
@@ -3340,3 +3340,18 @@ def service_control_dirsrv(host, function='restart'):
instance = realm_to_serverid(host.domain.realm)
cmd = host.run_command(['systemctl', function, f"dirsrv@{instance}"])
assert cmd.returncode == 0
+
+
+def host_add_with_random_password(host, new_host):
+ """
+ Add a new host with a random password and return the generated password.
+ """
+ kinit_admin(host)
+ cmd = host.run_command(
+ ['ipa', 'host-add', new_host.hostname, '--random']
+ )
+ result = re.search("Random password: (?P<password>.*$)",
+ cmd.stdout_text,
+ re.MULTILINE)
+ randpasswd1 = result.group('password')
+ return randpasswd1
diff --git a/ipatests/test_integration/test_replica_promotion.py b/ipatests/test_integration/test_replica_promotion.py
index 76d6aa24e2ab3d88b7013e0d107d0e27ae7f3426..f8c8414eefbc015cfc0947de575ea349a65a5e73 100644
--- a/ipatests/test_integration/test_replica_promotion.py
+++ b/ipatests/test_integration/test_replica_promotion.py
@@ -1368,3 +1368,90 @@ class TestReplicaConn(IntegrationTest):
logs = self.replica.get_file_contents(paths.IPAREPLICA_CONNCHECK_LOG)
error = "not allowed to perform server connection check"
assert error.encode() not in logs
+
+
+class TestReplicaPromotionRandomPassword(IntegrationTest):
+ """
+ Test installation of a replica using Random Password
+ (one step install and two-steps installation
+ with client and promotion).
+ """
+ num_replicas = 1
+
+ @classmethod
+ def install(cls, mh):
+ tasks.install_master(cls.master, setup_dns=True)
+ cls.replicas[0].resolver.backup()
+ nameservers = cls.master.ip
+ cls.replicas[0].resolver.setup_resolver(
+ nameservers, cls.master.domain.name
+ )
+
+ @replicas_cleanup
+ def test_replica_random_password_install(self):
+ """
+ Installing IPA replica server using Random Password.
+
+ Steps:
+ 1. Ensure replica host/server entries are clean and add DNS A record.
+ 2. Add the replica host with a random password and add it to
+ the ipaservers hostgroup.
+ 3. Install the replica using random password.
+ """
+ replica = self.replicas[0]
+ tasks.kinit_admin(self.master)
+ tasks.add_a_record(self.master, replica)
+ randpasswd = tasks.host_add_with_random_password(self.master,
+ replica)
+ self.master.run_command([
+ 'ipa', 'hostgroup-add-member', '--hosts',
+ replica.hostname, 'ipaservers'
+ ])
+ replica.run_command(
+ ['ipa-replica-install', '-p', randpasswd, '-U']
+ )
+
+ @replicas_cleanup
+ def test_replica_two_step_install(self):
+ """
+ Installing IPA replica server using Random Password installed client
+
+ Steps:
+ 1. Ensure replica host/server entries are clean and add DNS A record.
+ 2. Add the replica host with a random password and add it to
+ the ipaservers hostgroup.
+ 3. Install the IPA client using the Random Password.
+ 4. Promote the client to a replica.
+ 5. Install CA on the replica and verify the server role.
+ """
+ replica = self.replicas[0]
+ replica.resolver.backup()
+ tasks.kinit_admin(self.master)
+ tasks.add_a_record(self.master, replica)
+ randpasswd = tasks.host_add_with_random_password(self.master,
+ replica)
+ self.master.run_command([
+ 'ipa', 'hostgroup-add-member', '--hosts',
+ replica.hostname, 'ipaservers'
+ ])
+ replica.resolver.setup_resolver(
+ self.master.ip, self.master.domain.name
+ )
+ replica.run_command(
+ ['ipa-client-install', '-w', randpasswd, '-U']
+ )
+ Firewall(replica).enable_services(["freeipa-ldap",
+ "freeipa-ldaps"])
+ replica.run_command(['ipa-replica-install', '-U'])
+ tasks.kinit_admin(replica)
+ replica.run_command([
+ 'ipa-ca-install', '-p',
+ self.master.config.admin_password,
+ '-w', self.master.config.admin_password
+ ])
+ result = self.replicas[0].run_command([
+ 'ipa', 'server-role-find',
+ '--server', self.replicas[0].hostname,
+ '--role', 'CA server'
+ ])
+ assert 'Role status: enabled' in result.stdout_text
--
2.52.0

View File

@ -0,0 +1,760 @@
From 7cb2c5d38a84eb31aa9ddd20cf9dc0b6d90fa242 Mon Sep 17 00:00:00 2001
From: PRANAV THUBE <pthube@redhat.com>
Date: Tue, 27 Jan 2026 18:45:53 +0530
Subject: [PATCH] ipatests: Add integration tests for ipa-join command
Add tests for ipa-join command covering hostname, server, keytab,
and bindpw options with positive and negative scenarios.
Related: https://pagure.io/freeipa/issue/9930
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
---
ipatests/prci_definitions/gating.yaml | 12 +
.../nightly_ipa-4-13_latest.yaml | 12 +
.../nightly_ipa-4-13_latest_selinux.yaml | 13 +
ipatests/pytest_ipa/integration/tasks.py | 49 ++
ipatests/test_integration/test_ipa_join.py | 614 ++++++++++++++++++
5 files changed, 700 insertions(+)
create mode 100644 ipatests/test_integration/test_ipa_join.py
diff --git a/ipatests/prci_definitions/gating.yaml b/ipatests/prci_definitions/gating.yaml
index 0c2a0dafa9de12add8959d9974f080ba8bec0706..182f2c5a097856d22336fa66a0876bdfcf3f3f8d 100644
--- a/ipatests/prci_definitions/gating.yaml
+++ b/ipatests/prci_definitions/gating.yaml
@@ -394,3 +394,15 @@ jobs:
template: *ci-ipa-4-13-latest
timeout: 7200
topology: *master_3client
+
+ fedora-latest-ipa-4-13/test_ipa_join:
+ requires: [fedora-latest-ipa-4-13/build]
+ priority: 100
+ job:
+ class: RunPytest
+ args:
+ build_url: '{fedora-latest-ipa-4-13/build_url}'
+ test_suite: test_integration/test_ipa_join.py
+ template: *ci-ipa-4-13-latest
+ timeout: 3600
+ topology: *master_1repl_1client
diff --git a/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml b/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml
index c61701ef5f88760f1d6fc36d4acce453a22b6f8f..aba33a5a05185460305c7516c93ae25c60f9dda7 100644
--- a/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml
+++ b/ipatests/prci_definitions/nightly_ipa-4-13_latest.yaml
@@ -2292,3 +2292,15 @@ jobs:
template: *ci-ipa-4-13-latest
timeout: 2400
topology: *ipaserver
+
+ fedora-latest-ipa-4-13/test_ipa_join:
+ requires: [fedora-latest-ipa-4-13/build]
+ priority: 50
+ job:
+ class: RunPytest
+ args:
+ build_url: '{fedora-latest-ipa-4-13/build_url}'
+ test_suite: test_integration/test_ipa_join.py
+ template: *ci-ipa-4-13-latest
+ timeout: 3600
+ topology: *master_1repl_1client
diff --git a/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml b/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml
index 9b96f3e857e2125478b45632d8d58e42b6e92668..7184b722076ba2cab7d782d990a9bc218158a09f 100644
--- a/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml
+++ b/ipatests/prci_definitions/nightly_ipa-4-13_latest_selinux.yaml
@@ -2476,3 +2476,16 @@ jobs:
template: *ci-ipa-4-13-latest
timeout: 2400
topology: *ipaserver
+
+ fedora-latest-ipa-4-13/test_ipa_join:
+ requires: [fedora-latest-ipa-4-13/build]
+ priority: 50
+ job:
+ class: RunPytest
+ args:
+ build_url: '{fedora-latest-ipa-4-13/build_url}'
+ selinux_enforcing: True
+ test_suite: test_integration/test_ipa_join.py
+ template: *ci-ipa-4-13-latest
+ timeout: 3600
+ topology: *master_1repl_1client
diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py
index ff2ea9792d04ebd2e6bd7bb3b51d97f35cb3fbfb..47330d6d93401485e4eb7b2501cf5ea37498d719 100755
--- a/ipatests/pytest_ipa/integration/tasks.py
+++ b/ipatests/pytest_ipa/integration/tasks.py
@@ -3355,3 +3355,52 @@ def host_add_with_random_password(host, new_host):
re.MULTILINE)
randpasswd1 = result.group('password')
return randpasswd1
+
+
+def ipa_join(host, *extra_args, raiseonerr=True):
+ """Run ipa-join command.
+
+ :param host: The host to run command on
+ :param extra_args: Additional arguments (variable positional args)
+ e.g., '--hostname=client.example.com',
+ '--server=master.example.com',
+ '--keytab=/tmp/test.keytab',
+ '--bindpw=password',
+ '-u' (for unenroll)
+ :param raiseonerr: If True, raise exception on command failure
+ :return: Command result object
+ """
+ command = ['ipa-join']
+ command.extend(extra_args)
+ return host.run_command(command, raiseonerr=raiseonerr)
+
+
+def host_del(host, hostname, *extra_args, raiseonerr=True):
+ """Delete a host from IPA.
+
+ :param host: The IPA host to run command on
+ :param hostname: Hostname to delete
+ :param extra_args: Additional arguments (variable positional args)
+ :param raiseonerr: If True, raise exception on command failure
+ :return: Command result object
+ """
+ command = ['ipa', 'host-del', hostname]
+ command.extend(extra_args)
+ return host.run_command(command, raiseonerr=raiseonerr)
+
+
+def host_add(host, hostname, *extra_args, password=None, raiseonerr=True):
+ """Add a host to IPA.
+
+ :param host: The IPA host to run command on
+ :param hostname: Hostname to add
+ :param extra_args: Additional arguments (variable positional args)
+ :param password: OTP/enrollment password for the host (optional)
+ :param raiseonerr: If True, raise exception on command failure
+ :return: Command result object
+ """
+ command = ['ipa', 'host-add', hostname]
+ if password:
+ command.append(f'--password={password}')
+ command.extend(extra_args)
+ return host.run_command(command, raiseonerr=raiseonerr)
diff --git a/ipatests/test_integration/test_ipa_join.py b/ipatests/test_integration/test_ipa_join.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f7592aec8db1bfd048ec574d06d25bc24373499
--- /dev/null
+++ b/ipatests/test_integration/test_ipa_join.py
@@ -0,0 +1,614 @@
+#
+# Copyright (C) 2026 FreeIPA Contributors see COPYING for license
+#
+
+"""
+Tests for ipa-join command functionality.
+
+This module tests various combinations of ipa-join options including:
+- hostname
+- server
+- keytab
+- bindpw (OTP/enrollment password)
+- unenroll
+
+Ported from the shell-based test suite (t.ipajoin.sh and t.ipaotp.sh).
+"""
+
+from __future__ import absolute_import
+
+from ipapython.ipautil import ipa_generate_password
+from ipatests.pytest_ipa.integration import tasks
+from ipatests.test_integration.base import IntegrationTest
+
+
+# Constants
+OTP = ipa_generate_password(special=None)
+INVALID_PASSWORD = "WrongPassword"
+INVALID_SERVER = "No.Such.IPA.Server.Domain.com"
+TEST_KEYTAB = "/tmp/ipajoin.test.keytab"
+
+# Error messages
+ERR_SASL_BIND_FAILED = "SASL Bind failed"
+ERR_UNAUTHENTICATED_BIND = "Unauthenticated binds are not allowed"
+ERR_COULD_NOT_RESOLVE = "JSON-RPC call failed: Could not resolve hostname"
+ERR_UNABLE_ROOT_DN = "Unable to determine root DN"
+ERR_NO_CONFIG = "Unable to determine IPA server from /etc/ipa/default.conf"
+ERR_PREAUTH_FAILED = "Generic preauthentication failure"
+
+# Exit codes
+EXIT_SUCCESS = 0
+EXIT_GENERAL_ERROR = 1
+EXIT_PREAUTH_ERROR = 19
+EXIT_ROOT_DN_ERROR = 14
+EXIT_SASL_BIND_FAILED = 15
+EXIT_RESOLVE_ERROR = 17
+
+
+class TestIPAJoin(IntegrationTest):
+ """Tests for ipa-join command functionality.
+
+ This test class covers various ipa-join scenarios including:
+ - Basic enrollment and unenrollment
+ - Using hostname, server, keytab, and bindpw options
+ - Positive and negative test cases
+ - OTP (one-time password) enrollment tests
+
+ Tests require one master and one client.
+ """
+
+ topology = 'line'
+ num_clients = 1
+
+ @classmethod
+ def install(cls, mh):
+ tasks.install_master(cls.master, setup_dns=True)
+ tasks.install_client(cls.master, cls.clients[0])
+
+ @classmethod
+ def uninstall(cls, mh):
+ # Cleanup test keytab if exists
+ cls.clients[0].run_command(
+ ['rm', '-f', TEST_KEYTAB],
+ raiseonerr=False
+ )
+ tasks.uninstall_client(cls.clients[0])
+ tasks.uninstall_master(cls.master)
+
+ # =========================================================================
+ # ipa-join basic tests
+ # =========================================================================
+
+ def test_unenroll(self):
+ """Test ipa-join --unenroll option."""
+ result = tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+ assert result.returncode == EXIT_SUCCESS
+
+ def test_unenroll_already_unenrolled(self):
+ """Test ipa-join -u on an already unenrolled client.
+
+ When trying to unenroll a client that is not enrolled,
+ ipa-join should fail with a preauthentication error.
+ """
+ # Client is already unenrolled from previous test
+ result = tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ assert result.returncode == EXIT_PREAUTH_ERROR
+ assert ERR_PREAUTH_FAILED in result.stderr_text
+
+ def test_hostname_with_kerberos(self):
+ """Test ipa-join with --hostname using Kerberos auth."""
+ tasks.kinit_admin(self.clients[0])
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_hostname_bindpw_invalid(self):
+ """Test ipa-join with hostname and invalid bindpw."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
+
+ def test_hostname_bindpw_valid(self):
+ """Test ipa-join with hostname and valid OTP."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_hostname_keytab_with_kerberos(self):
+ """Test ipa-join with hostname and keytab using Kerberos."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--keytab={TEST_KEYTAB}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_hostname_keytab_bindpw_invalid(self):
+ """Test ipa-join with hostname, keytab, and invalid bindpw."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--keytab={TEST_KEYTAB}',
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
+
+ def test_hostname_keytab_bindpw_valid(self):
+ """Test ipa-join with hostname, keytab, and valid OTP."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--keytab={TEST_KEYTAB}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_hostname_server_invalid_with_kerberos(self):
+ """Test ipa-join with hostname and invalid server."""
+ tasks.kinit_admin(self.clients[0])
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={INVALID_SERVER}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_RESOLVE_ERROR
+ assert ERR_COULD_NOT_RESOLVE in result.stderr_text
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+
+ def test_hostname_server_invalid_bindpw_valid(self):
+ """Test ipa-join with hostname, invalid server, and valid OTP."""
+ tasks.kinit_admin(self.clients[0])
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={INVALID_SERVER}',
+ f'--bindpw={OTP}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_ROOT_DN_ERROR
+ assert ERR_UNABLE_ROOT_DN in result.stderr_text
+
+ def test_hostname_server_invalid_keytab_with_kerberos(self):
+ """Test ipa-join with hostname, invalid server, keytab."""
+ tasks.kinit_admin(self.clients[0])
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={INVALID_SERVER}',
+ f'--keytab={TEST_KEYTAB}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_RESOLVE_ERROR
+ assert ERR_COULD_NOT_RESOLVE in result.stderr_text
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+
+ def test_hostname_server_invalid_keytab_bindpw_valid(self):
+ """Test ipa-join with hostname, invalid server, keytab, valid OTP."""
+ tasks.kinit_admin(self.clients[0])
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={INVALID_SERVER}',
+ f'--keytab={TEST_KEYTAB}',
+ f'--bindpw={OTP}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_ROOT_DN_ERROR
+ assert ERR_UNABLE_ROOT_DN in result.stderr_text
+
+ def test_hostname_server_valid_with_kerberos(self):
+ """Test ipa-join with hostname and valid server."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={self.master.hostname}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_hostname_server_valid_bindpw_invalid(self):
+ """Test ipa-join with hostname, valid server, invalid bindpw."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={self.master.hostname}',
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
+
+ def test_hostname_server_valid_bindpw_valid(self):
+ """Test ipa-join with hostname, valid server, valid OTP."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={self.master.hostname}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_hostname_server_valid_keytab_with_kerberos(self):
+ """Test ipa-join with hostname, valid server, keytab."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname)
+
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={self.master.hostname}',
+ f'--keytab={TEST_KEYTAB}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_hostname_server_valid_keytab_bindpw_invalid(self):
+ """Test ipa-join with hostname, valid server, keytab, bad bindpw."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={self.master.hostname}',
+ f'--keytab={TEST_KEYTAB}',
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ # Note: Original test had "SASL Bind Failed" (capital F), checking both
+ assert "SASL Bind" in result.stderr_text
+ assert "ailed" in result.stderr_text
+
+ def test_hostname_server_valid_keytab_bindpw_valid(self):
+ """Test ipa-join with hostname, valid server, keytab, valid OTP."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--server={self.master.hostname}',
+ f'--keytab={TEST_KEYTAB}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_keytab_only_with_kerberos(self):
+ """Test ipa-join with keytab only using Kerberos."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname)
+
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--keytab={TEST_KEYTAB}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_keytab_bindpw_invalid(self):
+ """Test ipa-join with keytab and invalid bindpw."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--keytab={TEST_KEYTAB}',
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
+
+ def test_keytab_bindpw_valid(self):
+ """Test ipa-join with keytab and valid OTP."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--keytab={TEST_KEYTAB}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_server_invalid_only_with_kerberos(self):
+ """Test ipa-join with invalid server only."""
+ tasks.kinit_admin(self.clients[0])
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--server={INVALID_SERVER}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_RESOLVE_ERROR
+ assert ERR_COULD_NOT_RESOLVE in result.stderr_text
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+
+ def test_server_invalid_bindpw_valid(self):
+ """Test ipa-join with invalid server and valid OTP."""
+ tasks.kinit_admin(self.clients[0])
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--server={INVALID_SERVER}',
+ f'--bindpw={OTP}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_ROOT_DN_ERROR
+ assert ERR_UNABLE_ROOT_DN in result.stderr_text
+
+ def test_server_invalid_keytab_with_kerberos(self):
+ """Test ipa-join with invalid server and keytab."""
+ tasks.kinit_admin(self.clients[0])
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--server={INVALID_SERVER}',
+ f'--keytab={TEST_KEYTAB}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_RESOLVE_ERROR
+ assert ERR_COULD_NOT_RESOLVE in result.stderr_text
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+
+ def test_server_valid_only_with_kerberos(self):
+ """Test ipa-join with valid server only."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname)
+
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--server={self.master.hostname}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_server_valid_bindpw_invalid(self):
+ """Test ipa-join with valid server and invalid bindpw."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--server={self.master.hostname}',
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
+
+ def test_server_valid_bindpw_valid(self):
+ """Test ipa-join with valid server and valid OTP."""
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--server={self.master.hostname}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_server_valid_keytab_with_kerberos(self):
+ """Test ipa-join with valid server and keytab."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname)
+
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--server={self.master.hostname}',
+ f'--keytab={TEST_KEYTAB}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_bindpw_invalid_only(self):
+ """Test ipa-join with invalid bindpw only."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
+
+ # =========================================================================
+ # OTP (One-Time Password) tests
+ # =========================================================================
+
+ def test_otp_empty_password(self):
+ """Test ipa-join with empty OTP password (ipa_otp_1001)."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ '--bindpw=',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_ROOT_DN_ERROR
+ assert ERR_UNAUTHENTICATED_BIND in result.stderr_text
+
+ def test_otp_wrong_password(self):
+ """Test ipa-join with wrong OTP password (ipa_otp_1002)."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--bindpw={INVALID_PASSWORD}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
+
+ def test_otp_valid_password(self):
+ """Test ipa-join with valid OTP password (ipa_otp_1003)."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+ try:
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ def test_otp_reuse_fails(self):
+ """Test that reusing the same OTP fails (ipa_otp_1004)."""
+ tasks.kinit_admin(self.clients[0])
+ tasks.kinit_admin(self.master)
+ tasks.host_del(self.master, self.clients[0].hostname, raiseonerr=False)
+ tasks.host_add(self.master, self.clients[0].hostname, password=OTP)
+ try:
+ # First use should succeed
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--bindpw={OTP}'
+ )
+ assert result.returncode == EXIT_SUCCESS
+ finally:
+ self.clients[0].run_command(['kdestroy', '-A'], raiseonerr=False)
+ tasks.ipa_join(self.clients[0], '-u', raiseonerr=False)
+
+ # Second use of same OTP should fail
+ result = tasks.ipa_join(
+ self.clients[0],
+ f'--hostname={self.clients[0].hostname}',
+ f'--bindpw={OTP}',
+ raiseonerr=False
+ )
+
+ assert result.returncode == EXIT_SASL_BIND_FAILED
+ assert ERR_SASL_BIND_FAILED in result.stderr_text
--
2.52.0

View File

@ -1,271 +0,0 @@
From 00f8ddbfd2795228b343e1c39c1944b44d482c18 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
Date: Fri, 24 Nov 2023 11:46:19 +0200
Subject: [PATCH 1/4] ipa-kdb: add better detection of allowed user auth type
If default user authentication type is set to a list that does not
include a password or a hardened credential, the resulting configuration
might be incorrect for special service principals, including a krbtgt/..
one.
Add detection of special principals to avoid these situations and always
allow password or hardened for services.
Special handling is needed for the following principals:
- krbtgt/.. -- TGT service principals
- K/M -- master key principal
- kadmin/changepw -- service for changing passwords
- kadmin/kadmin -- kadmin service principal
- kadmin/history -- key used to encrypt history
Additionally, implicitly allow password or hardened credential use for
IPA services and IPA hosts since applications typically use keytabs for
that purpose.
Fixes: https://pagure.io/freeipa/issue/9485
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
---
daemons/ipa-kdb/ipa_kdb.c | 62 ++++++++++++++++++++++++++++++++++-----
1 file changed, 54 insertions(+), 8 deletions(-)
diff --git a/daemons/ipa-kdb/ipa_kdb.c b/daemons/ipa-kdb/ipa_kdb.c
index 06d511c76..dbb98dba6 100644
--- a/daemons/ipa-kdb/ipa_kdb.c
+++ b/daemons/ipa-kdb/ipa_kdb.c
@@ -26,6 +26,7 @@
#include "ipa_kdb.h"
#include "ipa_krb5.h"
#include "ipa_hostname.h"
+#include <kadm5/admin.h>
#define IPADB_GLOBAL_CONFIG_CACHE_TIME 60
@@ -207,5 +208,18 @@ static const struct {
{ "idp", IPADB_USER_AUTH_IDP },
{ }
+},
+ objclass_table[] = {
+ { "ipaservice", IPADB_USER_AUTH_PASSWORD },
+ { "ipahost", IPADB_USER_AUTH_PASSWORD },
+ { }
+},
+ princname_table[] = {
+ { KRB5_TGS_NAME, IPADB_USER_AUTH_PASSWORD },
+ { KRB5_KDB_M_NAME, IPADB_USER_AUTH_PASSWORD },
+ { KADM5_ADMIN_SERVICE, IPADB_USER_AUTH_PASSWORD },
+ { KADM5_CHANGEPW_SERVICE, IPADB_USER_AUTH_PASSWORD },
+ { KADM5_HIST_PRINCIPAL, IPADB_USER_AUTH_PASSWORD },
+ { }
};
void ipadb_parse_user_auth(LDAP *lcontext, LDAPMessage *le,
@@ -217,17 +231,49 @@ void ipadb_parse_user_auth(LDAP *lcontext, LDAPMessage *le,
*userauth = IPADB_USER_AUTH_NONE;
vals = ldap_get_values_len(lcontext, le, IPA_USER_AUTH_TYPE);
- if (!vals)
- return;
-
- for (i = 0; vals[i]; i++) {
- for (j = 0; userauth_table[j].name; j++) {
- if (strcasecmp(vals[i]->bv_val, userauth_table[j].name) == 0) {
- *userauth |= userauth_table[j].flag;
- break;
+ if (!vals) {
+ /* if there is no explicit ipaUserAuthType set, use objectclass */
+ vals = ldap_get_values_len(lcontext, le, "objectclass");
+ if (!vals)
+ return;
+
+ for (i = 0; vals[i]; i++) {
+ for (j = 0; objclass_table[j].name; j++) {
+ if (strcasecmp(vals[i]->bv_val, objclass_table[j].name) == 0) {
+ *userauth |= objclass_table[j].flag;
+ break;
+ }
+ }
+ }
+ } else {
+ for (i = 0; vals[i]; i++) {
+ for (j = 0; userauth_table[j].name; j++) {
+ if (strcasecmp(vals[i]->bv_val, userauth_table[j].name) == 0) {
+ *userauth |= userauth_table[j].flag;
+ break;
+ }
}
}
}
+
+ /* If neither ipaUserAuthType nor objectClass were definitive,
+ * check the krbPrincipalName to see if it is krbtgt/ or K/M one */
+ if (*userauth == IPADB_USER_AUTH_NONE) {
+ ldap_value_free_len(vals);
+ vals = ldap_get_values_len(lcontext, le, "krbprincipalname");
+ if (!vals)
+ return;
+ for (i = 0; vals[i]; i++) {
+ for (j = 0; princname_table[j].name; j++) {
+ if (strncmp(vals[i]->bv_val, princname_table[j].name,
+ strlen(princname_table[j].name)) == 0) {
+ *userauth |= princname_table[j].flag;
+ break;
+ }
+ }
+ }
+
+ }
/* If password auth is enabled, enable hardened policy too. */
if (*userauth & IPADB_USER_AUTH_PASSWORD) {
*userauth |= IPADB_USER_AUTH_HARDENED;
--
2.43.0
From 69ae9febfb4462766b3bfe3e07e76550ece97b42 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
Date: Fri, 24 Nov 2023 11:54:04 +0200
Subject: [PATCH 2/4] ipa-kdb: when applying ticket policy, do not deny PKINIT
PKINIT differs from other pre-authentication methods by the fact that it
can be matched indepedently of the user authentication types via certmap
plugin in KDC.
Since PKINIT is a strong authentication method, allow its authentication
indicator and only apply the ticket policy.
Fixes: https://pagure.io/freeipa/issue/9485
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
---
daemons/ipa-kdb/ipa_kdb_kdcpolicy.c | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
index 436ee0e62..2802221c7 100644
--- a/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
+++ b/daemons/ipa-kdb/ipa_kdb_kdcpolicy.c
@@ -119,11 +119,8 @@ ipa_kdcpolicy_check_as(krb5_context context, krb5_kdcpolicy_moddata moddata,
pol_limits = &(ied->pol_limits[IPADB_USER_AUTH_IDX_RADIUS]);
} else if (strcmp(auth_indicator, "pkinit") == 0) {
valid_auth_indicators++;
- if (!(ua & IPADB_USER_AUTH_PKINIT)) {
- *status = "PKINIT pre-authentication not allowed for this user.";
- kerr = KRB5KDC_ERR_POLICY;
- goto done;
- }
+ /* allow PKINIT unconditionally -- it has passed already at this
+ * point so some certificate was useful, only apply the limits */
pol_limits = &(ied->pol_limits[IPADB_USER_AUTH_IDX_PKINIT]);
} else if (strcmp(auth_indicator, "hardened") == 0) {
valid_auth_indicators++;
--
2.43.0
From 62c44c9e69aa2721990ca3628434713e1af6f59b Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
Date: Fri, 24 Nov 2023 12:20:55 +0200
Subject: [PATCH 3/4] ipa-kdb: clarify user auth table mapping use of
_AUTH_PASSWORD
Related: https://pagure.io/freeipa/issue/9485
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
---
daemons/ipa-kdb/ipa_kdb.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/daemons/ipa-kdb/ipa_kdb.c b/daemons/ipa-kdb/ipa_kdb.c
index dbb98dba6..4e6cacf24 100644
--- a/daemons/ipa-kdb/ipa_kdb.c
+++ b/daemons/ipa-kdb/ipa_kdb.c
@@ -195,6 +195,9 @@ done:
return base;
}
+/* In this table all _AUTH_PASSWORD entries will be
+ * expanded to include _AUTH_HARDENED in ipadb_parse_user_auth()
+ * which means there is no need to explicitly add it here */
static const struct {
const char *name;
enum ipadb_user_auth flag;
--
2.43.0
From c3bc938650b19a51706d8ccd98cdf8deaa26dc28 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
Date: Fri, 24 Nov 2023 13:00:48 +0200
Subject: [PATCH 4/4] ipatests: make sure PKINIT enrollment works with a strict
policy
Previously, for a global policy which does not include
'password', krb5kdc restart was failing. Now it should succeed.
We set admin user authentication type to PASSWORD to simplify
configuration in the test.
What matters here is that global policy does not include PKINIT and that
means a code in the ticket policy check will allow PKINIT implicitly
rather than explicitly.
Related: https://pagure.io/freeipa/issue/9485
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
---
.../test_integration/test_pkinit_install.py | 26 +++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/ipatests/test_integration/test_pkinit_install.py b/ipatests/test_integration/test_pkinit_install.py
index caa0e6a34..5c2e7af02 100644
--- a/ipatests/test_integration/test_pkinit_install.py
+++ b/ipatests/test_integration/test_pkinit_install.py
@@ -23,6 +23,24 @@ class TestPkinitClientInstall(IntegrationTest):
def install(cls, mh):
tasks.install_master(cls.master)
+ def enforce_password_and_otp(self):
+ """enforce otp by default and password for admin """
+ self.master.run_command(
+ [
+ "ipa",
+ "config-mod",
+ "--user-auth-type=otp",
+ ]
+ )
+ self.master.run_command(
+ [
+ "ipa",
+ "user-mod",
+ "admin",
+ "--user-auth-type=password",
+ ]
+ )
+
def add_certmaperule(self):
"""add certmap rule to map SAN dNSName to host entry"""
self.master.run_command(
@@ -86,6 +104,14 @@ class TestPkinitClientInstall(IntegrationTest):
cabundle = self.master.get_file_contents(paths.KDC_CA_BUNDLE_PEM)
client.put_file_contents(self.tmpbundle, cabundle)
+ def test_restart_krb5kdc(self):
+ tasks.kinit_admin(self.master)
+ self.enforce_password_and_otp()
+ self.master.run_command(['systemctl', 'stop', 'krb5kdc.service'])
+ self.master.run_command(['systemctl', 'start', 'krb5kdc.service'])
+ self.master.run_command(['systemctl', 'stop', 'kadmin.service'])
+ self.master.run_command(['systemctl', 'start', 'kadmin.service'])
+
def test_client_install_pkinit(self):
tasks.kinit_admin(self.master)
self.add_certmaperule()
--
2.43.0

View File

@ -0,0 +1,48 @@
From 7f6a2835f0972af5e94b58daf47fa60bfade4279 Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
Date: Thu, 22 Jan 2026 10:02:11 +0100
Subject: [PATCH] fetch_domains: Use case-insensitive comparison for domains
names
The fetch_domains method is using netr_DsRGetForestTrustInformation
to retrieve the forest trust information. The returned data contains
domain entries, with a DNS domain name that can contain mixed case
(for instance adDomain.Test).
The method compares the domain name with the provided parameter in a
case sensitive comparison, while it should use a case-insensitive
method (DNS names are case-insensitive).
Fix the method and compare the lowercase value instead.
Fixes: https://pagure.io/freeipa/issue/9924
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
ipaserver/dcerpc.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index 1182f128b4988bc699fe7a40d4834f1bead82cf5..5c05ffedb889e774e342cb6cb85ff954d06ac5e9 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -1635,7 +1635,7 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None):
t.forest_trust_data.netbios_domain_name.string
tname = unicode(t.forest_trust_data.dns_domain_name.string)
- if tname != trustdomain:
+ if tname.lower() != trustdomain.lower():
result['domains'][tname] = {
'cn': tname,
'ipantflatname': unicode(
@@ -1647,7 +1647,7 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None):
record.data.string = t.forest_trust_data.string
tname = unicode(t.forest_trust_data.string)
- if tname == trustdomain:
+ if tname.lower() == trustdomain.lower():
continue
result['suffixes'][tname] = {'cn': tname}
--
2.52.0

View File

@ -1,139 +0,0 @@
From 48846e98e5e988d600ddf81c937f353fcecdea1a Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Mon, 27 Nov 2023 16:11:08 -0500
Subject: [PATCH 1/2] hbactest was not collecting or returning messages
hbactest does a number of internal searches, one of which
can exceed the configured sizelimit: hbacrule-find
Collect any messages returned from thsi call and display them
to the user on the cli.
Fixes: https://pagure.io/freeipa/issue/9486
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
---
ipaclient/plugins/hbactest.py | 2 ++
ipaserver/plugins/hbactest.py | 14 +++++++++++---
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/ipaclient/plugins/hbactest.py b/ipaclient/plugins/hbactest.py
index 1b54530b2..e0f93b9c2 100644
--- a/ipaclient/plugins/hbactest.py
+++ b/ipaclient/plugins/hbactest.py
@@ -38,6 +38,8 @@ class hbactest(CommandOverride):
# Note that we don't actually use --detail below to see if details need
# to be printed as our execute() method will return None for corresponding
# entries and None entries will be skipped.
+ self.log_messages(output)
+
for o in self.output:
if o == 'value':
continue
diff --git a/ipaserver/plugins/hbactest.py b/ipaserver/plugins/hbactest.py
index 887a35b7e..568c13174 100644
--- a/ipaserver/plugins/hbactest.py
+++ b/ipaserver/plugins/hbactest.py
@@ -24,6 +24,8 @@ from ipalib import Command, Str, Flag, Int
from ipalib import _
from ipapython.dn import DN
from ipalib.plugable import Registry
+from ipalib.messages import VersionMissing
+
if api.env.in_server:
try:
import ipaserver.dcerpc
@@ -323,6 +325,9 @@ class hbactest(Command):
# 2. Required options are (user, target host, service)
# 3. Options: rules to test (--rules, --enabled, --disabled), request for detail output
rules = []
+ result = {
+ 'warning':None, 'matched':None, 'notmatched':None, 'error':None
+ }
# Use all enabled IPA rules by default
all_enabled = True
@@ -351,8 +356,12 @@ class hbactest(Command):
hbacset = []
if len(testrules) == 0:
- hbacset = self.api.Command.hbacrule_find(
- sizelimit=sizelimit, no_members=False)['result']
+ hbacrules = self.api.Command.hbacrule_find(
+ sizelimit=sizelimit, no_members=False)
+ hbacset = hbacrules['result']
+ for message in hbacrules['messages']:
+ if message['code'] != VersionMissing.errno:
+ result.setdefault('messages', []).append(message)
else:
for rule in testrules:
try:
@@ -469,7 +478,6 @@ class hbactest(Command):
error_rules = []
warning_rules = []
- result = {'warning':None, 'matched':None, 'notmatched':None, 'error':None}
if not options['nodetail']:
# Validate runs rules one-by-one and reports failed ones
for ipa_rule in rules:
--
2.43.0
From d1e09c68af8ac77f656dd639af5d9a7f07c41f9d Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Tue, 28 Nov 2023 13:35:13 -0500
Subject: [PATCH 2/2] ipatests: Verify that hbactest will return messages
Limit the sizelimit of the hbactest request to confirm that
the output includes a SearchResultTruncated message.
Fixes: https://pagure.io/freeipa/issue/9486
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
---
ipatests/test_xmlrpc/test_hbactest_plugin.py | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/ipatests/test_xmlrpc/test_hbactest_plugin.py b/ipatests/test_xmlrpc/test_hbactest_plugin.py
index 73c4ce232..e2e66c759 100644
--- a/ipatests/test_xmlrpc/test_hbactest_plugin.py
+++ b/ipatests/test_xmlrpc/test_hbactest_plugin.py
@@ -134,6 +134,7 @@ class test_hbactest(XMLRPC_test):
assert ret['value']
assert ret['error'] is None
assert ret['matched'] is None
+ assert 'messages' not in ret
assert ret['notmatched'] is None
def test_c_hbactest_check_rules_enabled_detail(self):
@@ -200,7 +201,23 @@ class test_hbactest(XMLRPC_test):
nodetail=True
)
- def test_g_hbactest_clear_testing_data(self):
+ def test_g_hbactest_searchlimit_message(self):
+ """
+ Test running 'ipa hbactest' with limited --sizelimit
+
+ We know there are at least 6 rules, 4 created here + 2 default.
+ """
+ ret = api.Command['hbactest'](
+ user=self.test_user,
+ targethost=self.test_host,
+ service=self.test_service,
+ nodetail=True,
+ sizelimit=2,
+ )
+
+ assert ret['messages'] is not None
+
+ def test_h_hbactest_clear_testing_data(self):
"""
Clear data for HBAC test plugin testing.
"""
--
2.43.0

View File

@ -0,0 +1,43 @@
From 8deb4be0962b25dfd43e1245307a8bb9d58cfc48 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Tue, 3 Feb 2026 09:46:25 -0500
Subject: [PATCH] Handle IPACertificate types in xmlrpc
The wrapping code didn't understand the IPACertificate class
so retrieving any entry that contained one would fail.
Treat it the same was as its parent class cryptography.Certificate.
Fixes: https://pagure.io/freeipa/issue/9935
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: David Hanina <dhanina@redhat.com>
---
ipalib/rpc.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index ed35afc965308e03269f05e01400660b207b548d..9773626eb054dd404256267c5fffbba1aa0579dd 100644
--- a/ipalib/rpc.py
+++ b/ipalib/rpc.py
@@ -56,7 +56,7 @@ from ipalib.errors import (errors_by_code, UnknownError, NetworkError,
XMLRPCMarshallError, JSONError)
from ipalib import errors, capabilities
from ipalib.request import context, Connection
-from ipalib.x509 import Encoding as x509_Encoding
+from ipalib.x509 import Encoding as x509_Encoding, IPACertificate
from ipapython import ipautil
from ipapython import session_storage
from ipapython.cookie import Cookie
@@ -220,7 +220,7 @@ def xml_wrap(value, version):
if isinstance(value, Principal):
return unicode(value)
- if isinstance(value, crypto_x509.Certificate):
+ if isinstance(value, (crypto_x509.Certificate, IPACertificate)):
return base64.b64encode(
value.public_bytes(x509_Encoding.DER)).decode('ascii')
--
2.52.0

View File

@ -1,43 +0,0 @@
From 16a739e0260f97705827f972d53c828809dbfdb2 Mon Sep 17 00:00:00 2001
From: Masahiro Matsuya <mmatsuya@redhat.com>
Date: Tue, 9 Jan 2024 23:12:11 +0900
Subject: [PATCH] ipatests: wait for replica update in test_dns_locations
test_ipa_ca_records and test_adtrust_system_records can fail with
NXDOMAIN, because it doesn't wait enough for the update on replica.
It can be resolved by waiting for the update with wait_for_replication.
Fixes: https://pagure.io/freeipa/issue/9504
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
(cherry picked from commit 905a55a4ef926068630ebd2ab375f58c24dedcd1)
---
ipatests/test_integration/test_dns_locations.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/ipatests/test_integration/test_dns_locations.py b/ipatests/test_integration/test_dns_locations.py
index 44900af80..89a310892 100644
--- a/ipatests/test_integration/test_dns_locations.py
+++ b/ipatests/test_integration/test_dns_locations.py
@@ -534,6 +534,9 @@ class TestDNSLocations(IntegrationTest):
expected_servers = (self.master.ip, self.replicas[1].ip)
+ ldap = self.master.ldap_connect()
+ tasks.wait_for_replication(ldap)
+
for ip in (self.master.ip, self.replicas[0].ip, self.replicas[1].ip):
self._test_A_rec_against_server(ip, self.domain, expected_servers)
@@ -557,6 +560,9 @@ class TestDNSLocations(IntegrationTest):
(self.PRIO_HIGH, self.WEIGHT, DNSName(self.master.hostname)),
)
+ ldap = self.master.ldap_connect()
+ tasks.wait_for_replication(ldap)
+
for ip in (self.master.ip, self.replicas[0].ip, self.replicas[1].ip):
self._test_SRV_rec_against_server(
ip, self.domain, expected_servers,
--
2.43.0

View File

@ -0,0 +1,159 @@
From a583b0dc08536a50e10b76e27861864b61906355 Mon Sep 17 00:00:00 2001
From: David Hanina <dhanina@redhat.com>
Date: Mon, 2 Feb 2026 11:14:48 +0100
Subject: [PATCH] Replace None with '' when uninstalling CA
At many places we're obtaining records from a config file and expect the
config to have those keys, but the user may delete the line instead of
setting the value to false, this then leads to failed uninstall. This
patch replaces None with '' at some places that do not check for None.
Fixes: https://pagure.io/freeipa/issue/9921
Signed-off-by: David Hanina <dhanina@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
---
ipaserver/install/ca.py | 12 ++++--
ipaserver/install/cainstance.py | 14 ++++++-
ipaserver/install/server/upgrade.py | 3 +-
.../test_integration/test_crlgen_manage.py | 3 +-
.../test_integration/test_uninstallation.py | 41 +++++++++++++++++++
5 files changed, 66 insertions(+), 7 deletions(-)
diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py
index 5a026aa4c556f7012552052cd08223746f3c39ae..2e953a567a3a230cb2a5e35192af76c61f8c1047 100644
--- a/ipaserver/install/ca.py
+++ b/ipaserver/install/ca.py
@@ -340,14 +340,20 @@ def uninstall_crl_check(options):
try:
crlgen_enabled = ca.is_crlgen_enabled()
- except cainstance.InconsistentCRLGenConfigException:
+ except cainstance.InconsistentCRLGenConfigException as e:
# If config is inconsistent, let's be safe and act as if
# crl gen was enabled
+ print(e)
crlgen_enabled = True
if crlgen_enabled:
- print("Deleting this server will leave your installation "
- "without a CRL generation master.")
+ if not options.ignore_last_of_role:
+ print("Deleting this server will leave your installation "
+ "without a CRL generation master. Use --ignore-last-of-role "
+ "to bypass this check.")
+ else:
+ print("Deleting this server will leave your installation "
+ "without a CRL generation master.")
if (options.unattended and not options.ignore_last_of_role) or \
not (options.unattended or ipautil.user_input(
"Are you sure you want to continue with the uninstall "
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index b8267a625554f9375d27160f39b67ee2e64a2dbb..4933ad23d7323859af92bd02f6ae156803e29997 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -1421,12 +1421,22 @@ class CAInstance(DogtagInstance):
try:
cache = directivesetter.get_directive(
self.config, 'ca.crl.MasterCRL.enableCRLCache', '=')
+
+ if cache is None:
+ raise InconsistentCRLGenConfigException(
+ "Configuration is inconsistent, please check "
+ "ca.crl.MasterCRL.enableCRLCache, "
+ "ca.crl.MasterCRL.enableCRLUpdates and "
+ "ca.listenToCloneModifications in {} and "
+ "run ipa-crlgen-manage [enable|disable] to repair".format(
+ self.config))
+
enableCRLCache = cache.lower() == 'true'
updates = directivesetter.get_directive(
- self.config, 'ca.crl.MasterCRL.enableCRLUpdates', '=')
+ self.config, 'ca.crl.MasterCRL.enableCRLUpdates', '=') or ''
enableCRLUpdates = updates.lower() == 'true'
listen = directivesetter.get_directive(
- self.config, 'ca.listenToCloneModifications', '=')
+ self.config, 'ca.listenToCloneModifications', '=') or ''
enableToClone = listen.lower() == 'true'
updateinterval = directivesetter.get_directive(
self.config, 'ca.certStatusUpdateInterval', '=')
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
index 548ee02e1e8524ce0002dca1764d48728eb0509a..8692c983409426193e1746f07fa1a0514621cb4a 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -1712,7 +1712,8 @@ def upgrade_configuration():
if ca.is_configured():
crl = directivesetter.get_directive(
paths.CA_CS_CFG_PATH, 'ca.crl.MasterCRL.enableCRLUpdates', '=')
- sub_dict['CLONE']='#' if crl.lower() == 'true' else ''
+ sub_dict['CLONE'] = '#' if crl is not None and \
+ crl.lower() == 'true' else ''
ds_dirname = dsinstance.config_dirname(ds.serverid)
diff --git a/ipatests/test_integration/test_crlgen_manage.py b/ipatests/test_integration/test_crlgen_manage.py
index c6f41ebf8939bad8006b1e5eaf37bad30dbfd9d8..8a2a28a75b76158fcc61a8e7612f81343336b64f 100644
--- a/ipatests/test_integration/test_crlgen_manage.py
+++ b/ipatests/test_integration/test_crlgen_manage.py
@@ -302,7 +302,8 @@ class TestCRLGenManage(IntegrationTest):
['ipa-server-install', '--uninstall', '-U'], raiseonerr=False)
assert result.returncode == 1
expected_msg = "Deleting this server will leave your installation " \
- "without a CRL generation master"
+ "without a CRL generation master. Use " \
+ "--ignore-last-of-role to bypass this check."
assert expected_msg in result.stdout_text
def test_uninstall_with_ignore_last_of_role(self):
diff --git a/ipatests/test_integration/test_uninstallation.py b/ipatests/test_integration/test_uninstallation.py
index 8d83f72868f5c103b0c31d2aa96630c00b2dfbd8..12b10caa60745dcbc2d811bff65d57fb5d865f09 100644
--- a/ipatests/test_integration/test_uninstallation.py
+++ b/ipatests/test_integration/test_uninstallation.py
@@ -237,3 +237,44 @@ class TestUninstallReinstall(IntegrationTest):
def test_reinstall_server(self):
tasks.install_master(self.master, setup_dns=False)
+
+
+class TestUninstallCRLGen(IntegrationTest):
+ """Test uninstallation of a replica with broken CRL configuration.
+
+ Removing ca.crl.MasterCRL.enableCRLCache from CS.cfg crashed.
+ https://pagure.io/freeipa/issue/9921
+ """
+
+ num_replicas = 1
+ topology = 'line'
+
+ @classmethod
+ def install(cls, mh):
+ tasks.install_master(cls.master, setup_dns=False)
+ tasks.install_replica(cls.master, cls.replicas[0])
+
+ def test_uninstall_replica_with_broken_crlgen(self):
+ self.replicas[0].run_command(['ipa-crlgen-manage', 'enable'])
+
+ self.replicas[0].run_command([
+ '/bin/sed',
+ '-i',
+ '/ca.crl.MasterCRL.enableCRLCache=true/d',
+ paths.CA_CS_CFG_PATH,
+ ])
+
+ result = self.replicas[0].run_command([
+ 'ipa-server-install',
+ '--ignore-last-of-role',
+ '--uninstall', '-U',
+ ])
+
+ expected_msg = "Configuration is inconsistent, please check " \
+ "ca.crl.MasterCRL.enableCRLCache, " \
+ "ca.crl.MasterCRL.enableCRLUpdates and " \
+ "ca.listenToCloneModifications in {} and run " \
+ "ipa-crlgen-manage [enable|disable] to repair".format(
+ paths.CA_CS_CFG_PATH)
+
+ assert expected_msg in result.stdout_text
--
2.52.0

View File

@ -1,707 +0,0 @@
From c3ac69e9cf8dfcc31ed11fc988c37bd99d3ec3cf Mon Sep 17 00:00:00 2001
From: Julien Rische <jrische@redhat.com>
Date: Wed, 14 Feb 2024 17:47:00 +0100
Subject: [PATCH] ipa-kdb: Rework ipadb_reinit_mspac()
Modify ipadb_reinit_mspac() to allocate and initialize ipactx->mspac
only if all its attributes can be set. If not, ipactx->mspac is set to
NULL. This makes easier to determine if the KDC is able to generate PACs
or not.
Also ipadb_reinit_mspac() is now able to return a status message
explaining why initialization of the PAC generator failed. This message
is printed in KDC logs.
Fixes: https://pagure.io/freeipa/issue/9535
Signed-off-by: Julien Rische <jrische@redhat.com>
Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
(cherry picked from commit 7f072e348d318e928f6270a182ca04dee8716677)
---
daemons/ipa-kdb/ipa_kdb.c | 14 +-
daemons/ipa-kdb/ipa_kdb.h | 4 +-
daemons/ipa-kdb/ipa_kdb_mspac.c | 340 +++++++++++++-----------
daemons/ipa-kdb/ipa_kdb_mspac_private.h | 2 +-
daemons/ipa-kdb/ipa_kdb_mspac_v6.c | 5 +-
daemons/ipa-kdb/ipa_kdb_mspac_v9.c | 16 +-
daemons/ipa-kdb/ipa_kdb_principals.c | 6 +-
7 files changed, 218 insertions(+), 169 deletions(-)
diff --git a/daemons/ipa-kdb/ipa_kdb.c b/daemons/ipa-kdb/ipa_kdb.c
index 0c6325df9..fcadb8ee7 100644
--- a/daemons/ipa-kdb/ipa_kdb.c
+++ b/daemons/ipa-kdb/ipa_kdb.c
@@ -443,6 +443,7 @@ int ipadb_get_connection(struct ipadb_context *ipactx)
struct timeval tv = { 5, 0 };
LDAPMessage *res = NULL;
LDAPMessage *first;
+ const char *stmsg;
int ret;
int v3;
@@ -522,16 +523,9 @@ int ipadb_get_connection(struct ipadb_context *ipactx)
}
/* get adtrust options using default refresh interval */
- ret = ipadb_reinit_mspac(ipactx, false);
- if (ret && ret != ENOENT) {
- /* TODO: log that there is an issue with adtrust settings */
- if (ipactx->lcontext == NULL) {
- /* for some reason ldap connection was reset in ipadb_reinit_mspac
- * and is no longer established => failure of ipadb_get_connection
- */
- goto done;
- }
- }
+ ret = ipadb_reinit_mspac(ipactx, false, &stmsg);
+ if (ret && stmsg)
+ krb5_klog_syslog(LOG_WARNING, "MS-PAC generator: %s", stmsg);
ret = 0;
diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h
index 5de5ea7a5..7baf4697f 100644
--- a/daemons/ipa-kdb/ipa_kdb.h
+++ b/daemons/ipa-kdb/ipa_kdb.h
@@ -352,7 +352,9 @@ krb5_error_code ipadb_v9_issue_pac(krb5_context context, unsigned int flags,
krb5_data ***auth_indicators);
#endif
-krb5_error_code ipadb_reinit_mspac(struct ipadb_context *ipactx, bool force_reinit);
+krb5_error_code ipadb_reinit_mspac(struct ipadb_context *ipactx,
+ bool force_reinit,
+ const char **stmsg);
void ipadb_mspac_struct_free(struct ipadb_mspac **mspac);
krb5_error_code ipadb_check_transited_realms(krb5_context kcontext,
diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
index 886ed7785..deed513b9 100644
--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
+++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
@@ -793,16 +793,16 @@ static krb5_error_code ipadb_fill_info3(struct ipadb_context *ipactx,
return ret;
}
+ if (!ipactx->mspac) {
+ /* can't give a PAC without server NetBIOS name or primary group RID */
+ return ENOENT;
+ }
+
if (info3->base.primary_gid == 0) {
if (is_host || is_service) {
info3->base.primary_gid = 515; /* Well known RID for domain computers group */
} else {
- if (ipactx->mspac->fallback_rid) {
- info3->base.primary_gid = ipactx->mspac->fallback_rid;
- } else {
- /* can't give a pack without a primary group rid */
- return ENOENT;
- }
+ info3->base.primary_gid = ipactx->mspac->fallback_rid;
}
}
@@ -812,26 +812,16 @@ static krb5_error_code ipadb_fill_info3(struct ipadb_context *ipactx,
/* always zero out, not used for Krb, only NTLM */
memset(&info3->base.key, '\0', sizeof(info3->base.key));
- if (ipactx->mspac->flat_server_name) {
- info3->base.logon_server.string =
- talloc_strdup(memctx, ipactx->mspac->flat_server_name);
- if (!info3->base.logon_server.string) {
- return ENOMEM;
- }
- } else {
- /* can't give a pack without Server NetBIOS Name :-| */
- return ENOENT;
+ info3->base.logon_server.string =
+ talloc_strdup(memctx, ipactx->mspac->flat_server_name);
+ if (!info3->base.logon_server.string) {
+ return ENOMEM;
}
- if (ipactx->mspac->flat_domain_name) {
- info3->base.logon_domain.string =
- talloc_strdup(memctx, ipactx->mspac->flat_domain_name);
- if (!info3->base.logon_domain.string) {
- return ENOMEM;
- }
- } else {
- /* can't give a pack without Domain NetBIOS Name :-| */
- return ENOENT;
+ info3->base.logon_domain.string =
+ talloc_strdup(memctx, ipactx->mspac->flat_domain_name);
+ if (!info3->base.logon_domain.string) {
+ return ENOMEM;
}
if (is_host || is_service) {
@@ -1044,6 +1034,11 @@ krb5_error_code ipadb_get_pac(krb5_context kcontext,
return KRB5_KDB_DBNOTINITED;
}
+ /* Check if PAC generator is initialized */
+ if (!ipactx->mspac) {
+ return ENOENT;
+ }
+
ied = (struct ipadb_e_data *)client->e_data;
if (ied->magic != IPA_E_DATA_MAGIC) {
return EINVAL;
@@ -1626,14 +1621,14 @@ static struct ipadb_adtrusts *get_domain_from_realm(krb5_context context,
{
struct ipadb_context *ipactx;
struct ipadb_adtrusts *domain;
- int i;
+ size_t i;
ipactx = ipadb_get_context(context);
if (!ipactx) {
return NULL;
}
- if (ipactx->mspac == NULL) {
+ if (!ipactx->mspac) {
return NULL;
}
@@ -1655,6 +1650,7 @@ static struct ipadb_adtrusts *get_domain_from_realm_update(krb5_context context,
{
struct ipadb_context *ipactx;
struct ipadb_adtrusts *domain;
+ const char *stmsg = NULL;
krb5_error_code kerr;
ipactx = ipadb_get_context(context);
@@ -1663,8 +1659,10 @@ static struct ipadb_adtrusts *get_domain_from_realm_update(krb5_context context,
}
/* re-init MS-PAC info using default update interval */
- kerr = ipadb_reinit_mspac(ipactx, false);
+ kerr = ipadb_reinit_mspac(ipactx, false, &stmsg);
if (kerr != 0) {
+ if (stmsg)
+ krb5_klog_syslog(LOG_WARNING, "MS-PAC generator: %s", stmsg);
return NULL;
}
domain = get_domain_from_realm(context, realm);
@@ -1717,6 +1715,7 @@ static krb5_error_code check_logon_info_consistent(krb5_context context,
struct ipadb_e_data *ied = NULL;
int flags = 0;
struct dom_sid client_sid;
+ const char *stmsg = NULL;
#ifdef KRB5_KDB_FLAG_ALIAS_OK
flags = KRB5_KDB_FLAG_ALIAS_OK;
#endif
@@ -1730,10 +1729,14 @@ static krb5_error_code check_logon_info_consistent(krb5_context context,
* check that our own view on the PAC details is up to date */
if (ipactx->mspac->domsid.num_auths == 0) {
/* Force re-init of KDB's view on our domain */
- kerr = ipadb_reinit_mspac(ipactx, true);
+ kerr = ipadb_reinit_mspac(ipactx, true, &stmsg);
if (kerr != 0) {
- krb5_klog_syslog(LOG_ERR,
- "PAC issue: unable to update realm's view on PAC info");
+ if (stmsg) {
+ krb5_klog_syslog(LOG_ERR, "MS-PAC generator: %s", stmsg);
+ } else {
+ krb5_klog_syslog(LOG_ERR, "PAC issue: unable to update " \
+ "realm's view on PAC info");
+ }
return KRB5KDC_ERR_POLICY;
}
}
@@ -1746,7 +1749,7 @@ static krb5_error_code check_logon_info_consistent(krb5_context context,
if (is_s4u && (ipactx->mspac->trusts != NULL)) {
/* Iterate through list of trusts and check if this SID belongs to
* one of the domains we trust */
- for(int i = 0 ; i < ipactx->mspac->num_trusts ; i++) {
+ for(size_t i = 0 ; i < ipactx->mspac->num_trusts ; i++) {
result = dom_sid_check(&ipactx->mspac->trusts[i].domsid,
info->info->info3.base.domain_sid, true);
if (result) {
@@ -1858,11 +1861,11 @@ krb5_error_code filter_logon_info(krb5_context context,
struct ipadb_mspac *mspac_ctx = ipactx->mspac;
result = FALSE;
/* Didn't match but perhaps the original PAC was issued by a child domain's DC? */
- for (k = 0; k < mspac_ctx->num_trusts; k++) {
- result = dom_sid_check(&mspac_ctx->trusts[k].domsid,
+ for (size_t m = 0; m < mspac_ctx->num_trusts; m++) {
+ result = dom_sid_check(&mspac_ctx->trusts[m].domsid,
info->info->info3.base.domain_sid, true);
if (result) {
- domain = &mspac_ctx->trusts[k];
+ domain = &mspac_ctx->trusts[m];
break;
}
}
@@ -2091,10 +2094,10 @@ static krb5_error_code ipadb_check_logon_info(krb5_context context,
return KRB5_KDB_DBNOTINITED;
}
/* In S4U case we might be dealing with the PAC issued by the trusted domain */
- if ((ipactx->mspac->trusts != NULL)) {
+ if (ipactx->mspac->trusts) {
/* Iterate through list of trusts and check if this SID belongs to
* one of the domains we trust */
- for(int i = 0 ; i < ipactx->mspac->num_trusts ; i++) {
+ for(size_t i = 0 ; i < ipactx->mspac->num_trusts ; i++) {
result = dom_sid_check(&ipactx->mspac->trusts[i].domsid,
&client_sid, false);
if (result) {
@@ -2631,7 +2634,7 @@ static char *get_server_netbios_name(struct ipadb_context *ipactx)
void ipadb_mspac_struct_free(struct ipadb_mspac **mspac)
{
- int i, j;
+ size_t i, j;
if (!*mspac) return;
@@ -2786,7 +2789,8 @@ ipadb_mspac_get_trusted_domains(struct ipadb_context *ipactx)
LDAPDN dn = NULL;
char **sid_blocklist_incoming = NULL;
char **sid_blocklist_outgoing = NULL;
- int ret, n, i;
+ size_t i, n;
+ int ret;
ret = asprintf(&base, "cn=ad,cn=trusts,%s", ipactx->base);
if (ret == -1) {
@@ -2871,7 +2875,7 @@ ipadb_mspac_get_trusted_domains(struct ipadb_context *ipactx)
t[n].upn_suffixes_len = NULL;
if (t[n].upn_suffixes != NULL) {
- int len = 0;
+ size_t len = 0;
for (; t[n].upn_suffixes[len] != NULL; len++);
@@ -2986,108 +2990,114 @@ done:
return ret;
}
-krb5_error_code ipadb_reinit_mspac(struct ipadb_context *ipactx, bool force_reinit)
+krb5_error_code
+ipadb_reinit_mspac(struct ipadb_context *ipactx, bool force_reinit,
+ const char **stmsg)
{
char *dom_attrs[] = { "ipaNTFlatName",
"ipaNTFallbackPrimaryGroup",
"ipaNTSecurityIdentifier",
NULL };
char *grp_attrs[] = { "ipaNTSecurityIdentifier", NULL };
- krb5_error_code kerr;
LDAPMessage *result = NULL;
LDAPMessage *lentry;
- struct dom_sid gsid;
- char *resstr;
- int ret;
+ struct dom_sid gsid, domsid;
+ char *resstr = NULL;
+ char *flat_domain_name = NULL;
+ char *flat_server_name = NULL;
+ char *fallback_group = NULL;
+ uint32_t fallback_rid;
time_t now;
+ const char *in_stmsg = NULL;
+ int err;
+ krb5_error_code trust_kerr = 0;
+
/* Do not update the mspac struct more than once a minute. This would
* avoid heavy load on the directory server if there are lots of requests
* from domains which we do not trust. */
now = time(NULL);
- if (ipactx->mspac != NULL &&
- (force_reinit == false) &&
- (now > ipactx->mspac->last_update) &&
- (now - ipactx->mspac->last_update) < 60) {
- return 0;
- }
-
- if (ipactx->mspac && ipactx->mspac->num_trusts == 0) {
- /* Check if there is any trust configured. If not, just return
- * and do not re-initialize the MS-PAC structure. */
- kerr = ipadb_mspac_check_trusted_domains(ipactx);
- if (kerr == KRB5_KDB_NOENTRY) {
- kerr = 0;
- goto done;
- } else if (kerr != 0) {
- goto done;
+ if (ipactx->mspac) {
+ if (!force_reinit &&
+ (now > ipactx->mspac->last_update) &&
+ (now - ipactx->mspac->last_update) < 60) {
+ /* SKIP */
+ err = 0;
+ goto end;
}
- }
-
- /* clean up in case we had old values around */
- ipadb_mspac_struct_free(&ipactx->mspac);
- ipactx->mspac = calloc(1, sizeof(struct ipadb_mspac));
- if (!ipactx->mspac) {
- kerr = ENOMEM;
- goto done;
+ if (ipactx->mspac->num_trusts == 0) {
+ /* Check if there is any trust configured. If not, just return
+ * and do not re-initialize the MS-PAC structure. */
+ err = ipadb_mspac_check_trusted_domains(ipactx);
+ if (err) {
+ if (err == KRB5_KDB_NOENTRY) {
+ /* SKIP */
+ err = 0;
+ } else {
+ in_stmsg = "Failed to fetch trusted domains information";
+ }
+ goto end;
+ }
+ }
}
- ipactx->mspac->last_update = now;
-
- kerr = ipadb_simple_search(ipactx, ipactx->base, LDAP_SCOPE_SUBTREE,
- "(objectclass=ipaNTDomainAttrs)", dom_attrs,
- &result);
- if (kerr == KRB5_KDB_NOENTRY) {
- return ENOENT;
- } else if (kerr != 0) {
- return EIO;
+ err = ipadb_simple_search(ipactx, ipactx->base, LDAP_SCOPE_SUBTREE,
+ "(objectclass=ipaNTDomainAttrs)", dom_attrs,
+ &result);
+ if (err == KRB5_KDB_NOENTRY) {
+ err = ENOENT;
+ in_stmsg = "Local domain NT attributes not configured";
+ goto end;
+ } else if (err) {
+ err = EIO;
+ in_stmsg = "Failed to fetch local domain NT attributes";
+ goto end;
}
lentry = ldap_first_entry(ipactx->lcontext, result);
if (!lentry) {
- kerr = ENOENT;
- goto done;
+ err = ENOENT;
+ in_stmsg = "Local domain NT attributes not configured";
+ goto end;
}
- ret = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry,
- "ipaNTFlatName",
- &ipactx->mspac->flat_domain_name);
- if (ret) {
- kerr = ret;
- goto done;
+ err = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry, "ipaNTFlatName",
+ &flat_domain_name);
+ if (err) {
+ in_stmsg = "Local domain NT flat name not configured";
+ goto end;
}
- ret = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry,
- "ipaNTSecurityIdentifier",
- &resstr);
- if (ret) {
- kerr = ret;
- goto done;
+ err = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry,
+ "ipaNTSecurityIdentifier", &resstr);
+ if (err) {
+ in_stmsg = "Local domain SID not configured";
+ goto end;
}
- ret = ipadb_string_to_sid(resstr, &ipactx->mspac->domsid);
- if (ret) {
- kerr = ret;
- free(resstr);
- goto done;
+ err = ipadb_string_to_sid(resstr, &domsid);
+ if (err) {
+ in_stmsg = "Malformed local domain SID";
+ goto end;
}
+
free(resstr);
- free(ipactx->mspac->flat_server_name);
- ipactx->mspac->flat_server_name = get_server_netbios_name(ipactx);
- if (!ipactx->mspac->flat_server_name) {
- kerr = ENOMEM;
- goto done;
+ flat_server_name = get_server_netbios_name(ipactx);
+ if (!flat_server_name) {
+ err = ENOMEM;
+ goto end;
}
- ret = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry,
- "ipaNTFallbackPrimaryGroup",
- &ipactx->mspac->fallback_group);
- if (ret && ret != ENOENT) {
- kerr = ret;
- goto done;
+ err = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry,
+ "ipaNTFallbackPrimaryGroup", &fallback_group);
+ if (err) {
+ in_stmsg = (err == ENOENT)
+ ? "Local fallback primary group not configured"
+ : "Failed to fetch local fallback primary group";
+ goto end;
}
/* result and lentry not valid any more from here on */
@@ -3095,53 +3105,81 @@ krb5_error_code ipadb_reinit_mspac(struct ipadb_context *ipactx, bool force_rein
result = NULL;
lentry = NULL;
- if (ret != ENOENT) {
- kerr = ipadb_simple_search(ipactx, ipactx->mspac->fallback_group,
- LDAP_SCOPE_BASE,
- "(objectclass=posixGroup)",
- grp_attrs, &result);
- if (kerr && kerr != KRB5_KDB_NOENTRY) {
- kerr = ret;
- goto done;
- }
+ err = ipadb_simple_search(ipactx, fallback_group, LDAP_SCOPE_BASE,
+ "(objectclass=posixGroup)", grp_attrs, &result);
+ if (err) {
+ in_stmsg = (err == KRB5_KDB_NOENTRY)
+ ? "Local fallback primary group has no POSIX definition"
+ : "Failed to fetch SID of POSIX group mapped as local fallback " \
+ "primary group";
+ goto end;
+ }
- lentry = ldap_first_entry(ipactx->lcontext, result);
- if (!lentry) {
- kerr = ENOENT;
- goto done;
- }
+ lentry = ldap_first_entry(ipactx->lcontext, result);
+ if (!lentry) {
+ err = ENOENT;
+ goto end;
+ }
- if (kerr == 0) {
- ret = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry,
- "ipaNTSecurityIdentifier",
- &resstr);
- if (ret && ret != ENOENT) {
- kerr = ret;
- goto done;
- }
- if (ret == 0) {
- ret = ipadb_string_to_sid(resstr, &gsid);
- if (ret) {
- free(resstr);
- kerr = ret;
- goto done;
- }
- ret = sid_split_rid(&gsid, &ipactx->mspac->fallback_rid);
- if (ret) {
- free(resstr);
- kerr = ret;
- goto done;
- }
- free(resstr);
- }
- }
+ err = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry,
+ "ipaNTSecurityIdentifier", &resstr);
+ if (err) {
+ in_stmsg = (err == ENOENT)
+ ? "The POSIX group set as fallback primary group has no SID " \
+ "configured"
+ : "Failed to fetch SID of POSIX group set as local fallback " \
+ "primary group";
+ goto end;
}
- kerr = ipadb_mspac_get_trusted_domains(ipactx);
+ err = ipadb_string_to_sid(resstr, &gsid);
+ if (err) {
+ in_stmsg = "Malformed SID of POSIX group set as local fallback " \
+ "primary group";
+ goto end;
+ }
-done:
+ err = sid_split_rid(&gsid, &fallback_rid);
+ if (err) {
+ in_stmsg = "Malformed SID of POSIX group mapped as local fallback " \
+ "primary group";
+ goto end;
+ }
+
+ /* clean up in case we had old values around */
+ ipadb_mspac_struct_free(&ipactx->mspac);
+
+ ipactx->mspac = calloc(1, sizeof(struct ipadb_mspac));
+ if (!ipactx->mspac) {
+ err = ENOMEM;
+ goto end;
+ }
+
+ ipactx->mspac->last_update = now;
+ ipactx->mspac->flat_domain_name = flat_domain_name;
+ ipactx->mspac->flat_server_name = flat_server_name;
+ ipactx->mspac->domsid = domsid;
+ ipactx->mspac->fallback_group = fallback_group;
+ ipactx->mspac->fallback_rid = fallback_rid;
+
+ trust_kerr = ipadb_mspac_get_trusted_domains(ipactx);
+ if (trust_kerr)
+ in_stmsg = "Failed to assemble trusted domains information";
+
+end:
+ if (stmsg)
+ *stmsg = in_stmsg;
+
+ if (resstr) free(resstr);
ldap_msgfree(result);
- return kerr;
+
+ if (err) {
+ if (flat_domain_name) free(flat_domain_name);
+ if (flat_server_name) free(flat_server_name);
+ if (fallback_group) free(fallback_group);
+ }
+
+ return err ? (krb5_error_code)err : trust_kerr;
}
krb5_error_code ipadb_check_transited_realms(krb5_context kcontext,
@@ -3151,11 +3189,11 @@ krb5_error_code ipadb_check_transited_realms(krb5_context kcontext,
{
struct ipadb_context *ipactx;
bool has_transited_contents, has_client_realm, has_server_realm;
- int i;
+ size_t i;
krb5_error_code ret;
ipactx = ipadb_get_context(kcontext);
- if (!ipactx || !ipactx->mspac) {
+ if (!ipactx) {
return KRB5_KDB_DBNOTINITED;
}
@@ -3217,7 +3255,7 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
char **trusted_realm)
{
struct ipadb_context *ipactx;
- int i, j, length;
+ size_t i, j, length;
const char *name;
bool result = false;
diff --git a/daemons/ipa-kdb/ipa_kdb_mspac_private.h b/daemons/ipa-kdb/ipa_kdb_mspac_private.h
index 7f0ca7a79..e650cfa73 100644
--- a/daemons/ipa-kdb/ipa_kdb_mspac_private.h
+++ b/daemons/ipa-kdb/ipa_kdb_mspac_private.h
@@ -31,7 +31,7 @@ struct ipadb_mspac {
char *fallback_group;
uint32_t fallback_rid;
- int num_trusts;
+ size_t num_trusts;
struct ipadb_adtrusts *trusts;
time_t last_update;
};
diff --git a/daemons/ipa-kdb/ipa_kdb_mspac_v6.c b/daemons/ipa-kdb/ipa_kdb_mspac_v6.c
index faf47ad1b..96cd50e4c 100644
--- a/daemons/ipa-kdb/ipa_kdb_mspac_v6.c
+++ b/daemons/ipa-kdb/ipa_kdb_mspac_v6.c
@@ -233,6 +233,7 @@ krb5_error_code ipadb_sign_authdata(krb5_context context,
krb5_db_entry *client_entry = NULL;
krb5_boolean is_equal;
bool force_reinit_mspac = false;
+ const char *stmsg = NULL;
is_as_req = ((flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) != 0);
@@ -309,7 +310,9 @@ krb5_error_code ipadb_sign_authdata(krb5_context context,
force_reinit_mspac = true;
}
- (void)ipadb_reinit_mspac(ipactx, force_reinit_mspac);
+ kerr = ipadb_reinit_mspac(ipactx, force_reinit_mspac, &stmsg);
+ if (kerr && stmsg)
+ krb5_klog_syslog(LOG_WARNING, "MS-PAC generator: %s", stmsg);
kerr = ipadb_get_pac(context, flags, client, server, NULL, authtime, &pac);
if (kerr != 0 && kerr != ENOENT) {
diff --git a/daemons/ipa-kdb/ipa_kdb_mspac_v9.c b/daemons/ipa-kdb/ipa_kdb_mspac_v9.c
index 3badd5b08..60db048e1 100644
--- a/daemons/ipa-kdb/ipa_kdb_mspac_v9.c
+++ b/daemons/ipa-kdb/ipa_kdb_mspac_v9.c
@@ -46,6 +46,7 @@ ipadb_v9_issue_pac(krb5_context context, unsigned int flags,
bool with_pad;
krb5_error_code kerr = 0;
bool is_as_req = flags & CLIENT_REFERRALS_FLAGS;
+ const char *stmsg = NULL;
if (is_as_req) {
get_authz_data_types(context, client, &with_pac, &with_pad);
@@ -110,12 +111,19 @@ ipadb_v9_issue_pac(krb5_context context, unsigned int flags,
force_reinit_mspac = TRUE;
}
}
- (void)ipadb_reinit_mspac(ipactx, force_reinit_mspac);
- /* MS-PAC needs proper configuration and if it is missing, we simply skip issuing one */
- if (ipactx->mspac->flat_server_name == NULL) {
+ /* MS-PAC generator has to be initalized */
+ kerr = ipadb_reinit_mspac(ipactx, force_reinit_mspac, &stmsg);
+ if (kerr && stmsg)
+ krb5_klog_syslog(LOG_ERR, "MS-PAC generator: %s", stmsg);
+
+ /* Continue even if initilization of PAC generator failed.
+ * It may caused by the trust objects part only. */
+
+ /* At least the core part of the PAC generator is required. */
+ if (!ipactx->mspac)
return KRB5_PLUGIN_OP_NOTSUPP;
- }
+
kerr = ipadb_get_pac(context, flags,
client, server, replaced_reply_key,
authtime, &new_pac);
diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c
index fadb132ed..07cc87746 100644
--- a/daemons/ipa-kdb/ipa_kdb_principals.c
+++ b/daemons/ipa-kdb/ipa_kdb_principals.c
@@ -1495,6 +1495,7 @@ static krb5_error_code dbget_alias(krb5_context kcontext,
krb5_db_entry *kentry = NULL;
krb5_data *realm;
krb5_boolean check = FALSE;
+ const char *stmsg = NULL;
/* TODO: also support hostbased aliases */
@@ -1562,8 +1563,11 @@ static krb5_error_code dbget_alias(krb5_context kcontext,
if (kerr == KRB5_KDB_NOENTRY) {
/* If no trusted realm found, refresh trusted domain data and try again
* because it might be a freshly added trust to AD */
- kerr = ipadb_reinit_mspac(ipactx, false);
+ kerr = ipadb_reinit_mspac(ipactx, false, &stmsg);
if (kerr != 0) {
+ if (stmsg)
+ krb5_klog_syslog(LOG_WARNING, "MS-PAC generator: %s",
+ stmsg);
kerr = KRB5_KDB_NOENTRY;
goto done;
}
--
2.43.0

View File

@ -0,0 +1,416 @@
From 91a6618e51b0e767c5cc5e4b1719531dbbd7268d Mon Sep 17 00:00:00 2001
From: Sudhir Menon <sumenon@redhat.com>
Date: Thu, 22 Jan 2026 12:43:56 +0530
Subject: [PATCH] ipatests: Add xmlrpc tests for ipa-delegation-cli
This patch adds below test cases to the the XML-RPC delegation plugin test suite
coverage of delegation operations and important bug regressions.
Test cases added:
Test basic delegation creation with write permission
Test delegation creation with --all flag
Test delegation creation with --raw flag (ACI format)
Test deletion of delegation with ipausers group
Test finding delegation by name criteria
Test finding delegation by membergroup filter
Test showing delegation by name
Test modifying delegation attrs
Test modifying delegation permissions
BZ 783548: Verify mod fails when membergroup doesn't exist
BZ 783554: Verify mod with empty attrs fails properly
BZ 888524: Verify find --group option works correctly
Fixes: https://pagure.io/freeipa/issue/9931
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Sudhir Menon <sumenon@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Anuja More <amore@redhat.com>
---
.../test_xmlrpc/test_delegation_plugin.py | 371 ++++++++++++++++++
1 file changed, 371 insertions(+)
diff --git a/ipatests/test_xmlrpc/test_delegation_plugin.py b/ipatests/test_xmlrpc/test_delegation_plugin.py
index b3d2aadbddbaaff6f40e1046e4df32bcc9ee7e2d..9245f259e21cad166c3c5b0565da3bb56a341e6b 100644
--- a/ipatests/test_xmlrpc/test_delegation_plugin.py
+++ b/ipatests/test_xmlrpc/test_delegation_plugin.py
@@ -333,4 +333,375 @@ class test_delegation(Declarative):
summary=u'Deleted delegation "%s"' % delegation1,
)
),
+
+
+ dict(
+ desc='Create delegation with mobile attr and write permission',
+ command=(
+ 'delegation_add', [u'test_mobile_delegation'], dict(
+ attrs=[u'mobile'],
+ permissions=u'write',
+ group=u'editors',
+ memberof=u'admins',
+ )
+ ),
+ expected=dict(
+ value=u'test_mobile_delegation',
+ summary=u'Added delegation "test_mobile_delegation"',
+ result=dict(
+ attrs=[u'mobile'],
+ permissions=[u'write'],
+ aciname=u'test_mobile_delegation',
+ group=u'editors',
+ memberof=member1,
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Create delegation with --all flag',
+ command=(
+ 'delegation_add', [u'test_all_flag'], dict(
+ attrs=[u'mobile'],
+ permissions=u'write',
+ group=u'editors',
+ memberof=u'admins',
+ all=True,
+ )
+ ),
+ expected=dict(
+ value=u'test_all_flag',
+ summary=u'Added delegation "test_all_flag"',
+ result=dict(
+ attrs=[u'mobile'],
+ permissions=[u'write'],
+ aciname=u'test_all_flag',
+ group=u'editors',
+ memberof=member1,
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Create delegation with --raw flag',
+ command=(
+ 'delegation_add', [u'test_raw_flag'], dict(
+ attrs=[u'mobile'],
+ permissions=u'write',
+ group=u'editors',
+ memberof=u'admins',
+ raw=True,
+ )
+ ),
+ expected=dict(
+ value=u'test_raw_flag',
+ summary=u'Added delegation "test_raw_flag"',
+ result={
+ 'aci': u'(targetattr = "mobile")(targetfilter = '
+ u'"(memberOf=%s)")(version 3.0;acl '
+ u'"delegation:test_raw_flag";allow (write) '
+ u'groupdn = "ldap:///%s";)' % (
+ DN(('cn', 'admins'), ('cn', 'groups'),
+ ('cn', 'accounts'), api.env.basedn),
+ DN(('cn', 'editors'), ('cn', 'groups'),
+ ('cn', 'accounts'), api.env.basedn))
+ },
+ ),
+ ),
+
+
+ dict(
+ desc='Delete test_mobile_delegation',
+ command=('delegation_del', [u'test_mobile_delegation'], {}),
+ expected=dict(
+ result=True,
+ value=u'test_mobile_delegation',
+ summary=u'Deleted delegation "test_mobile_delegation"',
+ )
+ ),
+
+
+ dict(
+ desc='Delete test_all_flag',
+ command=('delegation_del', [u'test_all_flag'], {}),
+ expected=dict(
+ result=True,
+ value=u'test_all_flag',
+ summary=u'Deleted delegation "test_all_flag"',
+ )
+ ),
+
+
+ dict(
+ desc='Delete test_raw_flag',
+ command=('delegation_del', [u'test_raw_flag'], {}),
+ expected=dict(
+ result=True,
+ value=u'test_raw_flag',
+ summary=u'Deleted delegation "test_raw_flag"',
+ )
+ ),
+
+
+ dict(
+ desc='Create delegation for ipausers group',
+ command=(
+ 'delegation_add', [u'delegation_del_positive_1001'], dict(
+ attrs=[u'mobile'],
+ group=u'ipausers',
+ memberof=u'admins',
+ )
+ ),
+ expected=dict(
+ value=u'delegation_del_positive_1001',
+ summary=u'Added delegation "delegation_del_positive_1001"',
+ result=dict(
+ attrs=[u'mobile'],
+ permissions=[u'write'],
+ aciname=u'delegation_del_positive_1001',
+ group=u'ipausers',
+ memberof=member1,
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Delete delegation_del_positive_1001',
+ command=('delegation_del', [u'delegation_del_positive_1001'], {}),
+ expected=dict(
+ result=True,
+ value=u'delegation_del_positive_1001',
+ summary=u'Deleted delegation "delegation_del_positive_1001"',
+ )
+ ),
+
+
+ dict(
+ desc='Create delegation for find, show, and mod tests',
+ command=(
+ 'delegation_add', [u'delegation_find_show_mod_test'], dict(
+ attrs=[u'mobile'],
+ permissions=u'write',
+ group=u'editors',
+ memberof=u'admins',
+ )
+ ),
+ expected=dict(
+ value=u'delegation_find_show_mod_test',
+ summary=u'Added delegation "delegation_find_show_mod_test"',
+ result=dict(
+ attrs=[u'mobile'],
+ permissions=[u'write'],
+ aciname=u'delegation_find_show_mod_test',
+ group=u'editors',
+ memberof=member1,
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Find delegation by name',
+ command=('delegation_find', [u'delegation_find_show_mod_test'], {}),
+ expected=dict(
+ count=1,
+ truncated=False,
+ summary=u'1 delegation matched',
+ result=[
+ {
+ 'attrs': [u'mobile'],
+ 'permissions': [u'write'],
+ 'aciname': u'delegation_find_show_mod_test',
+ 'group': u'editors',
+ 'memberof': member1,
+ },
+ ],
+ ),
+ ),
+
+
+ dict(
+ desc='Find delegation by membergroup',
+ command=('delegation_find', [], {'memberof': member1}),
+ expected=dict(
+ count=1,
+ truncated=False,
+ summary=u'1 delegation matched',
+ result=[
+ {
+ 'attrs': [u'mobile'],
+ 'permissions': [u'write'],
+ 'aciname': u'delegation_find_show_mod_test',
+ 'group': u'editors',
+ 'memberof': member1,
+ },
+ ],
+ ),
+ ),
+
+
+ dict(
+ desc='Show delegation by name',
+ command=('delegation_show', [u'delegation_find_show_mod_test'], {}),
+ expected=dict(
+ value=u'delegation_find_show_mod_test',
+ summary=None,
+ result={
+ 'attrs': [u'mobile'],
+ 'permissions': [u'write'],
+ 'aciname': u'delegation_find_show_mod_test',
+ 'group': u'editors',
+ 'memberof': member1,
+ },
+ ),
+ ),
+
+
+ dict(
+ desc='Modify delegation attrs',
+ command=(
+ 'delegation_mod', [u'delegation_find_show_mod_test'],
+ dict(attrs=[u'l'])
+ ),
+ expected=dict(
+ value=u'delegation_find_show_mod_test',
+ summary=u'Modified delegation "delegation_find_show_mod_test"',
+ result=dict(
+ attrs=[u'l'],
+ permissions=[u'write'],
+ aciname=u'delegation_find_show_mod_test',
+ group=u'editors',
+ memberof=member1,
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Modify delegation permissions',
+ command=(
+ 'delegation_mod', [u'delegation_find_show_mod_test'],
+ dict(permissions=u'read')
+ ),
+ expected=dict(
+ value=u'delegation_find_show_mod_test',
+ summary=u'Modified delegation "delegation_find_show_mod_test"',
+ result=dict(
+ attrs=[u'l'],
+ permissions=[u'read'],
+ aciname=u'delegation_find_show_mod_test',
+ group=u'editors',
+ memberof=member1,
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Delete delegation_find_show_mod_test',
+ command=('delegation_del', [u'delegation_find_show_mod_test'], {}),
+ expected=dict(
+ result=True,
+ value=u'delegation_find_show_mod_test',
+ summary=u'Deleted delegation "delegation_find_show_mod_test"',
+ )
+ ),
+
+
+ dict(
+ desc='Create delegation for BZ tests',
+ command=(
+ 'delegation_add', [u'delegation_bz_test'], dict(
+ attrs=[u'mobile'],
+ permissions=u'write',
+ group=u'ipausers',
+ memberof=u'admins',
+ )
+ ),
+ expected=dict(
+ value=u'delegation_bz_test',
+ summary=u'Added delegation "delegation_bz_test"',
+ result=dict(
+ attrs=[u'mobile'],
+ permissions=[u'write'],
+ aciname=u'delegation_bz_test',
+ group=u'ipausers',
+ memberof=member1,
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Try to modify with non-existent membergroup (BZ 783548)',
+ command=(
+ 'delegation_mod', [u'delegation_bz_test'],
+ dict(memberof=u'badmembergroup')
+ ),
+ expected=errors.NotFound(
+ reason=u'badmembergroup: group not found'),
+ ),
+
+
+ dict(
+ desc='Try to modify attrs with empty value (BZ 783554)',
+ command=(
+ 'delegation_mod', [u'delegation_bz_test'], dict(attrs=u'')
+ ),
+ expected=errors.RequirementError(name='attrs'),
+ ),
+
+
+ dict(
+ desc='Modify attrs to prepare for next BZ test',
+ command=(
+ 'delegation_mod', [u'delegation_bz_test'], dict(attrs=[u'l'])
+ ),
+ expected=dict(
+ value=u'delegation_bz_test',
+ summary=u'Modified delegation "delegation_bz_test"',
+ result=dict(
+ attrs=[u'l'],
+ permissions=[u'write'],
+ aciname=u'delegation_bz_test',
+ group=u'ipausers',
+ memberof=member1,
+ ),
+ ),
+ ),
+
+
+ dict(
+ desc='Find delegation by group filter (BZ 888524)',
+ command=('delegation_find', [], {'group': u'ipausers'}),
+ expected=dict(
+ count=1,
+ truncated=False,
+ summary=u'1 delegation matched',
+ result=[
+ {
+ 'attrs': [u'l'],
+ 'permissions': [u'write'],
+ 'aciname': u'delegation_bz_test',
+ 'group': u'ipausers',
+ 'memberof': member1,
+ },
+ ],
+ ),
+ ),
+
+
+ dict(
+ desc='Delete delegation_bz_test',
+ command=('delegation_del', [u'delegation_bz_test'], {}),
+ expected=dict(
+ result=True,
+ value=u'delegation_bz_test',
+ summary=u'Deleted delegation "delegation_bz_test"',
+ )
+ ),
+
]
--
2.52.0

View File

@ -1,34 +0,0 @@
From 44a762413c83f9637399afeb61b1e4b1ac111260 Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
Date: Feb 14 2024 12:24:48 +0000
Subject: ipatests: fix tasks.wait_for_replication method
With the fix for https://pagure.io/freeipa/issue/9171, the
method entry.single_value['nsds5replicaupdateinprogress'] now
returns a Boolean instead of a string "TRUE"/"FALSE".
The method tasks.wait_for_replication needs to be fixed so that
it properly detects when replication is not done.
Fixes: https://pagure.io/freeipa/issue/9530
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
---
diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py
index 9068ba6..952c9e6 100755
--- a/ipatests/pytest_ipa/integration/tasks.py
+++ b/ipatests/pytest_ipa/integration/tasks.py
@@ -1510,7 +1510,7 @@ def wait_for_replication(ldap, timeout=30,
statuses = [entry.single_value[status_attr] for entry in entries]
wrong_statuses = [s for s in statuses
if not re.match(target_status_re, s)]
- if any(e.single_value[progress_attr] == 'TRUE' for e in entries):
+ if any(e.single_value[progress_attr] for e in entries):
msg = 'Replication not finished'
logger.debug(msg)
elif wrong_statuses:

View File

@ -1,127 +0,0 @@
From 163f06cab678d517ab30ab6da59ae339f39ee7cf Mon Sep 17 00:00:00 2001
From: Francisco Trivino <ftrivino@redhat.com>
Date: Fri, 27 May 2022 17:31:40 +0200
Subject: [PATCH] Vault: add support for RSA-OAEP wrapping algo
None of the FIPS certified modules in RHEL support PKCS#1 v1.5 as FIPS
approved mechanism. This commit adds support for RSA-OAEP padding as a
fallback.
Fixes: https://pagure.io/freeipa/issue/9191
Signed-off-by: Francisco Trivino <ftrivino@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
(cherry picked from commit b1fb31fd20c900c9ff1d5d28dfe136439f6bf605)
---
ipaclient/plugins/vault.py | 57 ++++++++++++++++++++++++++++++--------
1 file changed, 45 insertions(+), 12 deletions(-)
diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py
index d4c84eb6b..ed16c73ae 100644
--- a/ipaclient/plugins/vault.py
+++ b/ipaclient/plugins/vault.py
@@ -119,8 +119,8 @@ def encrypt(data, symmetric_key=None, public_key=None):
return public_key_obj.encrypt(
data,
padding.OAEP(
- mgf=padding.MGF1(algorithm=hashes.SHA1()),
- algorithm=hashes.SHA1(),
+ mgf=padding.MGF1(algorithm=hashes.SHA256()),
+ algorithm=hashes.SHA256(),
label=None
)
)
@@ -154,8 +154,8 @@ def decrypt(data, symmetric_key=None, private_key=None):
return private_key_obj.decrypt(
data,
padding.OAEP(
- mgf=padding.MGF1(algorithm=hashes.SHA1()),
- algorithm=hashes.SHA1(),
+ mgf=padding.MGF1(algorithm=hashes.SHA256()),
+ algorithm=hashes.SHA256(),
label=None
)
)
@@ -705,14 +705,39 @@ class ModVaultData(Local):
return transport_cert, wrapping_algo
def _do_internal(self, algo, transport_cert, raise_unexpected,
- *args, **options):
+ use_oaep=False, *args, **options):
public_key = transport_cert.public_key()
# wrap session key with transport certificate
- wrapped_session_key = public_key.encrypt(
- algo.key,
- padding.PKCS1v15()
- )
+ # KRA may be configured using either the default PKCS1v15 or RSA-OAEP.
+ # there is no way to query this info using the REST interface.
+ if not use_oaep:
+ # PKCS1v15() causes an OpenSSL exception when FIPS is enabled
+ # if so, we fallback to RSA-OAEP
+ try:
+ wrapped_session_key = public_key.encrypt(
+ algo.key,
+ padding.PKCS1v15()
+ )
+ except ValueError:
+ wrapped_session_key = public_key.encrypt(
+ algo.key,
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.SHA256()),
+ algorithm=hashes.SHA256(),
+ label=None
+ )
+ )
+ else:
+ wrapped_session_key = public_key.encrypt(
+ algo.key,
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.SHA256()),
+ algorithm=hashes.SHA256(),
+ label=None
+ )
+ )
+
options['session_key'] = wrapped_session_key
name = self.name + '_internal'
@@ -723,7 +748,7 @@ class ModVaultData(Local):
errors.ExecutionError,
errors.GenericError):
_kra_config_cache.remove(self.api.env.domain)
- if raise_unexpected:
+ if raise_unexpected and use_oaep:
raise
return None
@@ -733,15 +758,23 @@ class ModVaultData(Local):
"""
# try call with cached transport certificate
result = self._do_internal(algo, transport_cert, False,
- *args, **options)
+ False, *args, **options)
if result is not None:
return result
# retrieve transport certificate (cached by vaultconfig_show)
transport_cert = self._get_vaultconfig(force_refresh=True)[0]
+
# call with the retrieved transport certificate
+ result = self._do_internal(algo, transport_cert, True,
+ False, *args, **options)
+
+ if result is not None:
+ return result
+
+ # call and use_oaep this time, last attempt
return self._do_internal(algo, transport_cert, True,
- *args, **options)
+ True, *args, **options)
@register(no_fail=True)
--
2.43.0

View File

@ -0,0 +1,49 @@
From 7cc96e42683a6d3ec9f2dc2a19e99330b6f3ce58 Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
Date: Wed, 4 Feb 2026 09:21:14 +0100
Subject: [PATCH] ipa-join: initialize pointer
OpenScanHub detected an uninitialized pointer in ipa_join:
Slapi_DN *sdn;
...
if (sdn) slapi_sdn_free(&sdn);
Initialize to NULL
Also initialize Slapi_Backend *be=NULL and char * filter=NULL
to avoid potential issues.
Fixes: https://pagure.io/freeipa/issue/9936
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
---
daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c b/daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c
index 3a70dd0a5594fc623e7e808ab8a734349a748a49..2f8923e10310a8a6e19ac701070d6451915c3be3 100644
--- a/daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c
+++ b/daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c
@@ -129,8 +129,8 @@ ipa_join(Slapi_PBlock *pb)
Slapi_PBlock *pbte = NULL;
Slapi_PBlock *pbtm = NULL;
Slapi_Entry *targetEntry=NULL;
- Slapi_DN *sdn;
- Slapi_Backend *be;
+ Slapi_DN *sdn=NULL;
+ Slapi_Backend *be=NULL;
Slapi_Entry **es = NULL;
int rc=0, ret=0, res;
size_t i;
@@ -139,7 +139,7 @@ ipa_join(Slapi_PBlock *pb)
char *fqdn = NULL;
Slapi_Mods *smods = NULL;
char *attrlist[] = {"fqdn", "krbPrincipalKey", "krbLastPwdChange", "krbPrincipalName", NULL };
- char * filter;
+ char * filter=NULL;
int scope = LDAP_SCOPE_SUBTREE;
char *principal = NULL;
--
2.52.0

View File

@ -1,88 +0,0 @@
From 84798137fabf75fe79aebbd97e4b8418de8ab0f2 Mon Sep 17 00:00:00 2001
From: Francisco Trivino <ftrivino@redhat.com>
Date: Fri, 19 Jan 2024 18:15:28 +0100
Subject: [PATCH] Vault: improve vault server archival/retrieval calls
error handling
If a vault operation fails, the error message just says "InternalError". This commit
improves error handling of key archival and retrieval calls by catching the PKIException
error and raising it as an IPA error.
Related: https://pagure.io/freeipa/issue/9191
Signed-off-by: Francisco Trivino <ftrivino@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
(cherry picked from commit dc1ab53f0aa0398d493f7440b5ec4d70d9c7d663)
---
ipaserver/plugins/vault.py | 40 +++++++++++++++++++++++++-------------
1 file changed, 26 insertions(+), 14 deletions(-)
diff --git a/ipaserver/plugins/vault.py b/ipaserver/plugins/vault.py
index 574c83a9a..13c4fac9a 100644
--- a/ipaserver/plugins/vault.py
+++ b/ipaserver/plugins/vault.py
@@ -45,6 +45,7 @@ if api.env.in_server:
import pki.key
from pki.crypto import DES_EDE3_CBC_OID
from pki.crypto import AES_128_CBC_OID
+ from pki import PKIException
if six.PY3:
unicode = str
@@ -1094,16 +1095,21 @@ class vault_archive_internal(PKQuery):
pki.key.KeyClient.KEY_STATUS_INACTIVE)
# forward wrapped data to KRA
- kra_client.keys.archive_encrypted_data(
- client_key_id,
- pki.key.KeyClient.PASS_PHRASE_TYPE,
- wrapped_vault_data,
- wrapped_session_key,
- algorithm_oid=algorithm_oid,
- nonce_iv=nonce,
- )
-
- kra_account.logout()
+ try:
+ kra_client.keys.archive_encrypted_data(
+ client_key_id,
+ pki.key.KeyClient.PASS_PHRASE_TYPE,
+ wrapped_vault_data,
+ wrapped_session_key,
+ algorithm_oid=algorithm_oid,
+ nonce_iv=nonce,
+ )
+ except PKIException as e:
+ kra_account.logout()
+ raise errors.EncodingError(
+ message=_("Unable to archive key: %s") % e)
+ finally:
+ kra_account.logout()
response = {
'value': args[-1],
@@ -1174,11 +1180,17 @@ class vault_retrieve_internal(PKQuery):
kra_client.keys.encrypt_alg_oid = algorithm_oid
# retrieve encrypted data from KRA
- key = kra_client.keys.retrieve_key(
- key_info.get_key_id(),
- wrapped_session_key)
+ try:
- kra_account.logout()
+ key = kra_client.keys.retrieve_key(
+ key_info.get_key_id(),
+ wrapped_session_key)
+ except PKIException as e:
+ kra_account.logout()
+ raise errors.EncodingError(
+ message=_("Unable to retrieve key: %s") % e)
+ finally:
+ kra_account.logout()
response = {
'value': args[-1],
--
2.43.0

View File

@ -0,0 +1,39 @@
From d5efb4decb74b50c05b1d252add1c075e660d154 Mon Sep 17 00:00:00 2001
From: Florence Blanc-Renaud <flo@redhat.com>
Date: Wed, 11 Feb 2026 10:23:36 +0100
Subject: [PATCH] ipatests: pruning is enabled when RSN is enabled
The test TestACMEPrune installs the server with --random-serial-numbers
but expects pruning to be disabled if the 389ds backend is BDB.
This is a wrong expectation as pruning is enabled as soon as RSN
are enabled (since commit 3777d2b).
Fix the test expectation.
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
---
ipatests/test_integration/test_acme.py | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/ipatests/test_integration/test_acme.py b/ipatests/test_integration/test_acme.py
index 4c66e4348beeaca95577a786a46e53fdc1532ef7..bedec9d1f9f0c168c11aceb155978b6a0dae8dd7 100644
--- a/ipatests/test_integration/test_acme.py
+++ b/ipatests/test_integration/test_acme.py
@@ -718,12 +718,8 @@ class TestACMEPrune(IntegrationTest):
< tasks.parse_version('11.3.0')):
raise pytest.skip("Certificate pruning is not available")
- # Pruning is enabled by default when the host supports lmdb
- if get_389ds_backend(self.master) == 'bdb':
- cs_cfg = self.master.get_file_contents(paths.CA_CS_CFG_PATH)
- assert "jobsScheduler.job.pruning.enabled=false".encode() in cs_cfg
- self.master.run_command(['ipa-acme-manage', 'pruning', '--enable'])
-
+ # Pruning is enabled by default when server is installed
+ # with --random-serial-numbers
cs_cfg = self.master.get_file_contents(paths.CA_CS_CFG_PATH)
assert "jobsScheduler.enabled=true".encode() in cs_cfg
assert "jobsScheduler.job.pruning.enabled=true".encode() in cs_cfg
--
2.52.0

View File

@ -1,98 +0,0 @@
From a406fd9aec7d053c044e73f16b05489bebd84bc8 Mon Sep 17 00:00:00 2001
From: Francisco Trivino <ftrivino@redhat.com>
Date: Fri, 19 Jan 2024 17:12:07 +0100
Subject: [PATCH] kra: set RSA-OAEP as default wrapping algo when FIPS is
enabled
Vault uses PKCS1v15 as default padding wrapping algo, which is not an approved
FIPS algorithm. This commit ensures that KRA is installed with RSA-OAEP if FIPS
is enabled. It also handles upgrade path.
Fixes: https://pagure.io/freeipa/issue/9191
Signed-off-by: Francisco Trivino <ftrivino@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
(cherry picked from commit f2eec9eb208e62f923375b9eaf34fcc491046a0d)
---
install/share/ipaca_default.ini | 3 +++
ipaserver/install/dogtaginstance.py | 4 +++-
ipaserver/install/krainstance.py | 12 ++++++++++++
ipaserver/install/server/upgrade.py | 12 ++++++++++++
4 files changed, 30 insertions(+), 1 deletion(-)
diff --git a/install/share/ipaca_default.ini b/install/share/ipaca_default.ini
index 082f507b2..691f1e1b7 100644
--- a/install/share/ipaca_default.ini
+++ b/install/share/ipaca_default.ini
@@ -166,3 +166,6 @@ pki_audit_signing_subject_dn=cn=KRA Audit,%(ipa_subject_base)s
# We will use the dbuser created for the CA.
pki_share_db=True
pki_share_dbuser_dn=uid=pkidbuser,ou=people,o=ipaca
+
+# KRA padding, set RSA-OAEP in FIPS mode
+pki_use_oaep_rsa_keywrap=%(fips_use_oaep_rsa_keywrap)s
\ No newline at end of file
diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py
index c2c6b3f49..c3c726f68 100644
--- a/ipaserver/install/dogtaginstance.py
+++ b/ipaserver/install/dogtaginstance.py
@@ -1020,7 +1020,9 @@ class PKIIniLoader:
# for softhsm2 testing
softhsm2_so=paths.LIBSOFTHSM2_SO,
# Configure a more secure AJP password by default
- ipa_ajp_secret=ipautil.ipa_generate_password(special=None)
+ ipa_ajp_secret=ipautil.ipa_generate_password(special=None),
+ # in FIPS mode use RSA-OAEP wrapping padding algo as default
+ fips_use_oaep_rsa_keywrap=tasks.is_fips_enabled()
)
@classmethod
diff --git a/ipaserver/install/krainstance.py b/ipaserver/install/krainstance.py
index 13cb2dcaa..0e04840a1 100644
--- a/ipaserver/install/krainstance.py
+++ b/ipaserver/install/krainstance.py
@@ -277,6 +277,18 @@ class KRAInstance(DogtagInstance):
# A restart is required
+ def enable_oaep_wrap_algo(self):
+ """
+ Enable KRA OAEP key wrap algorithm
+ """
+ with installutils.stopped_service('pki-tomcatd', 'pki-tomcat'):
+ directivesetter.set_directive(
+ self.config,
+ 'keyWrap.useOAEP',
+ 'true', quotes=False, separator='=')
+
+ # A restart is required
+
def update_cert_config(self, nickname, cert):
"""
When renewing a KRA subsystem certificate the configuration file
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
index e4dc7ae73..c84516b56 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -1780,6 +1780,18 @@ def upgrade_configuration():
else:
logger.info('ephemeralRequest is already enabled')
+ if tasks.is_fips_enabled():
+ logger.info('[Ensuring KRA OAEP wrap algo is enabled in FIPS]')
+ value = directivesetter.get_directive(
+ paths.KRA_CS_CFG_PATH,
+ 'keyWrap.useOAEP',
+ separator='=')
+ if value is None or value.lower() != 'true':
+ logger.info('Use the OAEP key wrap algo')
+ kra.enable_oaep_wrap_algo()
+ else:
+ logger.info('OAEP key wrap algo is already enabled')
+
# several upgrade steps require running CA. If CA is configured,
# always run ca.start() because we need to wait until CA is really ready
# by checking status using http
--
2.43.0

View File

@ -1,29 +0,0 @@
From a8e433f7c8d844d9f337a34db09b0197f2dbc5af Mon Sep 17 00:00:00 2001
From: Julien Rische <jrische@redhat.com>
Date: Tue, 20 Feb 2024 15:14:24 +0100
Subject: [PATCH] ipa-kdb: Fix double free in ipadb_reinit_mspac()
Fixes: https://pagure.io/freeipa/issue/9535
Signed-off-by: Julien Rische <jrische@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
(cherry picked from commit dd27d225524aa81c038a970961a4f878cf742e2a)
---
daemons/ipa-kdb/ipa_kdb_mspac.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
index deed513b9..0964d112a 100644
--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
+++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
@@ -3084,6 +3084,7 @@ ipadb_reinit_mspac(struct ipadb_context *ipactx, bool force_reinit,
}
free(resstr);
+ resstr = NULL;
flat_server_name = get_server_netbios_name(ipactx);
if (!flat_server_name) {
--
2.43.0

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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",
]

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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):

View File

@ -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",

View File

@ -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,
),
),

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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"

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,911 +0,0 @@
From aef72550a252d43423b99a179cb1e2ca3c2965e0 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Sat, 8 Nov 2025 00:40:42 +0100
Subject: [PATCH 01/16] ipa-graceperiod: fix memory leaks
Direct return of invalid grace limit bypassed cleanup code.
`tmpstr` variable was not freed in all code paths.
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
---
daemons/ipa-slapi-plugins/ipa-graceperiod/ipa_graceperiod.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/daemons/ipa-slapi-plugins/ipa-graceperiod/ipa_graceperiod.c b/daemons/ipa-slapi-plugins/ipa-graceperiod/ipa_graceperiod.c
index 345e1dee7..2912d5eb3 100644
--- a/daemons/ipa-slapi-plugins/ipa-graceperiod/ipa_graceperiod.c
+++ b/daemons/ipa-slapi-plugins/ipa-graceperiod/ipa_graceperiod.c
@@ -448,7 +448,8 @@ static int ipagraceperiod_preop(Slapi_PBlock *pb)
goto done;
} else if (grace_limit < -1) {
LOG_FATAL("Invalid passwordGraceLimit value %d\n", grace_limit);
- return LDAP_OPERATIONS_ERROR;
+ ret = LDAP_OPERATIONS_ERROR;
+ goto done;
}
grace_user_time = slapi_entry_attr_get_int(target_entry, "passwordGraceUserTime");
@@ -500,6 +501,7 @@ done:
slapi_vattr_values_free(&values, &actual_type_name, attr_free_flags);
}
if (sdn) slapi_sdn_free(&sdn);
+ slapi_ch_free_string(&tmpstr);
LOG("preop returning %d: %s\n", ret, errstr ? errstr : "success\n");
--
2.51.0
From 21471d2c34942bd2ef00850f22102f2006ec62ee Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Sat, 8 Nov 2025 00:44:35 +0100
Subject: [PATCH 02/16] ipa-lockout: fix memory leaks
Move cleanup of `unlock_time` to `done` label to ensure cleanup in all code paths.
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
---
daemons/ipa-slapi-plugins/ipa-lockout/ipa_lockout.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/daemons/ipa-slapi-plugins/ipa-lockout/ipa_lockout.c b/daemons/ipa-slapi-plugins/ipa-lockout/ipa_lockout.c
index a8095ccd3..9b157bcd3 100644
--- a/daemons/ipa-slapi-plugins/ipa-lockout/ipa_lockout.c
+++ b/daemons/ipa-slapi-plugins/ipa-lockout/ipa_lockout.c
@@ -812,7 +812,6 @@ static int ipalockout_preop(Slapi_PBlock *pb)
goto done;
}
}
- slapi_ch_free_string(&unlock_time);
}
max_fail = slapi_entry_attr_get_uint(policy_entry, "krbPwdMaxFailure");
@@ -837,6 +836,7 @@ static int ipalockout_preop(Slapi_PBlock *pb)
done:
if (lastfail) slapi_ch_free_string(&lastfail);
+ if (unlock_time) slapi_ch_free_string(&unlock_time);
slapi_entry_free(target_entry);
slapi_entry_free(policy_entry);
if (values != NULL) {
--
2.51.0
From c84d394fc273baeafd6e56b2d2fc1b5f3a0c363b Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Sat, 8 Nov 2025 00:46:35 +0100
Subject: [PATCH 03/16] ipa-pwd-extop: fix memory leaks
`cur_pw` was allocated but not freed after password validation.
`principal_expire` was allocated but not freed in all code paths.
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
---
daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c | 1 +
daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c | 4 ++++
2 files changed, 5 insertions(+)
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
index 989f2a02e..94e0f9c70 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
@@ -483,6 +483,7 @@ parse_req_done:
slapi_value_free(&cpw[0]);
slapi_value_free(&pw);
+ slapi_ch_free_string(&cur_pw);
if (ret != 0) {
LOG_TRACE("Invalid password!\n");
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
index 45626523f..7fab55c84 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
@@ -1489,6 +1489,7 @@ static int ipapwd_pre_bind(Slapi_PBlock *pb)
if (current_time > expire_time && expire_time > 0) {
LOG_FATAL("kerberos principal in %s is expired\n", dn);
+ slapi_ch_free_string(&principal_expire);
slapi_entry_free(entry);
slapi_sdn_free(&sdn);
slapi_send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL,
@@ -1521,6 +1522,7 @@ static int ipapwd_pre_bind(Slapi_PBlock *pb)
/* Authenticate the user. */
ret = ipapwd_authenticate(dn, entry, credentials);
if (ret) {
+ slapi_ch_free_string(&principal_expire);
slapi_entry_free(entry);
slapi_sdn_free(&sdn);
return 0;
@@ -1533,11 +1535,13 @@ static int ipapwd_pre_bind(Slapi_PBlock *pb)
/* Attempt to write out kerberos keys for the user. */
ipapwd_write_krb_keys(pb, discard_const(dn), entry, credentials);
+ slapi_ch_free_string(&principal_expire);
slapi_entry_free(entry);
slapi_sdn_free(&sdn);
return 0;
invalid_creds:
+ slapi_ch_free_string(&principal_expire);
slapi_entry_free(entry);
slapi_sdn_free(&sdn);
slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL);
--
2.51.0
From d26414276d678b0624904ad472a6c8b6e7dde980 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Sat, 8 Nov 2025 00:48:33 +0100
Subject: [PATCH 04/16] ipa-sidgen: fix memory leaks
In various code paths the `ctx` structure was freed, but not
`ctx->base_dn` which may have been allocated.
`sid` was duplicated, but the original memory was never freed.
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
---
daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.c | 9 ++++++++-
daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.h | 2 +-
daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen_common.c | 3 ++-
3 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.c b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.c
index 99e6b850b..9418ec303 100644
--- a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.c
+++ b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.c
@@ -66,6 +66,8 @@ static int ipa_sidgen_close(Slapi_PBlock *pb)
if (ret == 0) {
free_ranges(&ctx->ranges);
slapi_ch_free_string(&ctx->dom_sid);
+ slapi_ch_free_string(&ctx->base_dn);
+ free(ctx);
} else {
LOG_FATAL("Missing private plugin context.\n");
}
@@ -204,7 +206,10 @@ static int ipa_sidgen_init_ctx(Slapi_PBlock *pb, struct ipa_sidgen_ctx **_ctx)
done:
if (ret != 0) {
- free(ctx);
+ if (ctx) {
+ slapi_ch_free_string(&ctx->base_dn);
+ free(ctx);
+ }
} else {
*_ctx = ctx;
}
@@ -237,6 +242,8 @@ int ipa_sidgen_init(Slapi_PBlock *pb)
(void *) ipa_sidgen_add_post_op) != 0 ||
slapi_pblock_set(pb, SLAPI_PLUGIN_PRIVATE, ctx) != 0) {
LOG_FATAL("failed to register plugin\n");
+ slapi_ch_free_string(&ctx->base_dn);
+ free(ctx);
ret = EFAIL;
}
diff --git a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.h b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.h
index aec862796..fbae87e4d 100644
--- a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.h
+++ b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.h
@@ -74,7 +74,7 @@ struct range_info {
struct ipa_sidgen_ctx {
Slapi_ComponentId *plugin_id;
- const char *base_dn;
+ char *base_dn;
char *dom_sid;
struct range_info **ranges;
};
diff --git a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen_common.c b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen_common.c
index 13f4de541..b26c2df52 100644
--- a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen_common.c
+++ b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen_common.c
@@ -51,7 +51,7 @@ int get_dom_sid(Slapi_ComponentId *plugin_id, const char *base_dn, char **_sid)
int search_result;
Slapi_Entry **search_entries = NULL;
int ret;
- const char *sid;
+ char *sid = NULL;
search_pb = slapi_pblock_new();
if (search_pb == NULL) {
@@ -114,6 +114,7 @@ int get_dom_sid(Slapi_ComponentId *plugin_id, const char *base_dn, char **_sid)
ret = 0;
done:
+ slapi_ch_free_string(&sid);
slapi_free_search_results_internal(search_pb);
slapi_pblock_destroy(search_pb);
--
2.51.0
From 144b7c97aa29e8e99fe065d62643848a4eacc515 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Sat, 8 Nov 2025 00:57:49 +0100
Subject: [PATCH 05/16] ipa-range-check: fix memory leak
`ipa_range_check_close` function didn't do any cleanup.
The `ctx` structure was freed, but not `ctx->base_dn` which may have
been allocated.
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
---
.../ipa-range-check/ipa_range_check.c | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/daemons/ipa-slapi-plugins/ipa-range-check/ipa_range_check.c b/daemons/ipa-slapi-plugins/ipa-range-check/ipa_range_check.c
index 5b53a2fe5..37840cd47 100644
--- a/daemons/ipa-slapi-plugins/ipa-range-check/ipa_range_check.c
+++ b/daemons/ipa-slapi-plugins/ipa-range-check/ipa_range_check.c
@@ -76,7 +76,7 @@ Slapi_PluginDesc ipa_range_check_plugin_desc = {
struct ipa_range_check_ctx {
Slapi_ComponentId *plugin_id;
- const char *base_dn;
+ char *base_dn;
};
typedef enum {
@@ -469,6 +469,15 @@ static int ipa_range_check_start(Slapi_PBlock *pb)
static int ipa_range_check_close(Slapi_PBlock *pb)
{
+ int ret;
+ struct ipa_range_check_ctx *ctx;
+
+ ret = slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &ctx);
+ if (ret == 0 && ctx != NULL) {
+ slapi_ch_free_string(&ctx->base_dn);
+ free(ctx);
+ }
+
return 0;
}
@@ -752,7 +761,10 @@ static int ipa_range_check_init_ctx(Slapi_PBlock *pb,
done:
if (ret != 0) {
- free(ctx);
+ if (ctx) {
+ slapi_ch_free_string(&ctx->base_dn);
+ free(ctx);
+ }
} else {
*_ctx = ctx;
}
@@ -787,6 +799,8 @@ int ipa_range_check_init(Slapi_PBlock *pb)
(void *) ipa_range_check_add_pre_op) != 0 ||
slapi_pblock_set(pb, SLAPI_PLUGIN_PRIVATE, rc_ctx) != 0) {
LOG_FATAL("failed to register plugin\n");
+ slapi_ch_free_string(&rc_ctx->base_dn);
+ free(rc_ctx);
ret = EFAIL;
}
--
2.51.0
From 13277d0b4a060d934946ee34ba69a82f93f6f083 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Sat, 8 Nov 2025 00:59:58 +0100
Subject: [PATCH 06/16] ipa-extdom-extop: fix memory leaks
In various code paths the `ctx` structure was freed, but not `ctx`
resources (`base_dn`, `nss_ctx`, `extdom_instance_counter`) which may
have been allocated.
Plugin didn't have SLAPI_PLUGIN_CLOSE_FN registered, so context was
never freed on server shutdown.
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
---
.../ipa-extdom-extop/ipa_extdom_extop.c | 39 ++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c
index 5d22f9f2d..a180e3307 100644
--- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c
+++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c
@@ -171,6 +171,26 @@ static int ipa_extdom_start(Slapi_PBlock *pb)
return LDAP_SUCCESS;
}
+static int ipa_extdom_close(Slapi_PBlock *pb)
+{
+ int ret;
+ struct ipa_extdom_ctx *ctx;
+
+ ret = slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &ctx);
+ if (ret == 0 && ctx != NULL) {
+ if (ctx->extdom_instance_counter) {
+ slapi_counter_destroy(&ctx->extdom_instance_counter);
+ }
+ if (ctx->nss_ctx) {
+ back_extdom_free_context(&ctx->nss_ctx);
+ }
+ slapi_ch_free_string(&ctx->base_dn);
+ free(ctx);
+ }
+
+ return 0;
+}
+
static int ipa_extdom_extop(Slapi_PBlock *pb)
{
char *oid = NULL;
@@ -360,7 +380,16 @@ static int ipa_extdom_init_ctx(Slapi_PBlock *pb, struct ipa_extdom_ctx **_ctx)
done:
if (ret) {
- free(ctx);
+ if (ctx) {
+ if (ctx->extdom_instance_counter) {
+ slapi_counter_destroy(&ctx->extdom_instance_counter);
+ }
+ if (ctx->nss_ctx) {
+ back_extdom_free_context(&ctx->nss_ctx);
+ }
+ slapi_ch_free_string(&ctx->base_dn);
+ free(ctx);
+ }
} else {
*_ctx = ctx;
}
@@ -388,6 +417,10 @@ int ipa_extdom_init(Slapi_PBlock *pb)
ret = slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
(void *)ipa_extdom_start);
}
+ if (!ret) {
+ ret = slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *)ipa_extdom_close);
+ }
if (!ret) {
ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_OIDLIST,
ipa_extdom_oid_list);
@@ -405,6 +438,10 @@ int ipa_extdom_init(Slapi_PBlock *pb)
}
if (ret) {
LOG("Failed to set plug-in version, function, and OID.\n" );
+ slapi_counter_destroy(&extdom_ctx->extdom_instance_counter);
+ back_extdom_free_context(&extdom_ctx->nss_ctx);
+ slapi_ch_free_string(&extdom_ctx->base_dn);
+ free(extdom_ctx);
return -1;
}
--
2.51.0
From a203332f0689dae88aa4e31f42eb22416e28ada5 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Sat, 8 Nov 2025 01:03:52 +0100
Subject: [PATCH 07/16] ipa-enrollment: fix memory leaks
`smods`, `fqdn`, `sdn` were not freed.
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
---
daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c b/daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c
index 26cbb69d7..70a297da0 100644
--- a/daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c
+++ b/daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c
@@ -136,7 +136,7 @@ ipa_join(Slapi_PBlock *pb)
int is_root=0;
char *krbLastPwdChange = NULL;
char *fqdn = NULL;
- Slapi_Mods *smods;
+ Slapi_Mods *smods = NULL;
char *attrlist[] = {"fqdn", "krbPrincipalKey", "krbLastPwdChange", "krbPrincipalName", NULL };
char * filter;
@@ -328,8 +328,13 @@ free_and_return:
if (pbtm) {
slapi_pblock_destroy(pbtm);
}
+ if (smods) {
+ slapi_mods_free(&smods);
+ }
if (krbLastPwdChange) slapi_ch_free_string(&krbLastPwdChange);
+ if (fqdn) slapi_ch_free_string(&fqdn);
+ if (sdn) slapi_sdn_free(&sdn);
LOG("%s", errMesg ? errMesg : "success\n");
slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL);
--
2.51.0
From d7ba4663a72bc0aacff6cea38f83561d39c7b365 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Sat, 8 Nov 2025 01:05:15 +0100
Subject: [PATCH 08/16] topology: fix memory leaks
`agmt_attr_val`, `targetHost` and internal search results pblock were
not freed.
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
---
daemons/ipa-slapi-plugins/topology/topology_util.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/daemons/ipa-slapi-plugins/topology/topology_util.c b/daemons/ipa-slapi-plugins/topology/topology_util.c
index de8147a4a..f8da3b073 100644
--- a/daemons/ipa-slapi-plugins/topology/topology_util.c
+++ b/daemons/ipa-slapi-plugins/topology/topology_util.c
@@ -678,6 +678,7 @@ ipa_topo_util_update_agmt_list(TopoReplica *conf, TopoReplicaSegmentList *repl_s
mattrs[i],
segm_attr_val);
}
+ slapi_ch_free_string(&agmt_attr_val);
}
}
@@ -711,10 +712,10 @@ ipa_topo_util_update_agmt_list(TopoReplica *conf, TopoReplicaSegmentList *repl_s
ipa_topo_cfg_segment_set_visited(conf, topo_segm);
}
}
+ slapi_ch_free_string(&targetHost);
repl_agmt = entries[++nentries];
}
- slapi_free_search_results_internal(pb);
update_only:
/* check if segments not covered by agreement exist
@@ -724,6 +725,7 @@ update_only:
ipa_topo_get_plugin_hostname());
error_return:
+ slapi_free_search_results_internal(pb);
slapi_ch_free_string(&filter);
slapi_pblock_destroy(pb);
return rc;
--
2.51.0
From a1ebdf556d31f8ed5b1159674df9aff16f97ab2a Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Fri, 28 Nov 2025 12:19:05 +0100
Subject: [PATCH 09/16] ipa-pwd-extop: fix memory leaks
In `ipapwd_set_extradata` free `xdata` after it's not longer needed. It
was leaked because `slapi_value_new_berval()` makes a copy of the data.
In `ipapwd_free_slapi_value_array` free `svals` (caller's pointer)
instead of `sv` (local pointer).
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Alexander Bokovoy <abbra@users.noreply.github.com>
---
daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
index 5251713c6..204b2c6d9 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
@@ -1076,6 +1076,7 @@ int ipapwd_set_extradata(const char *dn,
slapi_value_free(&va[0]);
slapi_mods_free(&smods);
+ free(xdata);
return ret;
}
@@ -1091,7 +1092,7 @@ void ipapwd_free_slapi_value_array(Slapi_Value ***svals)
}
}
- slapi_ch_free((void **)sv);
+ slapi_ch_free((void **)svals);
}
void free_ipapwd_krbcfg(struct ipapwd_krbcfg **cfg)
--
2.51.0
From 7dd85cabad5b53f893cb1d1f4485608d54ee7c18 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Thu, 11 Dec 2025 10:08:35 +0100
Subject: [PATCH 10/16] ipa-pwd-extop: fix memory leaks of bind DN
In `ipapwd_chpwop()`, `ipapwd_setkeytab()`, and `ipapwd_getkeytab()`
functions, `bindDN`/`bind_dn` is obtained via `slapi_pblock_get()` with
SLAPI_CONN_DN which returns an allocated string. This string was never
freed in the cleanup sections of these functions.
Add `slapi_ch_free_string()` calls for the bind DN variables in the
`free_and_return` sections of all three functions.
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
index 94e0f9c70..eb441003e 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
@@ -647,6 +647,7 @@ free_and_return:
}
slapi_pblock_destroy(chpwop_pb);
}
+ slapi_ch_free_string(&bindDN);
slapi_ch_free_string(&oldPasswd);
slapi_ch_free_string(&newPasswd);
/* Either this is the same pointer that we allocated and set above,
@@ -1364,6 +1365,7 @@ static int ipapwd_setkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg)
/* Free anything that we allocated above */
free_and_return:
+ slapi_ch_free_string(&bindDN);
free(serviceName);
if (kset) ipapwd_keyset_free(&kset);
@@ -1781,6 +1783,7 @@ free_and_return:
slapi_send_ldap_result(pb, rc, NULL, err_msg, 0, NULL);
/* Free anything that we allocated above */
+ slapi_ch_free_string(&bind_dn);
if (krbctx) krb5_free_context(krbctx);
free(kenctypes);
free(service_name);
--
2.51.0
From 207f296ee4721d1fa51b03c3ecc843ef04fa2b5d Mon Sep 17 00:00:00 2001
From: Rafael Guterres Jeffman <rjeffman@redhat.com>
Date: Fri, 30 Jan 2026 17:01:06 -0300
Subject: [PATCH 11/16] ipa-pwd-extop: fix memory leaks in `ipapwd_pre_add()`
In `ipapwd_pre_add()`, when processing password from entry extension,
`userpw` was reassigned without freeing the previous value.
Additionally, `enabled` obtained from `ipapwd_getIpaConfigAttr()` was
never freed, and early returns bypassed the cleanup section causing
memory leaks.
Free `userpw` before reassigning it.
Free `enabled` after use.
Replace early `return 0` statements with `goto done` to ensure proper
cleanup of all allocated resources.
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
index 7fab55c84..6fb6856b8 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
@@ -282,6 +282,7 @@ static int ipapwd_pre_add(Slapi_PBlock *pb)
if (rc) {
goto done;
}
+ slapi_ch_free_string(&userpw);
userpw = slapi_ch_strdup(userpw_clear);
}
@@ -293,8 +294,11 @@ static int ipapwd_pre_add(Slapi_PBlock *pb)
if (NULL == enabled) {
LOG("no ipaMigrationEnabled in config, assuming FALSE\n");
} else if (0 == strcmp(enabled, "TRUE")) {
- return 0;
+ slapi_ch_free_string(&enabled);
+ rc = LDAP_SUCCESS;
+ goto done;
}
+ slapi_ch_free_string(&enabled);
/* With User Life Cycle, it could be a stage user that is activated.
* The userPassword and krb keys were set while the user was a stage user.
@@ -306,7 +310,8 @@ static int ipapwd_pre_add(Slapi_PBlock *pb)
LOG("User Life Cycle: %s is a activated stage user "
"(with prehashed password and krb keys)\n",
sdn ? slapi_sdn_get_dn(sdn) : "unknown");
- return 0;
+ rc = LDAP_SUCCESS;
+ goto done;
}
LOG("pre-hashed passwords are not valid\n");
--
2.51.0
From a3d6e658196665996932dd0d3f606673df8cee22 Mon Sep 17 00:00:00 2001
From: Rafael Guterres Jeffman <rjeffman@redhat.com>
Date: Fri, 30 Jan 2026 17:03:18 -0300
Subject: [PATCH 12/16] ipa-pwd-extop: fix bind DN memory leaks in pre-op
handlers
In `ipapwd_pre_add()` and `ipapwd_pre_mod()`, `binddn` is obtained via
`slapi_pblock_get()` with SLAPI_CONN_DN which returns an allocated
string. This string was never freed after use.
Add `slapi_ch_free_string(&binddn)` calls after the bind DN is no longer
needed in both functions.
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
index 6fb6856b8..51c39bbfc 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
@@ -378,6 +378,7 @@ static int ipapwd_pre_add(Slapi_PBlock *pb)
break;
}
}
+ slapi_ch_free_string(&binddn);
}
pwdop->pwdata.dn = slapi_ch_strdup(slapi_sdn_get_dn(sdn));
@@ -873,6 +874,7 @@ static int ipapwd_pre_mod(Slapi_PBlock *pb)
slapi_sdn_free(&bdn);
slapi_sdn_free(&tdn);
+ slapi_ch_free_string(&binddn);
}
--
2.51.0
From 549494b8d381a7960e232148ed676416dacad6fc Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Thu, 11 Dec 2025 13:13:45 +0100
Subject: [PATCH 13/16] ipa-pwd-extop: fix NT hash string memory leak
In `ipapwd_pre_add()` and `ipapwd_pre_mod()`, the `nt` string returned
by `ipapwd_gen_hashes()` was only freed when `is_smb` was true. When NT
hashes are generated for `is_ipant` entries but `is_smb` is false, the
`nt` string was leaked.
Free `nt`, `ntvals` and `svals` unconditionally.
Fix the error path in `ipapwd_pre_add()` where `nt` and `ntvals` were
leaked when `slapi_entry_attr_replace_sv()` failed for `svals`.
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
index 51c39bbfc..02c7ed3c6 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
@@ -417,22 +417,23 @@ static int ipapwd_pre_add(Slapi_PBlock *pb)
LOG_FATAL("failed to set encoded values in entry\n");
rc = LDAP_OPERATIONS_ERROR;
ipapwd_free_slapi_value_array(&svals);
+ slapi_ch_free_string(&nt);
+ ipapwd_free_slapi_value_array(&ntvals);
goto done;
}
-
- ipapwd_free_slapi_value_array(&svals);
}
+ ipapwd_free_slapi_value_array(&svals);
if (nt && is_smb) {
/* set value */
slapi_entry_attr_set_charptr(e, "sambaNTPassword", nt);
- slapi_ch_free_string(&nt);
}
+ slapi_ch_free_string(&nt);
if (ntvals && is_ipant) {
slapi_entry_attr_replace_sv(e, "ipaNTHash", ntvals);
- ipapwd_free_slapi_value_array(&ntvals);
}
+ ipapwd_free_slapi_value_array(&ntvals);
if (is_smb) {
/* with samba integration we need to also set sambaPwdLastSet or
@@ -913,21 +914,21 @@ static int ipapwd_pre_mod(Slapi_PBlock *pb)
/* replace values */
slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
"krbPrincipalKey", svals);
- ipapwd_free_slapi_value_array(&svals);
}
+ ipapwd_free_slapi_value_array(&svals);
if (nt && is_smb) {
/* replace value */
slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
"sambaNTPassword", nt);
- slapi_ch_free_string(&nt);
}
+ slapi_ch_free_string(&nt);
if (ntvals && is_ipant) {
slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
"ipaNTHash", ntvals);
- ipapwd_free_slapi_value_array(&ntvals);
}
+ ipapwd_free_slapi_value_array(&ntvals);
if (is_smb) {
/* with samba integration we need to also set sambaPwdLastSet or
--
2.51.0
From dc07404f435b9efe3da1b3ba2d81a0fe3ab608cc Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Thu, 11 Dec 2025 14:34:47 +0100
Subject: [PATCH 14/16] ipa-pwd-extop: fix password history values memory leak
In `ipapwd_post_modadd()`, the `pwvals` array returned by
`ipapwd_setPasswordHistory()` was passed to `slapi_mods_add_mod_values()`
but never freed. The `slapi_mods_add_mod_values()` function makes a copy
of the values, so the original array still needs to be freed.
Add `ipapwd_free_slapi_value_array()` call in the cleanup section to
free the array.
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
index 02c7ed3c6..74688ac39 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
@@ -1073,7 +1073,7 @@ static int ipapwd_post_modadd(Slapi_PBlock *pb)
void *op;
struct ipapwd_operation *pwdop = NULL;
Slapi_Mods *smods;
- Slapi_Value **pwvals;
+ Slapi_Value **pwvals = NULL;
int ret;
char *errMsg = "Internal operations error\n";
struct ipapwd_krbcfg *krbcfg = NULL;
@@ -1203,6 +1203,7 @@ done:
slapi_mods_free(&smods);
slapi_ch_free_string(&principal);
free_ipapwd_krbcfg(&krbcfg);
+ ipapwd_free_slapi_value_array(&pwvals);
return 0;
}
--
2.51.0
From e9978a6a1e3997329b54573dce9f460a26284302 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Thu, 11 Dec 2025 15:45:27 +0100
Subject: [PATCH 15/16] ipa-pwd-extop: fix memory leaks in
`ipapwd_gen_hashes()` error path
In `ipapwd_gen_hashes()`, when an error occurred after allocating output
parameters, `*ntvals` was freed but `*nthash` was not.
Add `slapi_ch_free_string(nthash)` to the error cleanup section.
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
daemons/ipa-slapi-plugins/ipa-pwd-extop/encoding.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/encoding.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/encoding.c
index 7b2f34122..05b317e93 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/encoding.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/encoding.c
@@ -220,6 +220,7 @@ int ipapwd_gen_hashes(struct ipapwd_krbcfg *krbcfg,
*svals = NULL;
*nthash = NULL;
+ *ntvals = NULL;
*errMesg = NULL;
if (is_krb) {
@@ -281,6 +282,7 @@ done:
if (rc) {
ipapwd_free_slapi_value_array(svals);
ipapwd_free_slapi_value_array(ntvals);
+ slapi_ch_free_string(nthash);
}
return rc;
--
2.51.0
From 6809fe26c08a3eca33be82448b248b7c330f5877 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Thu, 11 Dec 2025 16:16:02 +0100
Subject: [PATCH 16/16] ipa-pwd-extop: fix valueset memory leak in
`ipapwd_get_cur_kvno()`
In `ipapwd_get_cur_kvno()`, the `Slapi_ValueSet` obtained via
`slapi_attr_get_valueset()` was never freed. This function returns a
copy of the valueset that must be freed by the caller using
`slapi_valueset_free()`.
Add `slapi_valueset_free(svs)` before returning from the function.
Related: https://pagure.io/freeipa/issue/9895
Signed-off-by: Viktor Ashirov <vashirov@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
index 204b2c6d9..4c4240a98 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
@@ -755,6 +755,7 @@ next:
hint = slapi_valueset_next_value(svs, hint, &sv);
}
+ slapi_valueset_free(svs);
return kvno;
}
--
2.51.0

View File

@ -1,70 +0,0 @@
From 0d44e959e5bbe822b51137a8e7cf48fa25533805 Mon Sep 17 00:00:00 2001
From: Rafael Guterres Jeffman <rjeffman@redhat.com>
Date: Fri, 10 Dec 2021 12:15:36 -0300
Subject: [PATCH] Revert "freeipa.spec: depend on bind-dnssec-utils"
This reverts commit f89d59b6e18b54967682f6a37ce92ae67ab3fcda.
---
freeipa.spec.in | 4 +---
ipaplatform/base/paths.py | 2 +-
ipaplatform/fedora/paths.py | 1 +
ipaserver/dnssec/bindmgr.py | 1 -
4 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/freeipa.spec.in b/freeipa.spec.in
index 8f5c370e5..e20edb7bc 100755
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -576,11 +576,9 @@ Requires: %{name}-server = %{version}-%{release}
Requires: bind-dyndb-ldap >= 11.2-2
Requires: bind >= %{bind_version}
Requires: bind-utils >= %{bind_version}
-# bind-dnssec-utils is required by the OpenDNSSec integration
-# https://pagure.io/freeipa/issue/9026
-Requires: bind-dnssec-utils >= %{bind_version}
%if %{with bind_pkcs11}
Requires: bind-pkcs11 >= %{bind_version}
+Requires: bind-pkcs11-utils >= %{bind_version}
%else
Requires: softhsm >= %{softhsm_version}
Requires: openssl-pkcs11 >= %{openssl_pkcs11_version}
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index 7d21367ec..42a47f1df 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -258,8 +258,7 @@ class BasePathNamespace:
IPA_PKI_RETRIEVE_KEY = "/usr/libexec/ipa/ipa-pki-retrieve-key"
IPA_HTTPD_PASSWD_READER = "/usr/libexec/ipa/ipa-httpd-pwdreader"
IPA_PKI_WAIT_RUNNING = "/usr/libexec/ipa/ipa-pki-wait-running"
- DNSSEC_KEYFROMLABEL = "/usr/sbin/dnssec-keyfromlabel"
+ DNSSEC_KEYFROMLABEL = "/usr/sbin/dnssec-keyfromlabel-pkcs11"
- DNSSEC_KEYFROMLABEL_9_17 = "/usr/bin/dnssec-keyfromlabel"
GETSEBOOL = "/usr/sbin/getsebool"
GROUPADD = "/usr/sbin/groupadd"
USERMOD = "/usr/sbin/usermod"
diff --git a/ipaplatform/fedora/paths.py b/ipaplatform/fedora/paths.py
index 4e993c063..92a948966 100644
--- a/ipaplatform/fedora/paths.py
+++ b/ipaplatform/fedora/paths.py
@@ -36,6 +36,7 @@ class FedoraPathNamespace(RedHatPathNamespace):
NAMED_CRYPTO_POLICY_FILE = "/etc/crypto-policies/back-ends/bind.config"
if HAS_NFS_CONF:
SYSCONFIG_NFS = '/etc/nfs.conf'
+ DNSSEC_KEYFROMLABEL = "/usr/sbin/dnssec-keyfromlabel"
paths = FedoraPathNamespace()
diff --git a/ipaserver/dnssec/bindmgr.py b/ipaserver/dnssec/bindmgr.py
index 0c79cc03d..a15c0e601 100644
--- a/ipaserver/dnssec/bindmgr.py
+++ b/ipaserver/dnssec/bindmgr.py
@@ -127,7 +127,6 @@ class BINDMgr:
)
cmd = [
paths.DNSSEC_KEYFROMLABEL,
- '-E', 'pkcs11',
'-K', workdir,
'-a', attrs['idnsSecAlgorithm'][0],
'-l', uri
--
2.31.1

View File

@ -1,60 +0,0 @@
From 7807bcc55b4927fc327830d2237200772d2e1106 Mon Sep 17 00:00:00 2001
From: Rafael Guterres Jeffman <rjeffman@redhat.com>
Date: Fri, 17 Jun 2022 15:40:04 -0300
Subject: [PATCH] webui IdP: Remove arrow notation due to uglify-js limitation.
uglify-js 2.x series do not support ECMAScript 6 arrow notation ('=>')
for callback definition.
This patch changes the arrow definition callbacks for regular anonymous
function definitions.
---
install/ui/src/freeipa/idp.js | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/install/ui/src/freeipa/idp.js b/install/ui/src/freeipa/idp.js
index ada09c075..be3c4f0e6 100644
--- a/install/ui/src/freeipa/idp.js
+++ b/install/ui/src/freeipa/idp.js
@@ -227,7 +227,7 @@ IPA.add_idp_policy = function() {
// For custom template we show custom fields
// and mark all of them required and passed to the RPC
// If show_custom is false, the opposite happens
- custom_fields.forEach(fname => {
+ custom_fields.forEach(function(fname) {
widget_f = that.container.fields.get_field(fname);
widget_f.set_required(show_custom);
widget_f.set_enabled(show_custom);
@@ -235,7 +235,7 @@ IPA.add_idp_policy = function() {
});
// For template fields we show them if custom aren't shown
- template_fields.forEach(fname => {
+ template_fields.forEach(function(fname) {
widget_f = that.container.fields.get_field(fname);
widget_f.set_enabled(!show_custom);
widget_f.widget.set_visible(!show_custom);
@@ -252,7 +252,7 @@ IPA.add_idp_policy = function() {
var value = prov_f.get_value()[0];
// First, clear template fields from the previous provider choice
- template_fields.forEach(fname => {
+ template_fields.forEach(function(fname) {
widget_f = that.container.fields.get_field(fname);
widget_f.widget.set_visible(false);
widget_f.set_required(false);
@@ -260,9 +260,9 @@ IPA.add_idp_policy = function() {
});
// Second, enable and get required template-specific fields
- idp.templates.forEach(idp_v => {
+ idp.templates.forEach(function(idp_v) {
if (idp_v['value'] == value) {
- idp_v['fields'].forEach(fname => {
+ idp_v['fields'].forEach(function(fname) {
widget_f = that.container.fields.get_field(fname);
widget_f.set_required(true);
widget_f.set_enabled(true);
--
2.36.1

View File

@ -1,120 +0,0 @@
From 9a33838407f244e481523fe643bc0626874e8b1a Mon Sep 17 00:00:00 2001
From: Rafael Guterres Jeffman <rjeffman@redhat.com>
Date: Mon, 19 Dec 2022 14:57:03 -0300
Subject: [PATCH] Revert "DNSResolver: Fix use of nameservers with ports"
This reverts commit 5e2e4664aec641886923c2bec61ce25b96edb62a.
diff --git a/ipapython/dnsutil.py b/ipapython/dnsutil.py
index 58de365ab..4baeaf8cc 100644
--- a/ipapython/dnsutil.py 2023-05-19 05:12:52.471239297 -0300
+++ b/ipapython/dnsutil.py 2023-05-24 12:20:13.588867053 -0300
@@ -145,55 +145,6 @@
nameservers.remove(ipv4_loopback)
self.nameservers = nameservers
- @property
- def nameservers(self):
- return self._nameservers
-
- @nameservers.setter
- def nameservers(self, nameservers):
- """
- *nameservers*, a ``list`` of nameservers with optional ports:
- "SERVER_IP port PORT_NUMBER".
-
- Overloads dns.resolver.Resolver.nameservers setter to split off ports
- into nameserver_ports after setting nameservers successfully with the
- setter in dns.resolver.Resolver.
- """
- # Get nameserver_ports if it is already set
- if hasattr(self, "nameserver_ports"):
- nameserver_ports = self.nameserver_ports
- else:
- nameserver_ports = {}
-
- # Check nameserver items in list and split out converted port number
- # into nameserver_ports: { nameserver: port }
- if isinstance(nameservers, list):
- _nameservers = []
- for nameserver in nameservers:
- splits = nameserver.split()
- if len(splits) == 3 and splits[1] == "port":
- nameserver = splits[0]
- try:
- port = int(splits[2])
- if port < 0 or port > 65535:
- raise ValueError()
- except ValueError:
- raise ValueError(
- "invalid nameserver: %s is not a valid port" %
- splits[2])
- nameserver_ports[nameserver] = port
- _nameservers.append(nameserver)
- nameservers = _nameservers
-
- # Call dns.resolver.Resolver.nameservers setter
- if hasattr(dns.resolver.Resolver, "nameservers"):
- dns.resolver.Resolver.nameservers.__set__(self, nameservers)
- else:
- # old dnspython (<2) doesn't have 'nameservers' property
- self._nameservers = nameservers
- # Set nameserver_ports after successfull call to setter
- self.nameserver_ports = nameserver_ports
-
class DNSZoneAlreadyExists(dns.exception.DNSException):
supp_kwargs = {'zone', 'ns'}
diff --git a/ipatests/test_ipapython/test_dnsutil.py b/ipatests/test_ipapython/test_dnsutil.py
index 9070d89ad..5e7a46197 100644
--- a/ipatests/test_ipapython/test_dnsutil.py
+++ b/ipatests/test_ipapython/test_dnsutil.py
@@ -101,48 +101,3 @@ class TestSortURI:
assert dnsutil.sort_prio_weight([h3, h2, h1]) == [h1, h2, h3]
assert dnsutil.sort_prio_weight([h3, h3, h3]) == [h3]
assert dnsutil.sort_prio_weight([h2, h2, h1, h1]) == [h1, h2]
-
-
-class TestDNSResolver:
- @pytest.fixture(name="res")
- def resolver(self):
- """Resolver that doesn't read /etc/resolv.conf
-
- /etc/resolv.conf is not mandatory on systems
- """
- return dnsutil.DNSResolver(configure=False)
-
- def test_nameservers(self, res):
- res.nameservers = ["4.4.4.4", "8.8.8.8"]
- assert res.nameservers == ["4.4.4.4", "8.8.8.8"]
-
- def test_nameservers_with_ports(self, res):
- res.nameservers = ["4.4.4.4 port 53", "8.8.8.8 port 8053"]
- assert res.nameservers == ["4.4.4.4", "8.8.8.8"]
- assert res.nameserver_ports == {"4.4.4.4": 53, "8.8.8.8": 8053}
-
- res.nameservers = ["4.4.4.4 port 53", "8.8.8.8 port 8053"]
- assert res.nameservers == ["4.4.4.4", "8.8.8.8"]
- assert res.nameserver_ports == {"4.4.4.4": 53, "8.8.8.8": 8053}
-
- def test_nameservers_with_bad_ports(self, res):
- try:
- res.nameservers = ["4.4.4.4 port a"]
- except ValueError:
- pass
- else:
- pytest.fail("No fail on bad port a")
-
- try:
- res.nameservers = ["4.4.4.4 port -1"]
- except ValueError:
- pass
- else:
- pytest.fail("No fail on bad port -1")
-
- try:
- res.nameservers = ["4.4.4.4 port 65536"]
- except ValueError:
- pass
- else:
- pytest.fail("No fail on bad port 65536")

View File

@ -0,0 +1,16 @@
-----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEE11Z2TU1+KXxtrRFyaYdvcqbi008FAmlqBAcACgkQaYdvcqbi
009J4Q//cZi2ZS9RNBkU/v7v7Uvfb7WbsTAUQ2V5EJlqQ7BXbn2tkLtACbzeRn6X
aLECdZK+0etdyjbFlb7CvR6GUKw3BCZXpV62XXSQ3/EOhGRtGbrzRiIgaYOmFjzb
Hs4Ini6x/QClb0iA478ajG3YHrNYJhy3fFj+s3xzNGZuhJLvhlg3C2VDG7AgrzEx
RPiK7DIPY3SVUayxGuPLkNxsZiEQ1IcYtIu++Wp9QHmgPlAdP5aZ2TcEeotgUq4Z
+1LXEsDywEdQpg2iO92rja01q+zlcxzuQVjgdEh0G9lIw6f/GtB5WTdm2SER26oq
tld6MI6vrWZY+zMSc6FVjy0UB1Tk1kjjp7dp8OQhgzpe2i7LLKZl8bwRhvV6XlZT
+ylGHNK5cFglgtOjxIyLVGy6p6PIM+FtEFigUHB5faH+UrWTIL1+zrqWEhhiykI+
TT7/plOxG+tALr3ZDOZVqn1Idh9UQNQ8YdPzzKQMKAx5bbm5uHJXR6FQWt/P1QVc
S2N1Jlgi8IzbrnVPqN04BDCKsbOOWK6hP8a/SxoGW2IgTd7QWXalfRkeD4vdnM/1
9RRWRGuWWKuk2EUlKK202za+wJ7oOY4JSLXC3zxo1bTsHnUN+SYun+5BgYaiKLbM
dXZ4K+9Fs4clmT38W3I2LWPFoKcZVczN6/3MpQbTvmgEOkx8LZ0=
=jpva
-----END PGP SIGNATURE-----

View File

@ -1,16 +0,0 @@
-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEE11Z2TU1+KXxtrRFyaYdvcqbi008FAmVbZU0ACgkQaYdvcqbi
009Fgw/+PzHGNOJPs67TtoYITV/3ZCzMyrYTcazVACjD61Zw7JBgbZzZpQXxBSbj
7QWpNJa3P2JFtv2qOUXJto40mOGpMynyYpuYs4CtyJ86eHTUJyYTFppBmCzzozhT
2C2BeKKjzV8OOWQ7yO/2BTEZ7KtOcIr4ZI7iZCnLJF9Yt8x7TURjGRqxsHwT62Ip
vcrtm0LkkYv/fQ6pFZZfinKU1OBrZphwHMCU4Mlv411iQg4+NOxLSsVU/kegeKIO
adp4Y9g5dfAfdXEXb2Zt7gkmLaWMgf+XNSFDL/wkzRYt74HKwvbIPJQlTZ6pqLxQ
yTtiHGuMb7xNDWolpoueo1/lbxaHRRGJaSPs7zUht3IBxb7hiF65Gm3UaJhoeAXc
gVleZf/+0titOdkRfTD2N0P0hli7gaiRrbpw8K4joxMFpYrQGUxD8SI376gkOj6o
5RWSioPoG9txNM7Co+lVpci7WHhL+Tmhf1SlHyVJGKoNe/z4VHnjHeYlFWRVdDEI
OOupZzJQoLnso3lTwR5VEN8xGURnhbGV4MdUfD/6FhwmyHiPlYkytdZIsGsNDOab
978PPaKcIpbsZ4gUhshcbn7qaY809lNSpMtg8saYOP4J/5Nu+i9X5bJqOmoX0rKa
gAJDY5har+lExRnTEdYEGVB8qen5lqi8r1oYjnDpkSpq6BRoAHA=
=uQom
-----END PGP SIGNATURE-----

3774
SPECS/freeipa.spec Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff