Skip to content

Add LDAP/Kerberos helpers (DNS SRV, GSSAPI, getent)#243

Open
madhuriupadhye wants to merge 1 commit into
SSSD:masterfrom
madhuriupadhye:krb_misc2
Open

Add LDAP/Kerberos helpers (DNS SRV, GSSAPI, getent)#243
madhuriupadhye wants to merge 1 commit into
SSSD:masterfrom
madhuriupadhye:krb_misc2

Conversation

@madhuriupadhye
Copy link
Copy Markdown
Contributor

@madhuriupadhye madhuriupadhye commented Apr 27, 2026

Add LDAP/Kerberos helpers (DNS SRV, GSSAPI, getent)
Extend sssd-test-framework so LDAP+Kerberos system tests
can configure DNS, resolvers, and Directory Server GSSAPI
without per-test named, dig, or getent boilerplate.

  • dig — Parse A/PTR/SRV via jc; auto-append SRV for _service queries; strip trailing dots in results.
  • has_srv_record — True when SRV exists (parsed dig or dig +short fallback).
  • prepare_ldap_krb5_srv_discovery — Single path for LDAP/Kerberos SRV discovery on the test client:
    • Use lab DNS when _ldap._tcp / _kerberos._udp SRV already resolve.
    • Else try topology dns.test.
    • Else local named on 127.0.0.1 with LDAP/KDC A records and SRV in the discovery domain.
    • Always pin LDAP and KDC FQDNs in /etc/hosts (krb5_child uses /etc/krb5.conf, not only SSSD _srv_ discovery).
    • Probe KDC TCP port 88 before password authentication.
  • setup_sasl_canonicalize_bogus_ptr — Local named + /etc/hosts for ldap_sasl_canonicalize with bogus PTR; backs up and restores files via client.fs.
  • named helpers — Forwarders injection and dnssec-validation handling so named-checkconf does not fail on duplicate options.
  • AHostSv4Entry and ahostsv4 — Parsed getent ahostsv4 output.
  • resolve_ipv4 — Use multihost host.ip when set, otherwise ahostsv4.
  • LDAP.enable_gssapi(kdc) — Service principal, keytab, SASL maps, DS restart for GSSAPI/SASL tests.
  • KDC.configrdns = false in [libdefaults] for krb5 tests using krb5_auth.
  • docs/guides/testing-ldap-krb5.rst — Documents LDAP_KRB5 topology helpers, client.net.dig, prepare_ldap_krb5_srv_discovery, setup_sasl_canonicalize_bogus_ptr, and resolve_ipv4.
  • tests/test_tools.py — Unit tests for AHostSv4Entry.FromOutput.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces AHostSv4Entry and DigUtils to the SSSD test framework, enabling IPv4 address resolution via getent ahostsv4 and DNS checks using dig. The review feedback suggests improving security and robustness by switching from shell-based run to list-based exec for dig commands to prevent potential injection and better handle return codes. Additionally, suggestions were made to implement __str__ and __repr__ methods for consistency with other entry classes and to refine FromOutput to return None when no results are found to simplify boolean evaluations.

Comment thread sssd_test_framework/utils/tools.py Outdated
Comment on lines +1022 to +1026
result = self.host.conn.run(
f"dig +short SRV _ldap._tcp.{discovery_domain} | head -1",
raise_on_error=False,
)
return bool((result.stdout or "").strip())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

It is safer and more consistent with the repository's style to use self.host.conn.exec with a list of arguments instead of self.host.conn.run with a shell string. This avoids potential shell injection vulnerabilities. Additionally, the check should verify result.rc == 0 to ensure the command succeeded (e.g., dig is installed), and the head -1 pipe is redundant as the Python code already handles multiple lines correctly.

