Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3babf18fe4 | |||
| d78f1dfabf | |||
| c3c206b830 | |||
| 0a21dce0d7 | |||
| 5940880d9b | |||
| 17fcf2fed0 |
@@ -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
|
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`.
|
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
|
## [1.3.0] - 2026-07-23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
+3
-3
@@ -120,7 +120,7 @@ bare-metal / advanced standalone use; most deployments should use the file.
|
|||||||
- Health check: `http://localhost:3001/health` → `{"status":"ok"}`
|
- Health check: `http://localhost:3001/health` → `{"status":"ok"}`
|
||||||
- OIDC discovery: `http://localhost:3001/.well-known/openid-configuration`
|
- OIDC discovery: `http://localhost:3001/.well-known/openid-configuration`
|
||||||
- LDAP (internal, app↔slapd): `ldap://localhost:389` (not mapped to the host)
|
- 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)
|
### 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
|
2. **Use LDAPS / StartTLS** for any LDAP connection that crosses the network. The
|
||||||
bundled slapd listens on `ldaps:///` (636, TLS) and `ldap:///` (389, plain +
|
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
|
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
|
bind in cleartext. Direct-LDAP consumers (Linux hosts, LDAP-native apps,
|
||||||
use `ldaps://…:636` or StartTLS.
|
`theta42/proxy`) should use `ldaps://…:636` or StartTLS.
|
||||||
3. **Persist `JWT_SECRET`** — if the Docker image auto-generates one and you don't
|
3. **Persist `JWT_SECRET`** — if the Docker image auto-generates one and you don't
|
||||||
set `JWT_SECRET`, issued tokens invalidate on container recreation.
|
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
|
4. **Don't expose the UI's HTTP port to the internet** — terminate TLS at a front
|
||||||
|
|||||||
+2
-1
@@ -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)
|
# 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
|
# 389: LDAP (plain + StartTLS) — used internally by the app; map to host only
|
||||||
# if you want LAN clients to bind without TLS (not recommended).
|
# 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
|
EXPOSE 3001 389 636
|
||||||
|
|
||||||
# Health check
|
# Health check
|
||||||
|
|||||||
@@ -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
|
- **Web management UI** — manage users, groups, and OAuth clients from a
|
||||||
browser; invite and password-reset flows over email; user self-service for
|
browser; invite and password-reset flows over email; user self-service for
|
||||||
profile and API tokens.
|
profile and API tokens.
|
||||||
- **LDAPS for legacy apps** — apps that bind LDAP directly (Gitea, Emby, and
|
- **Direct LDAP binds** — Linux hosts (PAM/SSSD login, LDAP-backed `sudo`
|
||||||
anything else that speaks LDAP) use LDAPS (636) or StartTLS against the same
|
rules, SSH public keys via openssh-lpk) and LDAP-native apps (Gitea, Emby,
|
||||||
directory, so you don't maintain a second user database for them.
|
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
|
- **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.
|
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
|
- **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) │
|
│ OpenLDAP (slapd) │
|
||||||
│ - users / groups │
|
│ - users / groups │
|
||||||
│ - LDAPS :636 │─── legacy apps bind directly
|
│ - LDAPS :636 │─── Linux hosts + LDAP apps bind directly
|
||||||
│ - StartTLS :389 │
|
│ - StartTLS :389 │
|
||||||
└────────────────────────┘
|
└────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -57,3 +57,41 @@ Resources carry a flexible `metadata` JSON object that can store essential conte
|
|||||||
## Navigating the UI
|
## 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.
|
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
@@ -52,11 +52,13 @@ backend, that's the niche.
|
|||||||
- **Web management UI** — users, groups, and OAuth clients from a browser;
|
- **Web management UI** — users, groups, and OAuth clients from a browser;
|
||||||
invite and password-reset flows over email; self-service profile + API
|
invite and password-reset flows over email; self-service profile + API
|
||||||
tokens.
|
tokens.
|
||||||
- **LDAPS for legacy apps** — anything that binds LDAP directly (Gitea,
|
- **Direct LDAP binds** — anything that binds LDAP directly (Linux hosts
|
||||||
Emby, …) uses LDAPS/StartTLS against the same directory.
|
via PAM/SSSD, Gitea, Emby, …) uses LDAPS/StartTLS against the same
|
||||||
|
directory.
|
||||||
- **All-in-one Docker image** — app + OpenLDAP + Redis in one container, or
|
- **All-in-one Docker image** — app + OpenLDAP + Redis in one container, or
|
||||||
run the pieces separately via `app_*` env config.
|
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.
|
- **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
|
## Get it
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -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
|
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)
|
authenticates against it over `localhost:389` (inside the all-in-one container)
|
||||||
and exposes **LDAPS** (`ldaps://…:636`, TLS) for legacy apps that bind LDAP
|
and exposes **LDAPS** (`ldaps://…:636`, TLS) for anything that binds LDAP
|
||||||
directly — Gitea, Emby, the theta42/proxy, etc.
|
directly — Linux hosts (PAM/SSSD, sudo rules, SSH keys), Gitea, Emby, the
|
||||||
|
theta42/proxy, etc.
|
||||||
|
|
||||||
## Directory layout
|
## Directory layout
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "t42-sso-manager",
|
"name": "t42-sso-manager",
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "t42-sso-manager",
|
"name": "t42-sso-manager",
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^7.3.0",
|
"@fortawesome/fontawesome-free": "^7.3.0",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "t42-sso-manager",
|
"name": "t42-sso-manager",
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"description": "A very simple LDAP management and SSO system",
|
"description": "A very simple LDAP management and SSO system",
|
||||||
"author": [
|
"author": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ const DOCS = {
|
|||||||
accounts: {title: 'Accounts, Groups & Managers', file: path.join(__dirname, '../../docs/concepts-accounts.md')},
|
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')},
|
'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')},
|
'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')},
|
overview: {title: 'Overview', file: path.join(__dirname, '../../README.md')},
|
||||||
changelog: {title: 'Changelog', file: path.join(__dirname, '../../CHANGELOG.md')},
|
changelog: {title: 'Changelog', file: path.join(__dirname, '../../CHANGELOG.md')},
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
<div class="card-header actionMessage" style="display:none"></div>
|
<div class="card-header actionMessage" style="display:none"></div>
|
||||||
<div class="p-3 pb-0 text-muted small border-bottom">
|
<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.
|
<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>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="card-body table table-striped mb-0">
|
<table class="card-body table table-striped mb-0">
|
||||||
|
|||||||
Reference in New Issue
Block a user