From f27ce70c5d7c15ab5686b01057e17d3d44766111 Mon Sep 17 00:00:00 2001 From: William Mantly Date: Wed, 5 Aug 2026 01:27:14 -0400 Subject: [PATCH] fix: skip host self-registration when sso_token empty; roll up ldap-client v1.25.0 (v1.40.0) ldap-client no longer POSTs an empty Bearer to /api/directory-admin/resources (the misleading 'Invalid Credentials, login failed' during setup). Gitlink -> ldap-client 68fcdb5 (v1.25.0). --- CHANGELOG.md | 6 +++ bootstrap/bootstrap.js | 83 +++++++++++++++++++++++++++++++++++------- ldap-client | 2 +- 3 files changed, 77 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2e161e..eac06db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ orchestration code; see each submodule's own `CHANGELOG.md` [sso-manager-node](https://github.com/theta42/sso-manager-node/blob/master/CHANGELOG.md)) for what changed inside the apps it composes. +## [v1.40.0] - 2026-08-05 + +### Fixed +- **No more spurious "Invalid Credentials, login failed" during LDAP enrollment** (ldap-client v1.25.0, gitlink `68fcdb5`) — `index.sh` self-registered the host in the Directory when `sso_token` was *declared but empty* (it checked `[[ -v ]]`), POSTing an empty Bearer token and getting a misleading `LDAPLoginFailed`. It now only registers with a real token; the stack host (already seeded by the bootstrap) skips registration. +- **The `cn=ldapclient` service account now shows in the SSO Users UI** — it was created as a bare `organizationalRole` (invisible to the `posixAccount` user filter) and never joined `app_sso_service_account`, so it never appeared as a service account. The bootstrap now creates it as a `posixAccount` (uid 10001, above the regular-user reserved floor) and adds it to `app_sso_service_account`; for an existing account it best-effort adds the `posixAccount` shape (auxiliary, so it can't conflict with the structural `organizationalRole`) + the group membership. + ## [v1.39.0] - 2026-08-05 ### Fixed diff --git a/bootstrap/bootstrap.js b/bootstrap/bootstrap.js index 75d2465..6432c1a 100644 --- a/bootstrap/bootstrap.js +++ b/bootstrap/bootstrap.js @@ -178,31 +178,88 @@ function ldapModify(ldif) { } // ── 1. LDAP service account for the proxy ─────────────────────────────────── +// The proxy / ldap-client bind as cn=ldapclient. For it to SHOW in the SSO Users +// UI as a service account it must (a) match the user filter (posixAccount) and +// (b) be a member of app_sso_service_account (that membership is what the Users +// page marks as a non-person/service account). Older bootstraps created it as a +// bare organizationalRole (invisible to the Users list) and never joined the +// group, so it never appeared. Both are fixed here; the existing-path shape add +// is best-effort so a pre-existing account still binds even if the upgrade add +// fails. function ensureServiceAccount() { const pw = hashPasswordSSHA512(SVC_PASS); + const uidNum = '10001'; // distinct from the bootstrap admin's 10000; above uidGidReservedFloor so regular-user id allocation ignores it if (entryExists(SVC_DN)) { - log(`Service account ${SVC_DN} exists — resetting password to ./config`); - const r = ldapModify([ + log(`Service account ${SVC_DN} exists — ensuring service-account shape + password`); + // Add the auxiliary posixAccount objectClass + required attrs so the entry + // matches the Users list filter. inetOrgPerson is deliberately NOT added: + // it is structural and would conflict with the existing organizationalRole. + const shape = [ + `dn: ${SVC_DN}`, + 'changetype: modify', + 'add: objectClass', + 'objectClass: posixAccount', + '-', + 'add: uid', + 'uid: ldapclient', + '-', + 'add: uidNumber', + `uidNumber: ${uidNum}`, + '-', + 'add: gidNumber', + `gidNumber: ${uidNum}`, + '-', + 'add: homeDirectory', + 'homeDirectory: /nonexistent', + '-', + 'add: description', + 'description: LDAP bind service account (proxy / ldap-client)', + '', + ].join('\n'); + const rs = ldapModify(shape); + if (rs.code !== 0 && !/already exists|Type or value exists/i.test(rs.stderr)) { + log(' service-account shape warning (account still binds):', rs.stderr.trim()); + } + const rp = ldapModify([ `dn: ${SVC_DN}`, 'changetype: modify', 'replace: userPassword', `userPassword: ${pw}`, '', ].join('\n')); - if (r.code !== 0) log(' password reset warning:', r.stderr.trim()); - return; + if (rp.code !== 0) log(' password reset warning:', rp.stderr.trim()); + } else { + log(`Creating service account ${SVC_DN}`); + const entry = [ + `dn: ${SVC_DN}`, + 'objectClass: inetOrgPerson', + 'objectClass: posixAccount', + 'objectClass: top', + 'cn: ldapclient', + 'sn: ldapclient', + 'uid: ldapclient', + `uidNumber: ${uidNum}`, + `gidNumber: ${uidNum}`, + 'homeDirectory: /nonexistent', + 'description: LDAP bind service account (proxy / ldap-client)', + `userPassword: ${pw}`, + '', + ].join('\n'); + const r = ldapAdd(entry); + if (r.code !== 0) throw new Error(`ldapadd service account failed: ${r.stderr.trim()}`); } - log(`Creating service account ${SVC_DN}`); - const r = ldapAdd([ - `dn: ${SVC_DN}`, - 'objectClass: organizationalRole', - 'objectClass: simpleSecurityObject', - 'objectClass: top', - 'cn: ldapclient', - `userPassword: ${pw}`, + // Mark it as a service account (the Users UI's service-account signal). + const gdn = `cn=app_sso_service_account,ou=groups,${BASE_DN}`; + const rm = ldapModify([ + `dn: ${gdn}`, + 'changetype: modify', + 'add: member', + `member: ${SVC_DN}`, '', ].join('\n')); - if (r.code !== 0) throw new Error(`ldapadd service account failed: ${r.stderr.trim()}`); + if (rm.code === 0) log(` marked ${SVC_DN} as a service account`); + else if (/already exists|Type or value exists/i.test(rm.stderr)) log(` ${SVC_DN} already in app_sso_service_account`); + else log(` app_sso_service_account membership warning:`, rm.stderr.trim()); } // ── 2. First admin user ───────────────────────────────────────────────────── diff --git a/ldap-client b/ldap-client index ebaac18..68fcdb5 160000 --- a/ldap-client +++ b/ldap-client @@ -1 +1 @@ -Subproject commit ebaac181bcf49ae8b8a4cdf8af419a7d6e39e698 +Subproject commit 68fcdb53bd1487863baa4a8dc5a8b42dac81f39a