Commit Graph

13 Commits

Author SHA1 Message Date
wmantly ab9d9301f0 Fix: theta-jump OAuth client had no parent in the directory
seedDirectory() only ever linked ONE OAuth client -- whatever id was
passed in, which was always the proxy's (resolvedClientId). jump-host's
own OAuth client (minted by provisionJumpHost) was never passed through,
so it was created but never got a ResourceEdge to the "SSH Jump Host"
service resource -- it just showed up in the Directory with no parent.

provisionJumpHost now returns the jump client's id (looking it up even
on the "already configured" early-return path, so existing deployments
self-heal on the next setup.sh run instead of needing this fixed only for
fresh installs), and seedDirectory takes it as a third argument, linking
it under the jump-host service the same way the proxy's client is linked
under the proxy service.

Also fixed live on the affected deployment via the directory-admin API
(created the missing edge directly) rather than waiting for a rebuild.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 12:53:51 -04:00
wmantly f90d319eeb Fix jump-host SSH login: point it at ldaps://, not ldap://
jump-secrets.js's ldap.url was 'ldap://sso-manager:389' with tlsOptions
set. ldapts treats a non-empty tlsOptions as "use implicit TLS" regardless
of URL scheme, so every LDAP connection from jump-host opened a raw TLS
handshake against sso-manager's plaintext-LDAP port — slapd dropped the
connection before any LDAP message parsed (visible in slapd.log as
"connection lost" right after ACCEPT, no BIND ever logged). Every SSH
login failed with a generic "Permission denied" for any account, any
password — indistinguishable from a wrong credential.

Root-caused by building a local theta-env stack, restarting jump-host
with edited config, and calling userLdap.getUser/checkPassword directly
inside the container: got "Client network socket disconnected before
secure TLS connection was established" instead of a vague auth failure.
Switching to ldaps://sso-manager:636 (already exposed by the same
container, already what tlsOptions was meant for) fixes it — verified
getUser/checkPassword succeed and a real SSH login authenticates.

Companion defensive fix: simpleworkjs/ldap#1 (rejects this exact
ldap://+tlsOptions combination going forward, for proxy/sso too).

Existing deployments must edit their own ./config/jump-secrets.js (this
template only affects fresh bootstraps) — see the PR description.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 20:10:58 -04:00
wmantly 67374dc914 feat: provision jump-host web-UI SSO login (OIDC client); bump to v1.1.0
The jump host's web UI now authenticates via OIDC + a local admin
(jump-host v1.1.0). Wire that in the bundle:

