Add LDAP/Kerberos helpers (DNS SRV, GSSAPI, getent)#243
Conversation
There was a problem hiding this comment.
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.
| 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()) |
There was a problem hiding this comment.
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.
| 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()) |
| result = self.host.conn.run( | ||
| f"dig +short A {hostname} | head -1", | ||
| raise_on_error=False, | ||
| ) | ||
| return bool((result.stdout or "").strip()) |
There was a problem hiding this comment.
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).
| 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()) |
| 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) | ||
|
|
There was a problem hiding this comment.
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.
| 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 |
c9cc202 to
a2a437b
Compare
danlavu
left a comment
There was a problem hiding this comment.
So I wrote this a while ago, we have a dig utility
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.
2f274fa to
57f69c7
Compare
b34f486 to
4a3e93e
Compare
c68779e to
2cdeace
Compare
f52f467 to
823aeca
Compare
client.tools.dig (SRV/A checks) and client.tools.getent.ahostsv4 plus AHostSv4Entrye0c13ce to
dc90293
Compare
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>
thalman
left a comment
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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") |
| ) | ||
|
|
||
| # 8. Reload systemd and restart Directory Server | ||
| self.host.conn.run("systemctl daemon-reload") |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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)```
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, orgetentboilerplate.jc; auto-appendSRVfor_servicequeries; strip trailing dots in results.digordig +shortfallback)._ldap._tcp/_kerberos._udpSRV already resolve.dns.test.namedon127.0.0.1with LDAP/KDC A records and SRV in the discovery domain./etc/hosts(krb5_child uses/etc/krb5.conf, not only SSSD_srv_discovery).named+/etc/hostsforldap_sasl_canonicalizewith bogus PTR; backs up and restores files viaclient.fs.dnssec-validationhandling sonamed-checkconfdoes not fail on duplicate options.getent ahostsv4output.host.ipwhen set, otherwiseahostsv4.rdns = falsein[libdefaults]for krb5 tests usingkrb5_auth.LDAP_KRB5topology helpers,client.net.dig,prepare_ldap_krb5_srv_discovery,setup_sasl_canonicalize_bogus_ptr, andresolve_ipv4.AHostSv4Entry.FromOutput.