Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f27ce70c5d | |||
| 3354407f04 | |||
| 84d7c96c17 |
@@ -8,6 +8,17 @@ 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
|
||||
- **Plain LDAP (389) now reachable from the host** — `docker-compose.yml` published only LDAPS (636); plain LDAP (389) was deliberately not mapped, so the stack host's own enrollment (`setup.sh` → ldap-client, which configures sssd against `ldap://localhost:389` and `ldaps://localhost:636`) could not reach the directory over loopback. Both 389 and 636 are now published to the host (bind 0.0.0.0; `LDAP_BIND`/`LDAPS_BIND=127.0.0.1` to lock to the host only).
|
||||
|
||||
## [v1.38.0] - 2026-08-04
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -129,12 +129,15 @@ see browser warnings.)
|
||||
|
||||
Optional extra ports (only if you need them):
|
||||
- **4443** — alternate HTTPS listener (e.g. if 443 is taken by something else).
|
||||
- **636** (LDAPS) — for direct-LDAP clients on other machines (Linux hosts
|
||||
via PAM/SSSD, LDAP-native apps). The proxy itself reaches LDAP over the
|
||||
internal Docker network, so you do **not** need to expose 636 for the stack
|
||||
to work.
|
||||
**Do not forward 636 to the public internet.** If you need LAN clients to bind
|
||||
LDAP, set `CFG_LDAPS_HOST=ldap.internal.example.com` (or `sso-manager` for
|
||||
- **389** (LDAP) + **636** (LDAPS) — direct-LDAP access. The stack host's **own**
|
||||
enrollment (`setup.sh` → ldap-client) configures its sssd against
|
||||
`ldap://localhost:389` / `ldaps://localhost:636`, so both ports are published
|
||||
to the host by default (bind 0.0.0.0; set `LDAP_BIND`/`LDAPS_BIND=127.0.0.1` to
|
||||
lock to the host). LAN clients (Linux hosts via PAM/SSSD, LDAP-native apps) can
|
||||
bind over either; the proxy itself reaches LDAP over the internal Docker
|
||||
network and doesn't need them.
|
||||
**Do not forward 389/636 to the public internet.** If you need LAN clients to
|
||||
bind LDAP, set `CFG_LDAPS_HOST=ldap.internal.example.com` (or `sso-manager` for
|
||||
same-host Docker clients) in `setup.env` and use an internal DNS record / cert
|
||||
SAN. The default shows the public SSO hostname, which implies a public route.
|
||||
|
||||
|
||||
Vendored
+70
-13
@@ -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 ─────────────────────────────────────────────────────
|
||||
|
||||
+10
-6
@@ -52,12 +52,16 @@ services:
|
||||
# the UI is reachable on the LAN during setup). Set SSO_BIND=127.0.0.1 to
|
||||
# lock it to localhost once the proxy fronts it at https://<SSO_HOST>.
|
||||
- "${SSO_BIND:-0.0.0.0}:${SSO_PORT:-3001}:3001"
|
||||
# LDAPS for EXTERNAL direct-LDAP clients (legacy apps). The proxy itself
|
||||
# reaches LDAPS over theta-net (sso-manager:636) without this host mapping.
|
||||
# Prefer an internal-only hostname (set CFG_LDAPS_HOST in setup.env / ldapsHost
|
||||
# in sso-secrets.js) and do NOT forward 636 to the public internet.
|
||||
- "${LDAPS_PORT:-636}:636"
|
||||
# Plain LDAP (389) is NOT mapped — direct-LDAP clients should use LDAPS.
|
||||
# LDAPS (636) + plain LDAP (389) for direct-LDAP clients AND for the stack
|
||||
# host's OWN enrollment: setup.sh / ldap-client configure the host's sssd
|
||||
# against ldap://localhost and ldaps://localhost, and the LDAP server is
|
||||
# co-located on this host, so BOTH ports must be reachable from the host
|
||||
# over loopback — not only over the docker network. Bind 0.0.0.0 (default)
|
||||
# so LAN clients can use the host's local IP too; set LDAP_BIND and/or
|
||||
# LDAPS_BIND=127.0.0.1 to lock either to the host only. Prefer an internal
|
||||
# hostname (CFG_LDAPS_HOST) and do NOT forward 389/636 to the public internet.
|
||||
- "${LDAP_BIND:-0.0.0.0}:${LDAP_PORT:-389}:389"
|
||||
- "${LDAPS_BIND:-0.0.0.0}:${LDAPS_PORT:-636}:636"
|
||||
environment:
|
||||
# Config (LDAP, OAuth, SMTP, ...) is loaded by @simpleworkjs/conf from
|
||||
# ./config/sso-secrets.js (see volumes), then @simpleworkjs/bao-conf
|
||||
|
||||
+1
-1
Submodule ldap-client updated: ebaac181bc...68fcdb53bd
Reference in New Issue
Block a user