Compare commits

...

9 Commits

Author SHA1 Message Date
wmantly 3943ed02c5 Merge pull request #75 from theta42/chore/bump-sso-manager-node-v1.2.0
chore: bump sso-manager-node to v1.2.0
2026-07-21 00:18:27 -04:00
wmantly 7f43eee36e chore: bump sso-manager-node to v1.2.0 2026-07-21 00:17:52 -04:00
wmantly 19ea7e012a Merge pull request #74 from theta42/feature/multi-master-ldap-config
feat: Add LDAP replication configuration options
2026-07-21 00:14:25 -04:00
wmantly 94b357e915 docs: Add Multi-Site Support to features list 2026-07-21 00:10:26 -04:00
wmantly f5d8cdd09d feat: add LDAP replication configuration options 2026-07-20 23:56:16 -04:00
wmantly 96b3aec5eb Merge pull request #73 from theta42/release-1.1.20
Release 1.1.20: bump proxy submodule to v1.1.17
2026-07-20 00:31:55 -04:00
wmantly 5aff5349a8 Release 1.1.20: bump proxy submodule to v1.1.17
Pins the proxy submodule to v1.1.17 (wildcard sibling-parent fix: an
existing single-label subdomain host can now be attached to a wildcard
cert added afterward). Embeds proxy's v1.1.17 changelog in the theta-env
release notes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 00:31:12 -04:00
wmantly 3b0f8f1f9a chore: bump sso-manager-node submodule to v1.1.17 (#72)
Picks up the configurable LDAPS hostname feature and docs.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-19 01:14:32 -04:00
wmantly 28016376ad feat: propagate CFG_LDAPS_HOST through setup and document LDAPS networking (#71)
Pass optional CFG_LDAPS_HOST from setup.env through setup.sh into the
generated ./config/sso-secrets.js as ldap.ldapsHost. This lets operators
advertise an internal-only LDAPS hostname (e.g. ldap.internal.example.com
or sso-manager) on the SSO /integrations page instead of the public
OAuth issuer, avoiding a public 636 port forward.

- setup.env.example: add CFG_LDAPS_HOST
- setup.sh: read/forward CFG_LDAPS_HOST into sso-secrets.js
- config.example/sso-secrets.js.example: document ldapsHost/ldapsPort
- .env.example: add LDAPS_HOST for legacy .env migrations
- docker-compose.yml: comment warning against public 636 forwarding
- README.md: explain CFG_LDAPS_HOST recommendation
- CHANGELOG.md + bump version to 1.1.19

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-19 01:14:03 -04:00
10 changed files with 84 additions and 5 deletions
+6
View File
@@ -77,3 +77,9 @@ MGMT_BIND=0.0.0.0
# (sso-manager inside the docker net uses the service name, which is in the # (sso-manager inside the docker net uses the service name, which is in the
# cert's SAN, so the default is usually fine). # cert's SAN, so the default is usually fine).
LDAP_CERT_CN= LDAP_CERT_CN=
# ── Optional: LDAPS hostname shown on the SSO /integrations page ────────────────
# Leave blank to derive from the public SSO host (SSO_HOST). Set an internal-only
# name like 'ldap.internal.example.com' or 'sso-manager' so direct-LDAP clients
# don't need a public 636 port forward. See docs/ldap.md for network layouts.
LDAPS_HOST=
+36 -1
View File
@@ -10,6 +10,41 @@ for what changed inside the apps it composes.
## [Unreleased] ## [Unreleased]
## [1.1.20] - 2026-07-20
### Bumped
- proxy -> [v1.1.17](https://github.com/theta42/proxy/releases/tag/v1.1.17)
proxy:
### Fixed
- An existing single-label subdomain host (e.g. `sso.nl.wgnode.com`) could not be attached to a wildcard cert added later (e.g. `*.nl.wgnode.com`): `Host.lookUpWildcardParent()` only checked the wildcard-as-child position (the wildcard's own base domain) and missed the far more common wildcard-as-sibling case, so the edit form's "Parent Wildcard" option stayed permanently greyed out. It now checks both positions, and a regression test covers the sibling case.
## [1.1.19] - 2026-07-18
### Bumped
- sso-manager-node -> [v1.1.17](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.17)
sso-manager-node:
### Added
- `conf.ldap.ldapsHost` and `conf.ldap.ldapsPort` config options for advertising a separate, internal-only LDAPS hostname on the `/integrations` page. Falls back to the public OAuth issuer host when unset.
- Contextual help panel on `/integrations` → LDAP explaining why LDAPS needs a hostname, why port 636 should not be forwarded publicly, and the recommended internal-DNS / Docker-internal alternatives.
- Tests for the `/integrations` route's LDAPS URL derivation and `ldapsHost` override.
### Changed
- `nodejs/package.json` / `package-lock.json` version bumped to `1.1.17`.
- `routes/index.js` now derives the displayed LDAPS URL from `conf.ldap.ldapsHost`/`ldapsPort` with fallback to the OAuth issuer host.
- `docs/configuration.md`, `docs/ldap.md`, `DEPLOYMENT.md`, and `secrets.js.example` document the new `ldapsHost`/`ldapsPort` options and recommended network layouts.
### theta-env own changes
- `setup.env.example` adds optional `CFG_LDAPS_HOST` for the internal LDAPS hostname.
- `setup.sh` passes `CFG_LDAPS_HOST` into the generated `./config/sso-secrets.js` as `ldap.ldapsHost`.
- `config.example/sso-secrets.js.example` documents `ldap.ldapsHost` / `ldap.ldapsPort`.
- `.env.example` adds `LDAPS_HOST` for legacy `.env` migrations.
- `docker-compose.yml` comments warn against forwarding 636 to the public internet.
- `README.md` explains the `CFG_LDAPS_HOST` recommendation in the port-forwarding section.
## [1.1.18] - 2026-07-18 ## [1.1.18] - 2026-07-18
### Bumped ### Bumped
@@ -257,7 +292,7 @@ First tagged release. Establishes the `vX.Y.Z` tag convention going forward.
- proxy -> [v1.1.0](https://github.com/theta42/proxy/releases/tag/v1.1.0) - proxy -> [v1.1.0](https://github.com/theta42/proxy/releases/tag/v1.1.0)
- sso-manager-node -> [v1.1.0](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.0) - sso-manager-node -> [v1.1.0](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.0)
[Unreleased]: https://github.com/theta42/theta-env/compare/v1.1.18...HEAD [Unreleased]: https://github.com/theta42/theta-env/compare/v1.1.20...HEAD
[1.1.17]: https://github.com/theta42/theta-env/compare/v1.1.16...v1.1.17 [1.1.17]: https://github.com/theta42/theta-env/compare/v1.1.16...v1.1.17
[1.1.16]: https://github.com/theta42/theta-env/compare/v1.1.15...v1.1.16 [1.1.16]: https://github.com/theta42/theta-env/compare/v1.1.15...v1.1.16
[1.1.15]: https://github.com/theta42/theta-env/compare/v1.1.14...v1.1.15 [1.1.15]: https://github.com/theta42/theta-env/compare/v1.1.14...v1.1.15
+6
View File
@@ -60,6 +60,8 @@ It is **both** an OIDC client of the SSO (for login) **and** a direct LDAP
client (for user lookups). Legacy apps can still bind to LDAPS on the SSO client (for user lookups). Legacy apps can still bind to LDAPS on the SSO
directly. directly.
- **Multi-Site Support (Geo-Location Scaling):** Built-in support for N-Way Multi-Master LDAP replication, allowing you to deploy the stack across multiple physical locations for HA and low latency.
--- ---
## Before you begin ## Before you begin
@@ -120,6 +122,10 @@ Optional extra ports (only if you need them):
- **636** (LDAPS) — only if a legacy app on another machine binds to LDAP - **636** (LDAPS) — only if a legacy app on another machine binds to LDAP
directly over the network. The proxy itself reaches LDAP over the internal directly over the network. The proxy itself reaches LDAP over the internal
Docker network, so you do **not** need to expose 636 for the stack to work. 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
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.
### 4. Docker + Docker Compose ### 4. Docker + Docker Compose
+3
View File
@@ -17,6 +17,9 @@ module.exports = {
bindPassword: 'CHANGE-ME', // slapd root + app bind password bindPassword: 'CHANGE-ME', // slapd root + app bind password
userBase: 'ou=people,dc=example,dc=com', userBase: 'ou=people,dc=example,dc=com',
groupBase: 'ou=groups,dc=example,dc=com', groupBase: 'ou=groups,dc=example,dc=com',
// ldapsHost: 'ldap.internal.example.com', // optional: internal-only hostname
// shown on /integrations for direct LDAPS binds. Empty -> derive from issuer.
// ldapsPort: 636,
}, },
smtp: { // optional; leave host '' to skip smtp: { // optional; leave host '' to skip
host: '', port: 587, secure: false, host: '', port: 587, secure: false,
+4
View File
@@ -46,6 +46,8 @@ services:
- "${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 for EXTERNAL direct-LDAP clients (legacy apps). The proxy itself
# reaches LDAPS over theta-net (sso-manager:636) without this host mapping. # 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" - "${LDAPS_PORT:-636}:636"
# Plain LDAP (389) is NOT mapped — direct-LDAP clients should use LDAPS. # Plain LDAP (389) is NOT mapped — direct-LDAP clients should use LDAPS.
environment: environment:
@@ -54,6 +56,8 @@ services:
# reads that are not part of its conf tree. # reads that are not part of its conf tree.
- NODE_ENV=production - NODE_ENV=production
- NODE_PORT=3001 - NODE_PORT=3001
- LDAP_SERVER_ID=${LDAP_SERVER_ID:-}
- LDAP_REPLICATION_HOSTS=${LDAP_REPLICATION_HOSTS:-}
volumes: volumes:
# Operator-edited SSO secrets (sso-secrets.js). Read-WRITE so the bootstrap # Operator-edited SSO secrets (sso-secrets.js). Read-WRITE so the bootstrap
# can write the generated OAuth client creds into proxy-secrets.js. The # can write the generated OAuth client creds into proxy-secrets.js. The
+1
View File
@@ -44,6 +44,7 @@ snapshots state before every rebuild.
- **LDAPS** for legacy apps that bind directly. - **LDAPS** for legacy apps that bind directly.
- **Self-service API tokens** in both apps' UIs, for scripting/CI without a - **Self-service API tokens** in both apps' UIs, for scripting/CI without a
browser session. browser session.
- **Multi-Site Support (Geo-Location Scaling)** — built-in support for N-Way Multi-Master LDAP replication across physical locations.
## Get it ## Get it
+1 -1
Submodule proxy updated: 289a9587d6...21e295615b
+18
View File
@@ -36,6 +36,12 @@ CFG_DOMAIN=example.com
#CFG_ADMIN_UID=admin # initial SSO admin username #CFG_ADMIN_UID=admin # initial SSO admin username
#CFG_ADMIN_EMAIL=admin@proxy.example.com # defaults to admin@<proxyHost> #CFG_ADMIN_EMAIL=admin@proxy.example.com # defaults to admin@<proxyHost>
#CFG_LDAP_CERT_CN= # LDAP TLS cert CN; empty -> defaults to the domain #CFG_LDAP_CERT_CN= # LDAP TLS cert CN; empty -> defaults to the domain
#
# Hostname advertised on the SSO /integrations page for direct LDAPS binds.
# Leave blank to derive it from the public SSO host (same as oauth.issuer).
# Recommended: set an internal-only name like 'ldap.internal.example.com' or
# 'sso-manager' so clients don't need a public 636 port forward. See docs.
#CFG_LDAPS_HOST=
# Optional SMTP (outbound email from the SSO app). Leave blank to disable: # Optional SMTP (outbound email from the SSO app). Leave blank to disable:
#CFG_SMTP_HOST=smtp.example.com #CFG_SMTP_HOST=smtp.example.com
@@ -53,3 +59,15 @@ CFG_DOMAIN=example.com
# comment for how to actually change it after the account exists). Do NOT set # comment for how to actually change it after the account exists). Do NOT set
# CFG_LDAP_ADMIN_PASS / CFG_JWT_SECRET / CFG_ADMIN_PASS / CFG_SVC_PASS / # CFG_LDAP_ADMIN_PASS / CFG_JWT_SECRET / CFG_ADMIN_PASS / CFG_SVC_PASS /
# CFG_PROXY_ADMIN_PASS here. # CFG_PROXY_ADMIN_PASS here.
# ── Geo-Location Scaling (N-Way Multi-Master LDAP) ───────────────────────────
# If deploying this stack across multiple physical sites to provide local HA
# for directory services, you can enable N-Way Multi-Master OpenLDAP replication.
# This requires assigning a unique ID to each site and listing the LDAPS URLs
# of all OTHER sites in the cluster.
#
# Each site MUST have a unique LDAP_SERVER_ID (e.g. 1, 2, 3).
# LDAP_REPLICATION_HOSTS is a space-separated list of the other sites' LDAP URLs.
# Example for Site 1:
#LDAP_SERVER_ID=1
#LDAP_REPLICATION_HOSTS="ldaps://sso.site2.com:636 ldaps://sso.site3.com:636"
+6
View File
@@ -240,6 +240,8 @@ module.exports = {
bindPassword: $(js_str "$CFG_LDAP_ADMIN_PASS"), bindPassword: $(js_str "$CFG_LDAP_ADMIN_PASS"),
userBase: $(js_str "ou=people,${dn}"), userBase: $(js_str "ou=people,${dn}"),
groupBase: $(js_str "ou=groups,${dn}"), groupBase: $(js_str "ou=groups,${dn}"),
ldapsHost: $(js_str "${CFG_LDAPS_HOST:-}"),
ldapsPort: 636,
}, },
smtp: { smtp: {
host: $(js_str "${CFG_SMTP_HOST:-}"), host: $(js_str "${CFG_SMTP_HOST:-}"),
@@ -355,6 +357,7 @@ ensure_config() {
CFG_ADMIN_UID="${CFG_ADMIN_UID:-}" CFG_ADMIN_UID="${CFG_ADMIN_UID:-}"
CFG_ADMIN_EMAIL="${CFG_ADMIN_EMAIL:-}" CFG_ADMIN_EMAIL="${CFG_ADMIN_EMAIL:-}"
CFG_LDAP_CERT_CN="${CFG_LDAP_CERT_CN:-}" CFG_LDAP_CERT_CN="${CFG_LDAP_CERT_CN:-}"
CFG_LDAPS_HOST="${CFG_LDAPS_HOST:-}"
CFG_CLIENT_ID="${CFG_CLIENT_ID:-}" CFG_CLIENT_ID="${CFG_CLIENT_ID:-}"
CFG_CLIENT_SECRET="${CFG_CLIENT_SECRET:-}" CFG_CLIENT_SECRET="${CFG_CLIENT_SECRET:-}"
CFG_LDAP_ADMIN_PASS="${CFG_LDAP_ADMIN_PASS:-}" CFG_LDAP_ADMIN_PASS="${CFG_LDAP_ADMIN_PASS:-}"
@@ -383,6 +386,8 @@ ensure_config() {
CFG_ADMIN_PASS="${BOOTSTRAP_ADMIN_PASS:-$CFG_ADMIN_PASS}" CFG_ADMIN_PASS="${BOOTSTRAP_ADMIN_PASS:-$CFG_ADMIN_PASS}"
CFG_SVC_PASS="${LDAP_SERVICE_PASS:-$CFG_SVC_PASS}" CFG_SVC_PASS="${LDAP_SERVICE_PASS:-$CFG_SVC_PASS}"
CFG_LDAP_CERT_CN="${LDAP_CERT_CN:-$CFG_LDAP_CERT_CN}" CFG_LDAP_CERT_CN="${LDAP_CERT_CN:-$CFG_LDAP_CERT_CN}"
# .env has no legacy LDAPS_HOST key; this stays as set in setup.env/env.
CFG_LDAPS_HOST="${CFG_LDAPS_HOST:-}"
CFG_SMTP_HOST="${SMTP_HOST:-${CFG_SMTP_HOST:-}}" CFG_SMTP_HOST="${SMTP_HOST:-${CFG_SMTP_HOST:-}}"
CFG_SMTP_PORT="${SMTP_PORT:-${CFG_SMTP_PORT:-}}" CFG_SMTP_PORT="${SMTP_PORT:-${CFG_SMTP_PORT:-}}"
CFG_SMTP_USER="${SMTP_USER:-${CFG_SMTP_USER:-}}" CFG_SMTP_USER="${SMTP_USER:-${CFG_SMTP_USER:-}}"
@@ -416,6 +421,7 @@ ensure_config() {
CFG_ADMIN_UID="${CFG_ADMIN_UID:-admin}" CFG_ADMIN_UID="${CFG_ADMIN_UID:-admin}"
CFG_ADMIN_EMAIL="${CFG_ADMIN_EMAIL:-admin@$CFG_PROXY_HOST}" CFG_ADMIN_EMAIL="${CFG_ADMIN_EMAIL:-admin@$CFG_PROXY_HOST}"
CFG_LDAP_CERT_CN="${CFG_LDAP_CERT_CN:-}" CFG_LDAP_CERT_CN="${CFG_LDAP_CERT_CN:-}"
CFG_LDAPS_HOST="${CFG_LDAPS_HOST:-}"
CFG_CLIENT_ID="${CFG_CLIENT_ID:-}" CFG_CLIENT_ID="${CFG_CLIENT_ID:-}"
CFG_CLIENT_SECRET="${CFG_CLIENT_SECRET:-}" CFG_CLIENT_SECRET="${CFG_CLIENT_SECRET:-}"
# Random secrets (generated fresh unless sourced/migrated above). These do # Random secrets (generated fresh unless sourced/migrated above). These do