- bootstrap mints a dedicated 'theta-jump' OAuth client (redirect
  https://<JUMP_HOST>/api/auth/oidc/callback) and writes a full oidc
  block + generated local admin password into config/jump-secrets.js,
  mirroring the proxy's OIDC provisioning
- an existing pre-OIDC jump-secrets.js (API token but no OIDC client) is
  regenerated so upgraders get SSO login
- bump jump-host submodule v1.0.x -> v1.1.0

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 21:01:36 -04:00
wmantly 1f8f4c70be feat: optional SSH jump host component (theta42/jump-host)
Adds jump-host as a third, opt-in submodule, wired behind
CFG_JUMP_HOST_ENABLED (default off — existing installs unaffected):

- .gitmodules + jump-host submodule pinned to v1.0.0
- setup.sh: resolves the enable flag early, adds jump-host to the
  submodule tag-update loop and activates the `jump-host` compose
  profile when enabled; builds/starts the service after the proxy,
  waits for its /health, and registers its web UI as a proxy Host;
  passes CFG_JUMP_HOST_ENABLED/CFG_JUMP_HOST to the bootstrap
- docker-compose.yml: jump-host service with profiles:["jump-host"],
  depends_on sso-manager healthy, ports 2222 (SSH) + 3002 (web),
  ./config:ro + jump-data volume
- bootstrap.js: when enabled, mints a directory API token and writes
  ./config/jump-secrets.js (binds as cn=admin so it can write the
  sshPublicKey attribute for key injection), and seeds a directory
  service entry for the jump host. Warn-only, idempotent.
- setup.env.example: CFG_JUMP_HOST_ENABLED / CFG_JUMP_HOST / JUMP_SSH_PORT

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 15:55:40 -04:00
wmantly 3e85e37b63 feat: site name config, host facts, and service ports/repos in the directory seed
- CFG_SITE_NAME in setup.env (below CFG_DOMAIN, default "local") names
  the directory site; slug site_<name> matches ldap-client's parentSlug
  convention so joined Linux hosts land under the same site. Wired
  through sso-secrets.js stack.siteName.
- setup.sh collects host facts ON THE HOST (hostname, IP, default-route
  MAC, OS pretty-name, kernel — same collection as ldap-client/index.sh)
  and passes them into the bootstrap exec env; the stack host is now
  registered as host_<hostname> with that metadata (subType linux).
- Services carry their internal port and git repo in metadata
  (sso-manager 3001, proxy 3000, openldap 389/ext 636, openresty 443),
  using the metadata keys the directory UI natively displays.
- ensure() now adopts resources from the earlier seed layout (alt slugs
  'stack-host' / domain-slug site) and back-fills missing seed metadata
  via a metadata-only PUT — operator-set values are never overwritten.

Verified against a live app: old-layout resources are adopted and
back-filled (no duplicates), fresh seed creates the full graph, and a
second pass changes nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 03:18:20 -04:00
wmantly faf67d8ffc docs: direct LDAP binds are first-class, not "legacy"
Linux hosts authenticate against the directory (PAM/SSSD, sudoRole,
sshPublicKey) — reframe the OpenLDAP seed comment and changelog entry
accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 02:56:17 -04:00
wmantly 4f61eeb1a7 feat: also seed OpenLDAP and OpenResty services in the directory
The stack runs two more real services than the last seed captured:

- OpenLDAP: independently consumed via direct LDAPS binds (the SSO's
  /integrations page advertises it). Seeded with the ldaps:// endpoint,
  honoring ldap.ldapsHost when the operator set one.
- OpenResty: the proxy container's data plane (80/443) that every
  hostname in the stack actually flows through — distinct from the
  'proxy' entry, which is the node management UI. Seeded with a
  wildcard https://*.<domain> address (same wildcard convention the
  proxy's Host records use).

Both use metadata.subType so the directory UI badges them as
service (openldap) / service (openresty). Same idempotency: existing
slugs are operator-owned and untouched. Re-verified against a live app:
pass 1 creates all six resources + oauth edge, pass 2 changes nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 02:44:19 -04:00
wmantly 1c96c75118 feat: seed the SSO directory with the stack's own resources at bootstrap
The Directory page started empty even though setup.sh knows exactly what
it deployed. The bootstrap now seeds (via /api/directory-admin, as the
logged-in admin): a site from the configured domain, a "Stack host", the
SSO Manager + Proxy services with their public URLs in metadata, and
links the proxy's auto-registered OAuth client under its service.

Idempotent: resources whose slug already exists are operator-owned and
never touched. A seed failure only warns — never fails a bring-up (e.g.
against an older sso-manager image without /api/directory-admin).

Verified against a live app (sso-manager-node test stack): first pass
creates site/host/2 services + oauth edge; second pass changes nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 02:39:41 -04:00
wmantly e46768bb68 chore(release): public-release readiness fixes for 1.1.18
- CHANGELOG.md now embeds full app-level release notes for submodule bumps.
- .env.example uses explicit CHANGE-ME placeholders instead of realistic-looking defaults.
- config.example comments describe the actual CONF_SECRETS mechanism.
- setup.sh summary no longer prints generated passwords to stdout.
- bootstrap.js fails hard instead of falling back to weak default passwords.

Note: submodule pins will be updated to v1.1.16 after the app PRs merge.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-18 22:11:13 -04:00
wmantly b5f24d40fc Persist Redis + config in bind-mounted ./config/ (no .env); add backup/restore (#8)
Part A — lossless upgrades:
- Persist both bundled Redis stores via AOF+RDB on named volumes (sso-data,
  proxy-data) so OAuth clients, Host records, perms, DNS creds, and auto-ssl
  Let's Encrypt certs survive rebuilds.
- setup.sh: backup_before_rebuild() snapshots ./config/ + LDAP (slapcat) +
  both Redis (BGSAVE + compose cp) to ./backups/<ts>/ before each rebuild,
  keeps last BACKUP_KEEP (default 5). First run is a no-op.
- Restore runbook (README + docs): full / Redis-only / LDAP-only, with the
  AOF-vs-RDB note (delete the AOF before restoring an RDB).

Part B — eliminate .env / proxy.env:
- All config + secrets live in bind-mounted ./config/ (gitignored), read by each
  app's @simpleworkjs/conf from a symlinked secrets.js. Compose passes only
  NODE_ENV + NODE_PORT (no app_* env, which would override secrets.js).
- ./config/sso-secrets.js: app secrets + orchestrator-only stack/bootstrap/
  serviceAccountPass keys (app ignores the ones it doesn't use).
- ./config/proxy-secrets.js: oidc (clientId/clientSecret filled in by the
  bootstrap), ldap (bind creds), auth (admin groups/users).
- setup.sh ensure_config(): generates ./config/ with random secrets on first
  run (then exits for editing); one-time migration from .env/proxy.env
  preserving existing secrets (LDAP admin pass, JWT, OAuth client, service
  pass) so a running deployment keeps its directory + tokens + OAuth client.
- bootstrap/bootstrap.js: reads /config/*.js (not process.env), registers the
  proxy as an OIDC client, and writes the SSO-generated client id+secret back
  into ./config/proxy-secrets.js (sso mounts ./config RW, proxy RO).
- config.example/ holds committed annotated templates for manual reference.
- .gitignore: add config/, backups/, *.rdb, *.ldif.

Bump both gitlinks to the merged submodule tips:
- sso-manager-node -> 6920a9f (PR #34)
- proxy -> 8e78604 (PR #118)

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-12 13:16:17 -04:00
wmantly 5ec951a683 Fixed issues with password 2026-07-11 18:46:20 -04:00
wmantly f7edc5b672 Fixed env template 2026-07-11 18:42:05 -04:00
wmantly 9fb240ff45 theta-env: unified SSO Manager + Proxy stack with one-command setup
Composes theta42/sso-manager-node and theta42/proxy (as git submodules) on a
single Docker network and automates first-run wiring.

- docker-compose.yml: sso-manager (build ./sso-manager-node/Dockerfile.openldap)
  + proxy (build ./proxy/Dockerfile) on theta-net; SSO UI + mgmt port bound to
  localhost, LDAPS published, proxy 80/443/4443 published.
- setup.sh: idempotent one-command bring-up — validates .env, starts SSO, runs
  the bootstrap, writes ./proxy.env, starts the proxy, prints admin login.
- bootstrap/bootstrap.js: runs inside the sso-manager container (self-contained,
  Node built-ins + fetch only) — creates the LDAP service account, first admin
  (+ app_sso_admin/app_sso_oauth_admin membership), registers the proxy as an
  OIDC client via the SSO HTTP API, emits CLIENT_ID/CLIENT_SECRET.
- .env.example: all tunables (LDAP_BASE_DN, LDAP_ADMIN_PASS, JWT_SECRET,
  SSO_HOST, PROXY_HOST, BOOTSTRAP_ADMIN_*, LDAP_SERVICE_PASS, SMTP_*, ports).
- README.md + docs/ (Jekyll site for GitHub Pages): quickstart, architecture,
  standalone usage.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-11 17:04:36 -04:00