QA Use Cases — Identity (OpenLDAP + Dex)
Tool page: identity.md. One identity store:
OpenLDAP on the mini (dc=agentops,dc=local), consumed directly or
through one federation hop (Dex at id.rhels.com, OpenShift
OAuth). “If a service grows its own user database, that is a bug
to file” (source: identity.md).
Admin stories
IDN-A1 — Seed fidelity: live directory equals the seed [CLI]
- Persona: Directory admin. Goal: a cold rebuild reproduces the directory exactly as it stands.
- Steps: dump the live directory (users, groups, members) and
diff against the
openldap-seedLDIF inrhels/lab-agentops(gitops/modules/ldap/ldap.yaml). - Expected: no unexplained differences; the one by-design
exception is
app1002-prod, deliberately unseeded (it arrives through the request path) (source: identity.md — comparison “re-verified entry-for-entry and member-for-member on 2026-10-26”). - Assert: normalized live LDIF == seed LDIF, with the
allowlist
{app1002-prod}as the only permitted live-only DN set; the diff output is the run’s measured value.
IDN-A2 — LDAPS certificate is the Let’s Encrypt cert [CLI]
- Persona: Directory admin. Goal: the 636 listener serves
the LE certificate from
/certs, not a regenerated self-signed one. - Steps: probe the listener’s chain; confirm Certificate
ldap-rhels-tlsReady; repeat after any openldap pod restart. - Expected: chain verifies, subject is the ldap front name. Regression story: on 2026-10-28 the pod regenerated a self-signed cert after a restart; the postStart-hook fix is what this story guards (source: identity.md, “Listeners”; lab record 2026-10-28).
- Assert:
openssl s_client -connect ldap.rhels.com:636 -verify_return_error→Verify return code: 0, subject CN ==ldap.rhels.com, issuer matches /Let’s Encrypt/;kubectl -n agentops get certificate ldap-rhels-tls→READY == True.
IDN-A3 — Group-sync freshness [config/API]
- Persona: Platform admin. Goal: the read-only checker is current and per-group failures stay isolated.
- Steps: read the
group-sync-statusConfigMap; check the checker CronJob’s last-run age; query the Pushgateway series. - Expected: status no older than two checker cycles (2-minute
cadence); a failing group carries
failing_sinceand fails alone (source: identity.md, “The group machinery”). - Assert:
now - status.timestamp <= 240s; for every watched group, a status entry exists; the set of failing groups in the status == the set the suite’s own directory probe finds (cross-check, not self-report).
IDN-A4 — PostSync seed Job actually re-ran [CLI]
- Persona: Directory admin. Goal: guard the known seam — the seed hook runs only when a sync completes.
- Preconditions: a seed change was pushed since the last run.
- Steps: compare the
openldap-seedJob’s creation time to the seed change’s commit time; read its logs. - Expected: a Job run exists after the change. A stale Job is
the documented failure mode; the remedy is a direct
ldapadd/ldapmodifywith git kept as the record (source: identity.md, caveats 2026-10-24). - Assert:
job.creationTimestamp > seed_change_commit_timeand job statusComplete; outside a seed-change window the story records not-run.
IDN-A5 — Dex connector health [config/API]
- Persona: Platform admin. Goal: the federation hop every Argo/ACS login rides on is configured as recorded.
- Steps: fetch Dex’s discovery document; confirm the LDAP connector and its dedicated bind identity.
- Expected: discovery served; connector present; bind custody per-consumer, never the directory admin (source: service-directory.md; stackrox.md, “LDAP SSO”).
- Assert:
GET https://id.rhels.com/.well-known/openid-configuration→200,issuer == "https://id.rhels.com"; Dex’s rendered config contains a connector oftype == "ldap"whose bind DN is the recorded per-app bind account.
User stories
IDN-U1 — Bind login as a fixture user [CLI]
- Persona:
dev1. Goal: the primitive every login story in this section stands on. - Steps: bind as dev1 over the in-cluster 389 listener and over LDAPS 636.
- Expected: the bind returns dev1’s DN on both (source: identity.md Part B, “Verify a login works”).
- Assert:
ldapwhoami→ exit 0, output ==dn:uid=dev1,ou=users,dc=agentops,dc=localon both listeners.
IDN-U2 — Group membership is visible and correct [CLI]
- Persona:
dev1. - Steps: search
ou=groupsfor groups whosememberis dev1’s DN. - Expected: exactly the seeded app1001 memberships
(
app1001-devsupport,app1001-nonprod); the app1003 segment stays absent by design (source: identity.md — fixtures and the app1003 premise). - Assert: returned group CN set ==
{app1001-devsupport, app1001-nonprod}(plus any group created for dev1 by a later recorded request — additions must cite their request file).
IDN-U3 — Wrong password is rejected (negative) [CLI]
- Persona:
dev1with a wrong password. - Steps: bind with an incorrect password; repeat through the OpenShift console login form.
- Expected: invalid credentials; a visible console error.
The console leg is proven hourly by
console-wrong-password(source: health-suite README). - Assert: bind → exit non-zero, error matches /Invalid credentials (49)/; console leg per the health suite’s existing check result.
IDN-U4 — New user end-to-end [CLI]
- Persona: Directory admin creating the next fixture, then the new user. Goal: the documented add-a-user flow works as written, including its limits.
- Steps: follow identity.md Part B exactly: hash, seed edit,
sync, live
ldapmodifyfor membership, verify bind. - Expected: the user entry appears from the seed sync; the
membership does not appear until the live
ldapmodify— that asymmetry is the documented behavior, and the story fails if either half changes silently (source: identity.md, propagation table). - Assert: after sync,
ldapsearch (uid=<new>)→ 1 entry and group member set unchanged; after theldapmodify, member set contains the new DN; bind as the new user → exit 0. (Runs only when a fixture is actually being added — an on-demand story, not a per-run one.)
Gaps
- TBD: plaintext LDAP on 389 “is additional, not enforced” (identity.md); no documented condition sets when LDAPS enforcement happens, so an enforcement story has no expectation to assert yet.
- Docs gap: Dex has no book page of its own; IDN-A5’s expectations are assembled from the Service Directory and the StackRox page.
- Manual-only: none per-run (IDN-U4 is on-demand by design, not manual — its steps are scripted when invoked).