docs: fix stale multi-site claims, document LDAP MMR auto-config + CFG_PUBLIC_DOMAIN
The published docs site had drifted from what actually shipped: - docs/sso/multi-site.md said the no-inbound relay was "designed but not automated" -- it's been automated since earlier this session. Also said "don't combine" multi-site join and LDAP replication -- they're integrated now (join auto-configures LDAP MMR). - docs/sso/replication.md (the actually-published/linked replication page -- distinct from theta-directory's own docs/replication.md, which isn't linked from this site's nav at all) still only described the old fully-manual LDAP_SERVER_ID/LDAP_REPLICATION_HOSTS setup, with zero mention of the new auto-config or CFG_LDAP_MMR_MANUAL. - docs/index.md's feature bullet described multi-site purely as "N-Way Multi-Master LDAP replication" -- the actual master/spoke join feature (the more commonly-used, higher-level mechanism) wasn't mentioned on the homepage at all. - CFG_PUBLIC_DOMAIN (shipped, never documented anywhere an operator would read it) now explained in multi-site.md. - spoke.env now discoverable from quickstart.md, not just multi-site.md. Also documents the real, load-bearing limitation from this session's promotion/LDAP-orphan fix: the master's own replication peer list only updates on its own next setup.sh run, not live the instant a spoke joins or a promotion happens.
This commit is contained in:
+6
-4
@@ -74,10 +74,12 @@ The SSO Manager and the proxy it fronts, both stood up by one `./setup.sh` run:
|
|||||||
management built directly into the web interface, allowing operators to drive
|
management built directly into the web interface, allowing operators to drive
|
||||||
system administration and automation pipelines programmatically without an
|
system administration and automation pipelines programmatically without an
|
||||||
active browser session.
|
active browser session.
|
||||||
- **Multi-Site Geo-Replication**: Built-in support for N-Way Multi-Master LDAP
|
- **Multi-Site Geo-Replication**: A master site and any number of spokes join
|
||||||
replication, allowing directory states to sync across geographically separated
|
with one key and one URL, staying in sync automatically (no manual LDAP
|
||||||
physical hardware or remote data centers for high availability and low-latency
|
config) — see [Multi-Site (Master/Spoke Join)](sso/multi-site.html). Raw
|
||||||
local reads.
|
N-Way Multi-Master LDAP replication is also available directly for
|
||||||
|
deployments that want every site independently writable with no
|
||||||
|
master/spoke concept — see [Geo-Location Scaling](sso/replication.html).
|
||||||
- **Multi-Target Load Balancing**: Native reverse-proxy load balancing that
|
- **Multi-Target Load Balancing**: Native reverse-proxy load balancing that
|
||||||
distributes traffic across multiple application backends using customizable
|
distributes traffic across multiple application backends using customizable
|
||||||
health checks and round-robin strategies.
|
health checks and round-robin strategies.
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ file shape.
|
|||||||
> exist, `./setup.sh` migrates them into `./config/` preserving your existing
|
> exist, `./setup.sh` migrates them into `./config/` preserving your existing
|
||||||
> secrets — no need to write a `setup.env`.
|
> secrets — no need to write a `setup.env`.
|
||||||
|
|
||||||
|
> **Joining an existing Theta Directory cluster instead of seeding a fresh
|
||||||
|
> one?** See [Multi-Site (Master/Spoke Join)](sso/multi-site.html) —
|
||||||
|
> `spoke.env.example` has the join-a-cluster vars split out into their own
|
||||||
|
> file, or set them directly in `setup.env` (which has every option).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Run
|
## 3. Run
|
||||||
|
|||||||
+42
-17
@@ -10,10 +10,10 @@ the **master** (single write authority for the shared catalog) and any
|
|||||||
number of **spokes** — read-only replicas that stay in sync automatically and
|
number of **spokes** — read-only replicas that stay in sync automatically and
|
||||||
run local authentication with zero WAN dependency.
|
run local authentication with zero WAN dependency.
|
||||||
|
|
||||||
This is a different, higher-level mechanism than [raw LDAP N-way
|
This is a higher-level mechanism than [raw LDAP N-way
|
||||||
replication](replication.html) — see [How this relates to LDAP
|
replication](replication.html) — and it now drives that lower-level
|
||||||
replication](#how-this-relates-to-ldap-replication) below if you're deciding
|
replication for you automatically. See [How this relates to LDAP
|
||||||
between the two.
|
replication](#how-this-relates-to-ldap-replication) below.
|
||||||
|
|
||||||
## Why and when to use this
|
## Why and when to use this
|
||||||
|
|
||||||
@@ -45,6 +45,13 @@ between the two.
|
|||||||
stack's setup. Both are read; `spoke.env`'s values win on a conflict.
|
stack's setup. Both are read; `spoke.env`'s values win on a conflict.
|
||||||
No public IP on this site at all? `spoke.env.example` also covers the
|
No public IP on this site at all? `spoke.env.example` also covers the
|
||||||
no-inbound relay vars (`CFG_SPOKE_NO_INBOUND`/`CFG_SPOKE_PUBLIC_HOST`).
|
no-inbound relay vars (`CFG_SPOKE_NO_INBOUND`/`CFG_SPOKE_PUBLIC_HOST`).
|
||||||
|
|
||||||
|
Want this spoke reachable at its own public domain rather than sharing
|
||||||
|
the master's? `CFG_DOMAIN` (the LDAP identity namespace) must stay
|
||||||
|
identical across every site in a cluster — MMR replicas can't diverge
|
||||||
|
on base DN — but `CFG_PUBLIC_DOMAIN` overrides just this site's own web
|
||||||
|
hostnames (`sso.*`/`proxy.*`) independently of it. Only meaningful for
|
||||||
|
an inbound spoke serving its own traffic directly.
|
||||||
3. The spoke pulls the master's full export (LDAP tree, resource catalog,
|
3. The spoke pulls the master's full export (LDAP tree, resource catalog,
|
||||||
agent-signing key) and adopts it, then registers its own reachable URL
|
agent-signing key) and adopts it, then registers its own reachable URL
|
||||||
with the master so it can receive live updates going forward.
|
with the master so it can receive live updates going forward.
|
||||||
@@ -82,26 +89,44 @@ so on) are **not** currently synced — each site still generates its own.
|
|||||||
|
|
||||||
- Both sites need a network path to each other's HTTP(S) API — the master to
|
- Both sites need a network path to each other's HTTP(S) API — the master to
|
||||||
pull an export from, the spoke to push replication updates back to. A site
|
pull an export from, the spoke to push replication updates back to. A site
|
||||||
with no inbound path at all (e.g. behind CGNAT) can't join yet on its own;
|
with **no inbound path at all** (e.g. behind CGNAT) can still join: set
|
||||||
a relay mechanism for that case is designed but not automated (see the
|
`CFG_SPOKE_NO_INBOUND=true` + `CFG_SPOKE_PUBLIC_HOST` (`spoke.env.example`)
|
||||||
[architecture spec](https://github.com/theta42/theta-suite/blob/master/docs/MULTI_SITE_SPEC.md)
|
once its jump-host is meshed to the master's over WireGuard (mesh peering
|
||||||
for the current status).
|
itself is a manual, one-time step on both jump-hosts — see [Theta Gateway
|
||||||
|
→ Mesh](../jump-host/mesh.html)) — the master then relays traffic to it
|
||||||
|
and auto-creates the matching route on its own `theta-proxy`. A spoke with
|
||||||
|
**zero inbound and zero outbound** path still can't join at all (the join
|
||||||
|
itself needs to reach the master's API directly).
|
||||||
- Joining only ever happens on a **fresh install**. There's no way to merge
|
- Joining only ever happens on a **fresh install**. There's no way to merge
|
||||||
an already-populated directory into a master's — re-provision the host
|
an already-populated directory into a master's — re-provision the host
|
||||||
first.
|
first.
|
||||||
|
- Promoting a spoke to master doesn't instantly finish reconciling OpenLDAP
|
||||||
|
replication (see below) — re-run `setup.sh` on the newly-promoted node
|
||||||
|
promptly afterward.
|
||||||
|
|
||||||
## How this relates to LDAP replication
|
## How this relates to LDAP replication
|
||||||
|
|
||||||
[N-way LDAP replication](replication.html) is a *lower-level*, different
|
[N-way LDAP replication](replication.html) is the *lower-level* mechanism
|
||||||
mechanism: every site runs a fully independent, fully writable `slapd`, wired
|
underneath this: `slapd`'s own `syncrepl`, wired via `LDAP_SERVER_ID` +
|
||||||
together with raw `syncrepl` environment variables, and there's no concept of
|
`LDAP_REPLICATION_HOSTS`. Originally this was hand-configured by the
|
||||||
a master or a managed join. It predates this feature and is still there for
|
operator, separately from the join flow above, for deployments that wanted
|
||||||
deployments that specifically want every site independently writable.
|
every site independently writable with no concept of a master.
|
||||||
|
|
||||||
Multi-site join (this page) is the opposite design: one write authority, a
|
**When you join via this page's flow, that lower-level config is now handled
|
||||||
managed onboarding flow, and automatic ongoing sync — closer to what most
|
for you.** The master auto-assigns each spoke a unique `LDAP_SERVER_ID` at
|
||||||
"add a second office" or "add a home-lab spoke" setups actually want. **Don't
|
join time and derives every site's LDAP URL from its already-known HTTPS
|
||||||
combine the two** — pick one per deployment.
|
endpoint — `theta-suite`'s `bootstrap/site-ldap-register.js` applies it,
|
||||||
|
re-checked on every `setup.sh` run since the peer list grows as spokes join.
|
||||||
|
You don't hand-set `LDAP_SERVER_ID`/`LDAP_REPLICATION_HOSTS` for a cluster
|
||||||
|
built this way. See [Geo-Location Scaling](replication.html#automatic-config-via-multi-site-join)
|
||||||
|
for the mechanics, and its documented limitation: the *master's* own
|
||||||
|
replication list only updates on ITS next `setup.sh` run, not live the
|
||||||
|
instant a new spoke joins.
|
||||||
|
|
||||||
|
Still want fully independent, always-writable sites with no master/spoke
|
||||||
|
concept at all? `CFG_LDAP_MMR_MANUAL=true` opts out of the automatic path so
|
||||||
|
you can hand-set `LDAP_SERVER_ID`/`LDAP_REPLICATION_HOSTS` directly, same as
|
||||||
|
before this integration existed.
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
|
|||||||
+34
-9
@@ -23,33 +23,58 @@ In an N-Way Multi-Master setup, every site runs a fully active OpenLDAP server (
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
To enable replication, you must pass two environment variables to the `sso-manager` container:
|
The container's entrypoint reads two environment variables to configure this
|
||||||
|
-- `LDAP_SERVER_ID` (a unique integer for this node) and
|
||||||
|
`LDAP_REPLICATION_HOSTS` (a space-separated list of every **other** node's
|
||||||
|
LDAP URL) -- and, when both are set, automatically loads the `syncprov`
|
||||||
|
module, enables `mirrormode`, and generates the necessary `syncrepl` blocks
|
||||||
|
in `/etc/openldap/slapd.conf`.
|
||||||
|
|
||||||
1. `LDAP_SERVER_ID`: A unique integer for this node (e.g., `1`, `2`, `3`). This MUST be unique across the cluster.
|
### Automatic config via Multi-Site join
|
||||||
2. `LDAP_REPLICATION_HOSTS`: A space-separated list of the LDAP URLs of all **other** nodes in the cluster.
|
|
||||||
|
|
||||||
### Example using `theta-env` / Docker Compose
|
**If you're using [Multi-Site join](multi-site.html) (`CFG_MASTER_DIRECTORY_URL`/`spoke.env`),
|
||||||
|
you don't set these by hand.** The master assigns each spoke a unique
|
||||||
|
`LDAP_SERVER_ID` at join time (the same way it assigns a WireGuard mesh
|
||||||
|
index) and derives `LDAP_REPLICATION_HOSTS` from every site's already-known
|
||||||
|
HTTPS endpoint (`ldaps://<same-host>:636`) -- `theta-suite`'s `bootstrap/
|
||||||
|
site-ldap-register.js` applies it and re-checks on every `setup.sh` run,
|
||||||
|
since the peer list grows as new spokes join, restarting `sso-manager` only
|
||||||
|
when the computed config actually changed.
|
||||||
|
|
||||||
**Site 1 (`setup.env` or `docker-compose.yml`)**
|
**Known limitation**: the *master's* own `LDAP_REPLICATION_HOSTS` only gets
|
||||||
|
recomputed when its `setup.sh` is re-run — there's no live push telling an
|
||||||
|
already-running master about a spoke that joined five minutes ago. Re-run
|
||||||
|
`setup.sh` on the master after bringing up a new spoke (or after promoting
|
||||||
|
one to master) to pick up the current peer list. A spoke's own config, by
|
||||||
|
contrast, is re-checked and applied on every `setup.sh` run there, which is
|
||||||
|
the common/recurring event.
|
||||||
|
|
||||||
|
### Manual configuration
|
||||||
|
|
||||||
|
Have a topology outside a `theta-suite`-managed cluster (fully independent,
|
||||||
|
always-writable sites, no master/spoke concept)? Set
|
||||||
|
`CFG_LDAP_MMR_MANUAL=true` to skip the automatic path entirely and set the
|
||||||
|
two variables directly -- without this, the automatic step runs on every
|
||||||
|
deployment (every fresh install starts as a master) and will overwrite them.
|
||||||
|
|
||||||
|
**Site 1**
|
||||||
```env
|
```env
|
||||||
LDAP_SERVER_ID=1
|
LDAP_SERVER_ID=1
|
||||||
LDAP_REPLICATION_HOSTS="ldaps://sso.site2.com:636 ldaps://sso.site3.com:636"
|
LDAP_REPLICATION_HOSTS="ldaps://sso.site2.com:636 ldaps://sso.site3.com:636"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Site 2 (`setup.env` or `docker-compose.yml`)**
|
**Site 2**
|
||||||
```env
|
```env
|
||||||
LDAP_SERVER_ID=2
|
LDAP_SERVER_ID=2
|
||||||
LDAP_REPLICATION_HOSTS="ldaps://sso.site1.com:636 ldaps://sso.site3.com:636"
|
LDAP_REPLICATION_HOSTS="ldaps://sso.site1.com:636 ldaps://sso.site3.com:636"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Site 3 (`setup.env` or `docker-compose.yml`)**
|
**Site 3**
|
||||||
```env
|
```env
|
||||||
LDAP_SERVER_ID=3
|
LDAP_SERVER_ID=3
|
||||||
LDAP_REPLICATION_HOSTS="ldaps://sso.site1.com:636 ldaps://sso.site2.com:636"
|
LDAP_REPLICATION_HOSTS="ldaps://sso.site1.com:636 ldaps://sso.site2.com:636"
|
||||||
```
|
```
|
||||||
|
|
||||||
Once configured, the container's entrypoint will automatically load the `syncprov` module, enable `mirrormode`, and generate the necessary `syncrepl` blocks in `/etc/openldap/slapd.conf`.
|
|
||||||
|
|
||||||
## User Locations
|
## User Locations
|
||||||
|
|
||||||
When creating or editing a user, you can specify their **Location (Site)**. This maps directly to the standard LDAP `l` (localityName) attribute, allowing you to track which physical site a user belongs to natively within the directory.
|
When creating or editing a user, you can specify their **Location (Site)**. This maps directly to the standard LDAP `l` (localityName) attribute, allowing you to track which physical site a user belongs to natively within the directory.
|
||||||
|
|||||||
Reference in New Issue
Block a user