Suggested change
result = self.host.conn.run(
f"dig +short SRV _ldap._tcp.{discovery_domain} | head -1",
raise_on_error=False,
)
return bool((result.stdout or "").strip())
result = self.host.conn.exec(
["dig", "+short", "SRV", f"_ldap._tcp.{discovery_domain}"],
raise_on_error=False,
)
return result.rc == 0 and bool((result.stdout or "").strip())

Comment thread sssd_test_framework/utils/tools.py Outdated
Comment on lines +1035 to +1039
result = self.host.conn.run(
f"dig +short A {hostname} | head -1",
raise_on_error=False,
)
return bool((result.stdout or "").strip())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Similar to ldap_srv_available, using exec with a list is preferred for safety and consistency. Checking the return code (rc == 0) ensures that the function doesn't return True if the command fails with an error message on stdout (e.g., if the binary is missing).

Suggested change
result = self.host.conn.run(
f"dig +short A {hostname} | head -1",
raise_on_error=False,
)
return bool((result.stdout or "").strip())
result = self.host.conn.exec(
["dig", "+short", "A", hostname],
raise_on_error=False,
)
return result.rc == 0 and bool((result.stdout or "").strip())

Comment thread sssd_test_framework/utils/tools.py
Comment thread sssd_test_framework/utils/tools.py Outdated
Comment on lines +619 to +625
def FromOutput(cls, stdout: str) -> AHostSv4Entry:
for line in stdout.splitlines():
parts = line.split()
if parts:
return cls(ip=parts[0])
return cls(ip=None)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To maintain consistency with other getent helpers in this module and to allow for intuitive boolean checks by the caller, FromOutput should return None if no valid entry is found in the output. Currently, it returns an object with ip=None, which causes client.tools.getent.ahostsv4(...) to evaluate to True even when the lookup failed to find an address.

Suggested change
def FromOutput(cls, stdout: str) -> AHostSv4Entry:
for line in stdout.splitlines():
parts = line.split()
if parts:
return cls(ip=parts[0])
return cls(ip=None)
@classmethod
def FromOutput(cls, stdout: str) -> AHostSv4Entry | None:
for line in stdout.splitlines():
parts = line.split()
if parts:
return cls(ip=parts[0])
return None

Copy link
Copy Markdown
Contributor

@danlavu danlavu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@madhuriupadhye

So I wrote this a while ago, we have a dig utility

https://tests.sssd.io/en/latest/api/sssd_test_framework.utils.network.html#sssd_test_framework.utils.network.NetworkUtils

To add some more complication, I've changed the way it worked, in #231, which is going to be merged soon. Please rebase onto this PR, the existing dig stuff, and add only the getent.

@madhuriupadhye madhuriupadhye force-pushed the krb_misc2 branch 3 times, most recently from 2f274fa to 57f69c7 Compare April 30, 2026 12:20
@madhuriupadhye madhuriupadhye force-pushed the krb_misc2 branch 3 times, most recently from b34f486 to 4a3e93e Compare May 25, 2026 14:01
@madhuriupadhye madhuriupadhye force-pushed the krb_misc2 branch 3 times, most recently from c68779e to 2cdeace Compare May 28, 2026 10:31
@madhuriupadhye madhuriupadhye requested a review from danlavu May 28, 2026 10:34
@madhuriupadhye madhuriupadhye force-pushed the krb_misc2 branch 5 times, most recently from f52f467 to 823aeca Compare May 29, 2026 10:50
@madhuriupadhye madhuriupadhye changed the title Adds client.tools.dig (SRV/A checks) and client.tools.getent.ahostsv4 plus AHostSv4Entry Add LDAP/Kerberos helpers (DNS SRV, GSSAPI, getent) May 29, 2026
@madhuriupadhye madhuriupadhye force-pushed the krb_misc2 branch 2 times, most recently from e0c13ce to dc90293 Compare May 29, 2026 11:19
@thalman thalman self-assigned this May 29, 2026
@thalman thalman self-requested a review May 29, 2026 11:29
Extend sssd-test-framework so LDAP+Kerberos system tests
can configure DNS, resolvers, and Directory Server GSSAPI
without per-test `named`, `dig`, or `getent` boilerplate.

