Compare commits

..

6 Commits

Author SHA1 Message Date
wmantly 3babf18fe4 Merge pull request #96 from theta42/release/v1.3.1
Release 1.3.1
2026-07-23 03:29:01 -04:00
wmantly d78f1dfabf Release 1.3.1
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 03:24:23 -04:00
wmantly c3c206b830 Merge pull request #95 from theta42/docs/directory-doc
Surface the Directory documentation — register in-app and link
2026-07-23 03:20:05 -04:00
wmantly 0a21dce0d7 docs: surface the Directory doc — register in-app, link from UI and site
docs/directory.md existed but was orphaned: not in the /docs registry,
not linked anywhere. Now:

- registered as /docs/directory ("Directory & Inventory")
- help icon on the Directory page header links to it (same pattern as
  users/groups/profile pages)
- linked from the docs site index feature list
- extended with the shared slug conventions (site_<name>, host_<hostname>),
  the automatic registration story (theta-env stack seeding, ldap-client
  Linux host enrollment), and the admin + read-only API surface (the
  read-only graph routes live at /api/discovery, not /api/directory).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 03:17:50 -04:00
wmantly 5940880d9b Merge pull request #94 from theta42/docs/ldap-not-legacy
docs: direct LDAP binds are first-class, not "legacy"
2026-07-23 02:57:20 -04:00
wmantly 17fcf2fed0 docs: direct LDAP binds are first-class, not "legacy"
Linux hosts are a primary consumer of the directory — PAM/SSSD login,
LDAP-backed sudo rules (sudoRole), and SSH public keys (openssh-lpk) —
which is exactly what the custom schemas exist for. Describe LDAPS /
StartTLS consumers as "Linux hosts and LDAP-native apps" instead of
"legacy apps" across README, DEPLOYMENT, docs, and the Dockerfile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 02:55:11 -04:00
11 changed files with 68 additions and 15 deletions
+8
View File
@@ -4,6 +4,14 @@ All notable changes to this project are documented here. Format loosely
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions
correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`.
## [1.3.1] - 2026-07-23
### Added
- The Directory documentation (`docs/directory.md`) is now surfaced: registered in-app at `/docs/directory` ("Directory & Inventory"), help-linked from the Directory page header, and linked from the docs-site index. Extended with the shared slug conventions (`site_<name>`, `host_<hostname>` — as used by ldap-client and the theta-env seed), the automatic-registration story (theta-env stack seeding, ldap-client Linux host enrollment), and the API surface (admin at `/api/directory-admin`, read-only graph at `/api/discovery`).
### Changed
- Direct LDAP binds are described as first-class, not "legacy", across README, DEPLOYMENT.md, docs, and the Dockerfile: Linux hosts are a primary consumer of the directory (PAM/SSSD login, LDAP-backed `sudo` via `sudoRole`, SSH public keys via openssh-lpk) — exactly what the custom schemas exist for.
## [1.3.0] - 2026-07-23
### Added
+3 -3
View File
@@ -120,7 +120,7 @@ bare-metal / advanced standalone use; most deployments should use the file.
- Health check: `http://localhost:3001/health``{"status":"ok"}`
- OIDC discovery: `http://localhost:3001/.well-known/openid-configuration`
- LDAP (internal, app↔slapd): `ldap://localhost:389` (not mapped to the host)
- LDAPS (for legacy apps / direct binds): `ldaps://<host>:636` (TLS)
- LDAPS (direct binds: Linux hosts, LDAP-native apps): `ldaps://<host>:636` (TLS)
### API tokens (personal access tokens)
@@ -483,8 +483,8 @@ netstat -tlnp | grep 389
2. **Use LDAPS / StartTLS** for any LDAP connection that crosses the network. The
bundled slapd listens on `ldaps:///` (636, TLS) and `ldap:///` (389, plain +
StartTLS); port 389 is not mapped to the host by default so LAN clients can't
bind in cleartext. Direct-LDAP apps (legacy services, `theta42/proxy`) should
use `ldaps://…:636` or StartTLS.
bind in cleartext. Direct-LDAP consumers (Linux hosts, LDAP-native apps,
`theta42/proxy`) should use `ldaps://…:636` or StartTLS.
3. **Persist `JWT_SECRET`** — if the Docker image auto-generates one and you don't
set `JWT_SECRET`, issued tokens invalidate on container recreation.
4. **Don't expose the UI's HTTP port to the internet** — terminate TLS at a front
+2 -1
View File
@@ -131,7 +131,8 @@ COPY ops/schema/openssh-lpk.schema /etc/openldap/schema/openssh-lpk.schema
# 3001: SSO Manager web interface (HTTP — terminate TLS at the front proxy)
# 389: LDAP (plain + StartTLS) — used internally by the app; map to host only
# if you want LAN clients to bind without TLS (not recommended).
# 636: LDAPS — for legacy apps / direct LDAP binds over the network (TLS)
# 636: LDAPS — direct LDAP binds over the network (TLS): Linux host auth
# (PAM/SSSD, sudo, SSH keys) and LDAP-native apps
EXPOSE 3001 389 636
# Health check
+5 -4
View File
@@ -41,9 +41,10 @@ phone-home, no hosted control plane, and no per-user pricing.
- **Web management UI** — manage users, groups, and OAuth clients from a
browser; invite and password-reset flows over email; user self-service for
profile and API tokens.
- **LDAPS for legacy apps** — apps that bind LDAP directly (Gitea, Emby, and
anything else that speaks LDAP) use LDAPS (636) or StartTLS against the same
directory, so you don't maintain a second user database for them.
- **Direct LDAP binds** — Linux hosts (PAM/SSSD login, LDAP-backed `sudo`
rules, SSH public keys via openssh-lpk) and LDAP-native apps (Gitea, Emby,
and anything else that speaks LDAP) use LDAPS (636) or StartTLS against the
same directory, so you don't maintain a second user database for them.
- **Personal access tokens** — any user can mint a long-lived bearer token to
drive the management API from scripts or CI, scoped to their own permissions.
- **All-in-one Docker image** — app + OpenLDAP + Redis in one container, or run
@@ -151,7 +152,7 @@ details, including env var overrides (`LDAP_BASE_DN`, `SKIP_LDAP`, ...), in
┌────────────────────────┐
│ OpenLDAP (slapd) │
│ - users / groups │
│ - LDAPS :636 │─── legacy apps bind directly
│ - LDAPS :636 │─── Linux hosts + LDAP apps bind directly
│ - StartTLS :389 │
└────────────────────────┘
```
+38
View File
@@ -57,3 +57,41 @@ Resources carry a flexible `metadata` JSON object that can store essential conte
## Navigating the UI
The Directory Management interface provides a **Tree View** toggle that visually nests your resources, making it easy to comprehend your network topography at a glance. You can also filter, search, and sort your entire infrastructure inventory. From the tree view, you can click the green `+` icon next to any resource to instantly add a child resource beneath it.
## Slug conventions
Slugs are the stable identifiers automation keys off, so the tooling around the SSO Manager follows a shared convention:
- **Sites**: `site_<name>` — e.g. `site_local`, `site_us-east`
- **Hosts**: `host_<hostname>` — e.g. `host_pve1`, `host_web01`
- **Services/apps**: a plain slug or `app_<name>` — e.g. `sso-manager`, `app_emby`
The auto-created LDAP groups derive from the slug (`<slug>_access` / `<slug>_admin`), so keep slugs stable once access groups are in use.
## Automatic registration
You don't have to build the graph by hand — the theta42 tooling registers itself:
### The stack itself (theta-env)
[theta-env](https://github.com/theta42/theta-env)'s `./setup.sh` seeds the directory on every run with the stack it deploys:
- a **site** (name from `CFG_SITE_NAME` in `setup.env`, default `local` → slug `site_local`) marked as the current site
- the **host** the stack runs on (`host_<hostname>`), with IP, MAC address, OS, and kernel collected from the machine
- the **services** it composes — SSO Manager, Proxy (management UI), OpenLDAP Directory (the LDAPS endpoint Linux hosts and LDAP-native apps bind to), and OpenResty Edge (the 80/443 data plane) — each with its address, internal port, and git repo
- the proxy's auto-registered **OAuth client**, linked under its service
The seed is idempotent and non-destructive: a resource whose slug already exists is considered operator-owned — the seed only fills in metadata fields you haven't set, and never overwrites your values.
### Linux hosts (ldap-client)
The `ldap-client` join script enrolls a Debian/Ubuntu machine for LDAP login (SSSD/PAM), LDAP-backed `sudo`, and SSH keys from the directory — and, when given an SSO API token, registers the machine as a `host_<hostname>` resource with its IP, MAC, OS, and kernel, parented to the site named by its configured location.
## API
All of the above uses the same admin API the UI does (group `app_sso_directory_admin` or `app_sso_admin`):
- `GET/POST /api/directory-admin/resources`, `PUT/DELETE /api/directory-admin/resources/:id`
- `GET/POST/DELETE /api/directory-admin/edges` — parent/child links (`hosts`, `oauth` relations)
- `GET/POST/DELETE /api/directory-admin/groups` — resource ↔ LDAP group links
- Read-only graph views (any authenticated user): `GET /api/discovery/resources`, `/api/discovery/resources/:slug`, `/api/discovery/graph`, `/api/discovery/me`
+4 -2
View File
@@ -52,11 +52,13 @@ backend, that's the niche.
- **Web management UI** — users, groups, and OAuth clients from a browser;
invite and password-reset flows over email; self-service profile + API
tokens.
- **LDAPS for legacy apps** — anything that binds LDAP directly (Gitea,
Emby, …) uses LDAPS/StartTLS against the same directory.
- **Direct LDAP binds** — anything that binds LDAP directly (Linux hosts
via PAM/SSSD, Gitea, Emby, …) uses LDAPS/StartTLS against the same
directory.
- **All-in-one Docker image** — app + OpenLDAP + Redis in one container, or
run the pieces separately via `app_*` env config.
- **Geo-Location Scaling** — built-in support for N-Way Multi-Master OpenLDAP [replication](replication.html) across physical sites.
- **[Directory & Inventory](directory.html)** — map sites, hosts, and services as a graph with rich metadata (IP/MAC, OS/kernel, ports, git repos), auto-provisioned access groups, and automatic registration from theta-env and ldap-client.
## Get it
+3 -2
View File
@@ -14,8 +14,9 @@ description: SSO Manager's bundled OpenLDAP directory — schema, service accoun
SSO Manager runs an OpenLDAP directory holding your users and groups. The app
authenticates against it over `localhost:389` (inside the all-in-one container)
and exposes **LDAPS** (`ldaps://…:636`, TLS) for legacy apps that bind LDAP
directly — Gitea, Emby, the theta42/proxy, etc.
and exposes **LDAPS** (`ldaps://…:636`, TLS) for anything that binds LDAP
directly — Linux hosts (PAM/SSSD, sudo rules, SSH keys), Gitea, Emby, the
theta42/proxy, etc.
## Directory layout
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "t42-sso-manager",
"version": "1.3.0",
"version": "1.3.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "t42-sso-manager",
"version": "1.3.0",
"version": "1.3.1",
"license": "MIT",
"dependencies": {
"@fortawesome/fontawesome-free": "^7.3.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "t42-sso-manager",
"version": "1.3.0",
"version": "1.3.1",
"description": "A very simple LDAP management and SSO system",
"author": [
{
+1
View File
@@ -33,6 +33,7 @@ const DOCS = {
accounts: {title: 'Accounts, Groups & Managers', file: path.join(__dirname, '../../docs/concepts-accounts.md')},
'oauth-apps': {title: 'Connecting Apps (SSO)', file: path.join(__dirname, '../../docs/concepts-oauth-apps.md')},
'api-tokens': {title: 'API Tokens', file: path.join(__dirname, '../../docs/concepts-api-tokens.md')},
directory: {title: 'Directory & Inventory', file: path.join(__dirname, '../../docs/directory.md')},
overview: {title: 'Overview', file: path.join(__dirname, '../../README.md')},
changelog: {title: 'Changelog', file: path.join(__dirname, '../../CHANGELOG.md')},
+1
View File
@@ -29,6 +29,7 @@
<div class="card-header actionMessage" style="display:none"></div>
<div class="p-3 pb-0 text-muted small border-bottom">
<i class="fa-solid fa-circle-info"></i> Manage infrastructure, services, and their relationships.
<a href="/docs/directory" class="text-reset float-end" title="Help"><i class="fa-solid fa-circle-question"></i></a>
</div>
<div class="table-responsive">
<table class="card-body table table-striped mb-0">