Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 28025847d0 |
@@ -8,17 +8,6 @@ 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,15 +129,12 @@ 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).
|
||||||
- **389** (LDAP) + **636** (LDAPS) — direct-LDAP access. The stack host's **own**
|
- **636** (LDAPS) — for direct-LDAP clients on other machines (Linux hosts
|
||||||
enrollment (`setup.sh` → ldap-client) configures its sssd against
|
via PAM/SSSD, LDAP-native apps). The proxy itself reaches LDAP over the
|
||||||
`ldap://localhost:389` / `ldaps://localhost:636`, so both ports are published
|
internal Docker network, so you do **not** need to expose 636 for the stack
|
||||||
to the host by default (bind 0.0.0.0; set `LDAP_BIND`/`LDAPS_BIND=127.0.0.1` to
|
to work.
|
||||||
lock to the host). LAN clients (Linux hosts via PAM/SSSD, LDAP-native apps) can
|
**Do not forward 636 to the public internet.** If you need LAN clients to bind
|
||||||
bind over either; the proxy itself reaches LDAP over the internal Docker
|
LDAP, set `CFG_LDAPS_HOST=ldap.internal.example.com` (or `sso-manager` for
|
||||||
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
+13
-70
@@ -178,88 +178,31 @@ 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 — ensuring service-account shape + password`);
|
log(`Service account ${SVC_DN} exists — resetting password to ./config`);
|
||||||
// Add the auxiliary posixAccount objectClass + required attrs so the entry
|
const r = ldapModify([
|
||||||
// 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 (rp.code !== 0) log(' password reset warning:', rp.stderr.trim());
|
if (r.code !== 0) log(' password reset warning:', r.stderr.trim());
|
||||||
} else {
|
return;
|
||||||
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()}`);
|
|
||||||
}
|
}
|
||||||
// Mark it as a service account (the Users UI's service-account signal).
|
log(`Creating service account ${SVC_DN}`);
|
||||||
const gdn = `cn=app_sso_service_account,ou=groups,${BASE_DN}`;
|
const r = ldapAdd([
|
||||||
const rm = ldapModify([
|
`dn: ${SVC_DN}`,
|
||||||
`dn: ${gdn}`,
|
'objectClass: organizationalRole',
|
||||||
'changetype: modify',
|
'objectClass: simpleSecurityObject',
|
||||||
'add: member',
|
'objectClass: top',
|
||||||
`member: ${SVC_DN}`,
|
'cn: ldapclient',
|
||||||
|
`userPassword: ${pw}`,
|
||||||
'',
|
'',
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
if (rm.code === 0) log(` marked ${SVC_DN} as a service account`);
|
if (r.code !== 0) throw new Error(`ldapadd service account failed: ${r.stderr.trim()}`);
|
||||||
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 ─────────────────────────────────────────────────────
|
||||||
|
|||||||
+6
-10
@@ -52,16 +52,12 @@ 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 (636) + plain LDAP (389) for direct-LDAP clients AND for the stack
|
# LDAPS for EXTERNAL direct-LDAP clients (legacy apps). The proxy itself
|
||||||
# host's OWN enrollment: setup.sh / ldap-client configure the host's sssd
|
# reaches LDAPS over theta-net (sso-manager:636) without this host mapping.
|
||||||
# against ldap://localhost and ldaps://localhost, and the LDAP server is
|
# Prefer an internal-only hostname (set CFG_LDAPS_HOST in setup.env / ldapsHost
|
||||||
# co-located on this host, so BOTH ports must be reachable from the host
|
# in sso-secrets.js) and do NOT forward 636 to the public internet.
|
||||||
# over loopback — not only over the docker network. Bind 0.0.0.0 (default)
|
- "${LDAPS_PORT:-636}:636"
|
||||||
# so LAN clients can use the host's local IP too; set LDAP_BIND and/or
|
# Plain LDAP (389) is NOT mapped — direct-LDAP clients should use LDAPS.
|
||||||
# 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: 68fcdb53bd...ebaac181bc
Reference in New Issue
Block a user