Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f27ce70c5d | |||
| 3354407f04 |
@@ -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))
|
[sso-manager-node](https://github.com/theta42/sso-manager-node/blob/master/CHANGELOG.md))
|
||||||
for what changed inside the apps it composes.
|
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
|
## [v1.38.0] - 2026-08-04
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -129,12 +129,15 @@ see browser warnings.)
|
|||||||
|
|
||||||
Optional extra ports (only if you need them):
|
Optional extra ports (only if you need them):
|
||||||
- **4443** — alternate HTTPS listener (e.g. if 443 is taken by something else).
|
- **4443** — alternate HTTPS listener (e.g. if 443 is taken by something else).
|
||||||
- **636** (LDAPS) — for direct-LDAP clients on other machines (Linux hosts
|
- **389** (LDAP) + **636** (LDAPS) — direct-LDAP access. The stack host's **own**
|
||||||
via PAM/SSSD, LDAP-native apps). The proxy itself reaches LDAP over the
|
enrollment (`setup.sh` → ldap-client) configures its sssd against
|
||||||
internal Docker network, so you do **not** need to expose 636 for the stack
|
`ldap://localhost:389` / `ldaps://localhost:636`, so both ports are published
|
||||||
to work.
|
to the host by default (bind 0.0.0.0; set `LDAP_BIND`/`LDAPS_BIND=127.0.0.1` to
|
||||||
**Do not forward 636 to the public internet.** If you need LAN clients to bind
|
lock to the host). LAN clients (Linux hosts via PAM/SSSD, LDAP-native apps) can
|
||||||
LDAP, set `CFG_LDAPS_HOST=ldap.internal.example.com` (or `sso-manager` for
|
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
|
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.
|
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 ───────────────────────────────────
|
// ── 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() {
|
function ensureServiceAccount() {
|
||||||
const pw = hashPasswordSSHA512(SVC_PASS);
|
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)) {
|
if (entryExists(SVC_DN)) {
|
||||||
log(`Service account ${SVC_DN} exists — resetting password to ./config`);
|
log(`Service account ${SVC_DN} exists — ensuring service-account shape + password`);
|
||||||
const r = ldapModify([
|
// 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}`,
|
`dn: ${SVC_DN}`,
|
||||||
'changetype: modify',
|
'changetype: modify',
|
||||||
'replace: userPassword',
|
'replace: userPassword',
|
||||||
`userPassword: ${pw}`,
|
`userPassword: ${pw}`,
|
||||||
'',
|
'',
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
if (r.code !== 0) log(' password reset warning:', r.stderr.trim());
|
if (rp.code !== 0) log(' password reset warning:', rp.stderr.trim());
|
||||||
return;
|
} 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}`);
|
// Mark it as a service account (the Users UI's service-account signal).
|
||||||
const r = ldapAdd([
|
const gdn = `cn=app_sso_service_account,ou=groups,${BASE_DN}`;
|
||||||
`dn: ${SVC_DN}`,
|
const rm = ldapModify([
|
||||||
'objectClass: organizationalRole',
|
`dn: ${gdn}`,
|
||||||
'objectClass: simpleSecurityObject',
|
'changetype: modify',
|
||||||
'objectClass: top',
|
'add: member',
|
||||||
'cn: ldapclient',
|
`member: ${SVC_DN}`,
|
||||||
`userPassword: ${pw}`,
|
|
||||||
'',
|
'',
|
||||||
].join('\n'));
|
].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 ─────────────────────────────────────────────────────
|
// ── 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
|
# 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>.
|
# lock it to localhost once the proxy fronts it at https://<SSO_HOST>.
|
||||||
- "${SSO_BIND:-0.0.0.0}:${SSO_PORT:-3001}:3001"
|
- "${SSO_BIND:-0.0.0.0}:${SSO_PORT:-3001}:3001"
|
||||||
# LDAPS for EXTERNAL direct-LDAP clients (legacy apps). The proxy itself
|
# LDAPS (636) + plain LDAP (389) for direct-LDAP clients AND for the stack
|
||||||
# reaches LDAPS over theta-net (sso-manager:636) without this host mapping.
|
# host's OWN enrollment: setup.sh / ldap-client configure the host's sssd
|
||||||
# Prefer an internal-only hostname (set CFG_LDAPS_HOST in setup.env / ldapsHost
|
# against ldap://localhost and ldaps://localhost, and the LDAP server is
|
||||||
# in sso-secrets.js) and do NOT forward 636 to the public internet.
|
# co-located on this host, so BOTH ports must be reachable from the host
|
||||||
- "${LDAPS_PORT:-636}:636"
|
# over loopback — not only over the docker network. Bind 0.0.0.0 (default)
|
||||||
# Plain LDAP (389) is NOT mapped — direct-LDAP clients should use LDAPS.
|
# 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:
|
environment:
|
||||||
# Config (LDAP, OAuth, SMTP, ...) is loaded by @simpleworkjs/conf from
|
# Config (LDAP, OAuth, SMTP, ...) is loaded by @simpleworkjs/conf from
|
||||||
# ./config/sso-secrets.js (see volumes), then @simpleworkjs/bao-conf
|
# ./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