- **dig** — Parse A/PTR/SRV via `jc`; auto-append `SRV` for `_service` queries; strip trailing dots in results.
- **has_srv_record** — True when SRV exists (parsed `dig` or `dig +short` fallback).
- **prepare_ldap_krb5_srv_discovery** — Single path for LDAP/Kerberos SRV discovery on the test client:
  - Use lab DNS when `_ldap._tcp` / `_kerberos._udp` SRV already resolve.
  - Else try topology `dns.test`.
  - Else local `named` on `127.0.0.1` with LDAP/KDC A records and SRV in the discovery domain.
  - Always pin LDAP and KDC FQDNs in `/etc/hosts` (krb5_child uses `/etc/krb5.conf`, not only SSSD `_srv_` discovery).
  - Probe KDC TCP port 88 before password authentication.
- **setup_sasl_canonicalize_bogus_ptr** — Local `named` + `/etc/hosts` for `ldap_sasl_canonicalize` with bogus PTR; backs up and restores files via `client.fs`.
- **named helpers** — Forwarders injection and `dnssec-validation` handling so `named-checkconf` does not fail on duplicate options.
- **AHostSv4Entry** and **ahostsv4** — Parsed `getent ahostsv4` output.
- **resolve_ipv4** — Use multihost `host.ip` when set, otherwise `ahostsv4`.
- **LDAP.enable_gssapi(kdc)** — Service principal, keytab, SASL maps, DS restart for GSSAPI/SASL tests.
- **KDC.config** — `rdns = false` in `[libdefaults]` for krb5 tests using `krb5_auth`.
- **docs/guides/testing-ldap-krb5.rst** — Documents `LDAP_KRB5` topology helpers, `client.net.dig`, `prepare_ldap_krb5_srv_discovery`, `setup_sasl_canonicalize_bogus_ptr`, and `resolve_ipv4`.
- **tests/test_tools.py** — Unit tests for `AHostSv4Entry.FromOutput`.

Assisted by: Cursor(auto)

Signed-off-by: Madhuri Upadhye <mupadhye@redhat.com>
Copy link
Copy Markdown
Contributor

@thalman thalman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good.

What I really do not like is running dnf to install bind. IMO those packages should be preinstalled (and service disabled) if we need them. Is that possible?


# 4. Copy krb5.conf from KDC to LDAP server
krb5_conf = kdc.config()
self.host.conn.run(f"cat > /etc/krb5.conf << 'EOFKRB5'\n{krb5_conf}\nEOFKRB5")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there reason to not use fs.write()?


# 5. Configure Cyrus SASL to use the keytab
# This is critical - without this, the SASL GSSAPI plugin won't know where to find the keytab
self.host.conn.run(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have fs.mkdir and fs.write.

)

# Also create for other possible SASL application names
self.host.conn.run("cp /etc/sasl2/slapd.conf /etc/sasl2/ns-slapd.conf")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have fs.copy

)

# 8. Reload systemd and restart Directory Server
self.host.conn.run("systemctl daemon-reload")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have SystemdServices class for this

ldap_ip = self._role_ipv4(ldap_hostname, role_host=ldap_host, label="LDAP host")
if kdc_ip is None:
kdc_ip = self._role_ipv4(kdc_hostname, role_host=kdc_host, label="KDC host")
if self.host.conn.run("rpm -q bind", raise_on_error=False).rc != 0:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe rpm -q bind && rpm -q bind-utils? WDYT?

"""
return self.__exec(HostsEntry, "hosts", name, service)

def ahostsv4(self, name: str, *, service: str | None = None) -> AHostSv4Entry | None:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be better if we made one method to support both ipv4 and ipv6 lookups, so something like this?


database = "ahostsv6" if ipv6 else "ahostssv4"

command = self.host.conn.exec(["getent", *args, database, str(name)], raise_on_error=False)```

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants