Skip to content

Commit b34f486

Browse files
Add LDAP GSSAPI support
Add LDAP GSSAPI support and port DNS discovery tests Add enable_gssapi() method to LDAP role for GSSAPI/SASL authentication testing. Added `LDAP.enable_gssapi(kdc)` method to configure Directory Server for GSSAPI authentication. **Usage**: ```python ldap.enable_gssapi(kdc) client.sssd.domain["ldap_sasl_mech"] = "GSSAPI" Signed-off-by: Madhuri Upadhye <mupadhye@redhat.com>
1 parent 39fd0f1 commit b34f486

1 file changed

Lines changed: 141 additions & 1 deletion

File tree

sssd_test_framework/roles/ldap.py

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
from __future__ import annotations
44

55
import base64
6+
import tempfile
7+
import time
68
from datetime import datetime
79
from enum import Enum
8-
from typing import Any, Generic, TypeVar
10+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
911

1012
import ldap
1113
import ldap.ldapobject
@@ -17,6 +19,9 @@
1719
from .generic import GenericNetgroupMember, GenericPasswordPolicy, ProtocolName
1820
from .nfs import NFSExport
1921

22+
if TYPE_CHECKING:
23+
from .kdc import KDC
24+
2025
__all__ = [
2126
"LDAPRoleType",
2227
"LDAPPasswordPolicy",
@@ -239,6 +244,141 @@ def setup(self) -> None:
239244
except ldap.TYPE_OR_VALUE_EXISTS:
240245
pass
241246

247+
def enable_gssapi(self, kdc: KDC) -> None:
248+
"""
249+
Configure Directory Server for GSSAPI/SASL authentication.
250+
251+
This method sets up the LDAP server to accept GSSAPI (Kerberos) authentication
252+
by creating a service principal, exporting the keytab, and configuring Directory Server.
253+
254+
.. code-block:: python
255+
:caption: Example usage
256+
257+
@pytest.mark.topology(KnownTopology.LDAP_KRB5)
258+
def test_ldap_gssapi(client: Client, ldap: LDAP, kdc: KDC):
259+
# Enable GSSAPI on LDAP server
260+
ldap.enable_gssapi(kdc)
261+
262+
ldap.user('testuser').add()
263+
kdc.principal('testuser').add()
264+
265+
# Configure SSSD to use GSSAPI
266+
client.sssd.domain["ldap_sasl_mech"] = "GSSAPI"
267+
client.sssd.start()
268+
269+
result = client.tools.id('testuser')
270+
assert result is not None
271+
272+
:param kdc: KDC role object to create service principal
273+
:type kdc: KDC
274+
"""
275+
276+
# 1. Install required packages
277+
self.host.conn.run(
278+
"dnf install -y cyrus-sasl-gssapi krb5-workstation || "
279+
"yum install -y cyrus-sasl-gssapi krb5-workstation",
280+
)
281+
self.host.conn.run("rpm -q cyrus-sasl-gssapi")
282+
283+
# 2. Create LDAP service principal
284+
ldap_principal = f"ldap/{self.host.hostname}"
285+
kdc.principal(ldap_principal).add(password=None)
286+
287+
# 3. Export keytab to LDAP server (same transfer pattern as LDAPKRB5TopologyController)
288+
keytab_path = "/etc/dirsrv/ds.keytab"
289+
keytab_staging = "/tmp/sssd-test-framework-ds.keytab"
290+
qualified_principal = kdc.qualify(ldap_principal)
291+
kdc.host.conn.run(f"rm -f {keytab_staging}", raise_on_error=False)
292+
kdc.host.conn.run(
293+
f"kadmin.local -q 'ktadd -k {keytab_staging} -norandkey \"{qualified_principal}\"'"
294+
)
295+
with tempfile.NamedTemporaryFile() as tmp:
296+
kdc.host.fs.download(keytab_staging, tmp.name)
297+
self.host.fs.upload(tmp.name, keytab_path)
298+
kdc.host.conn.run(f"rm -f {keytab_staging}", raise_on_error=False)
299+
self.host.conn.run(f"chown dirsrv:dirsrv {keytab_path}")
300+
self.host.conn.run(f"chmod 600 {keytab_path}")
301+
302+
# 4. Copy krb5.conf from KDC to LDAP server
303+
krb5_conf = kdc.config()
304+
self.host.conn.run(f"cat > /etc/krb5.conf << 'EOFKRB5'\n{krb5_conf}\nEOFKRB5")
305+
306+
# Add default_keytab_name to krb5.conf as fallback
307+
self.host.conn.run(
308+
f"sed -i '/\\[libdefaults\\]/a\\ default_keytab_name = {keytab_path}' /etc/krb5.conf"
309+
)
310+
311+
# 5. Configure Cyrus SASL to use the keytab
312+
# This is critical - without this, the SASL GSSAPI plugin won't know where to find the keytab
313+
self.host.conn.run(
314+
f"mkdir -p /etc/sasl2 && "
315+
f"cat > /etc/sasl2/slapd.conf << 'EOFSASL'\n"
316+
f"mech_list: GSSAPI EXTERNAL PLAIN LOGIN\n"
317+
f"keytab: {keytab_path}\n"
318+
f"EOFSASL"
319+
)
320+
321+
# Also create for other possible SASL application names
322+
self.host.conn.run("cp /etc/sasl2/slapd.conf /etc/sasl2/ns-slapd.conf")
323+
self.host.conn.run("cp /etc/sasl2/slapd.conf /etc/sasl2/ldap.conf")
324+
325+
# 6. Set KRB5_KTNAME environment variable for Directory Server via sysconfig
326+
# Note: systemd Environment= directives don't work reliably in containers
327+
# Use EnvironmentFile instead
328+
self.host.conn.run(
329+
f"echo 'KRB5_KTNAME={keytab_path}' > /etc/sysconfig/dirsrv-localhost"
330+
)
331+
332+
# 7. Configure SASL identity mapping in Directory Server
333+
# Align default Kerberos maps with the data suffix (sssd-qe krb_credential_cache) and
334+
# add a high-priority map for host/ldap service principals used by SSSD GSSAPI binds.
335+
realm = kdc.realm
336+
base_dn = self.naming_context
337+
binddn = self.host.binddn
338+
bindpw = self.host.bindpw
339+
340+
sasl_ldif = ""
341+
for cn in (
342+
"Kerberos uid mapping",
343+
"rfc 2829 dn syntax",
344+
"rfc 2829 u syntax",
345+
"uid mapping",
346+
):
347+
sasl_ldif += (
348+
f"dn: cn={cn},cn=mapping,cn=sasl,cn=config\n"
349+
"changetype: modify\n"
350+
"replace: nsSaslMapBaseDNTemplate\n"
351+
f"nsSaslMapBaseDNTemplate: {base_dn}\n"
352+
"\n"
353+
)
354+
self.host.conn.run(
355+
f"ldapmodify -x -D '{binddn}' -w '{bindpw}' -H ldap://localhost",
356+
input=sasl_ldif,
357+
)
358+
service_map_ldif = (
359+
"dn: cn=SSSD service principals,cn=mapping,cn=sasl,cn=config\n"
360+
"changetype: add\n"
361+
"objectClass: top\n"
362+
"objectClass: nsSaslMapping\n"
363+
"cn: SSSD service principals\n"
364+
f"nsSaslMapRegexString: ^(host|ldap)/.*@{realm}$\n"
365+
"nsSaslMapBaseDNTemplate: cn=Directory Manager\n"
366+
"nsSaslMapFilterTemplate: (objectclass=*)\n"
367+
"nsSaslMapPriority: 10\n"
368+
)
369+
self.host.conn.run(
370+
f"ldapmodify -x -D '{binddn}' -w '{bindpw}' -H ldap://localhost",
371+
input=service_map_ldif,
372+
raise_on_error=False,
373+
)
374+
375+
# 8. Reload systemd and restart Directory Server
376+
self.host.conn.run("systemctl daemon-reload")
377+
self.host.conn.run("systemctl restart dirsrv@localhost")
378+
379+
# Wait for Directory Server to fully start with GSSAPI support
380+
time.sleep(3)
381+
242382
def fqn(self, name: str) -> str:
243383
"""
244384
Return fully qualified name in form name@domain.

0 commit comments

Comments
 (0)