feat(infra): redundant GLAuth pair for FallbackServers failover drills
Two local glauth containers (zb-ldap-primary :3893 / zb-ldap-backup :3894) simulating the NAM two-DC topology, so the primary can be killed during a drill without touching the shared 10.100.0.35 instance. Drill recipe + verified-PASS record (HistorianGateway, 2026-08-13) in the README.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
# Redundant LDAP test pair (primary + backup "DC")
|
||||
|
||||
Two GLAuth containers on the **dev Mac** that simulate the NAM two-DC topology
|
||||
(`uswap-vdc-nam1`/`nam2`) for exercising `ZB.MOM.WW.Auth.Ldap` **0.2.x
|
||||
`FallbackServers` sticky failover**. Deliberately local — the shared
|
||||
`zb-shared-glauth` on `10.100.0.35:3893` serves every other dev stack and must
|
||||
never be stopped for a drill.
|
||||
|
||||
| | container | host port | from a container |
|
||||
|---|---|---|---|
|
||||
| primary | `zb-ldap-primary` | `3893` | `host.docker.internal:3893` |
|
||||
| backup | `zb-ldap-backup` | `3894` | `host.docker.internal:3894` |
|
||||
|
||||
Both mount the same `config.toml` — identical directory contents, like a real
|
||||
replicated pair. It is a copy of [`../glauth/config.toml`](../glauth/config.toml)
|
||||
with one difference: the serviceaccount password here is the well-known local
|
||||
value `serviceaccount123` (the shared instance's is rotated out-of-band, SEC-36).
|
||||
Same users otherwise (`multi-role`/`password`, etc.).
|
||||
|
||||
```bash
|
||||
docker compose up -d # start the pair
|
||||
```
|
||||
|
||||
## Consumer wiring (any of the four apps)
|
||||
|
||||
```
|
||||
Ldap__Server: host.docker.internal # primary
|
||||
Ldap__Port: "3893"
|
||||
Ldap__FallbackServers__0: host.docker.internal:3894
|
||||
Ldap__Transport: None
|
||||
Ldap__AllowInsecure: "true"
|
||||
```
|
||||
|
||||
First wired consumer: `HistorianGateway/docker/docker-compose.yml` (Auth pin
|
||||
`0.2.1`, dashboard login enabled). Note its Production-env container needs
|
||||
`Serilog__WriteTo__0__Name: Console` or it logs **nothing** — the drill's
|
||||
failover lines would be invisible.
|
||||
|
||||
## Failover drill (verified PASS 2026-08-13 against HistorianGateway)
|
||||
|
||||
1. login → served by primary, no warnings
|
||||
2. `docker stop zb-ldap-primary` → login OK; app logs
|
||||
`WRN LDAP endpoint …:3893 failed system-side at connect` +
|
||||
`INF LDAP sticky preference moved to …:3894`
|
||||
3. `docker start zb-ldap-primary` → login OK, **still served by the backup**
|
||||
(sticky by design — verified by bind counts: primary got zero new binds)
|
||||
4. `docker stop zb-ldap-backup` → login OK; warn on `:3894`, sticky moves back
|
||||
to `:3893`
|
||||
|
||||
The sticky preference lives in the app's singleton `LdapAuthService`, so an app
|
||||
restart resets it to the configured primary.
|
||||
@@ -0,0 +1,169 @@
|
||||
[ldap]
|
||||
enabled = true
|
||||
listen = "0.0.0.0:3893"
|
||||
|
||||
[ldaps]
|
||||
enabled = false
|
||||
|
||||
[backend]
|
||||
datastore = "config"
|
||||
baseDN = "dc=zb,dc=local"
|
||||
|
||||
[behaviors]
|
||||
# Dev: do not lock out on failed binds (avoids surprises during testing).
|
||||
LimitFailedBinds = false
|
||||
|
||||
# ── Groups ───────────────────────────────────────────────────────────
|
||||
# ScadaBridge role groups (55xx) — DB-mapped (LdapGroupMappings)
|
||||
[[groups]]
|
||||
name = "SCADA-Admins"
|
||||
gidnumber = 5501
|
||||
[[groups]]
|
||||
name = "SCADA-Designers"
|
||||
gidnumber = 5502
|
||||
[[groups]]
|
||||
name = "SCADA-Deploy-All"
|
||||
gidnumber = 5503
|
||||
[[groups]]
|
||||
name = "SCADA-Deploy-SiteA"
|
||||
gidnumber = 5504
|
||||
[[groups]]
|
||||
name = "SCADA-Viewers"
|
||||
gidnumber = 5505
|
||||
|
||||
# OPC-UA permission groups (560x) — OtOpcUa + MxGateway OPC write model
|
||||
[[groups]]
|
||||
name = "ReadOnly"
|
||||
gidnumber = 5601
|
||||
[[groups]]
|
||||
name = "WriteOperate"
|
||||
gidnumber = 5602
|
||||
[[groups]]
|
||||
name = "WriteTune"
|
||||
gidnumber = 5603
|
||||
[[groups]]
|
||||
name = "WriteConfigure"
|
||||
gidnumber = 5604
|
||||
[[groups]]
|
||||
name = "AlarmAck"
|
||||
gidnumber = 5605
|
||||
|
||||
# MxGateway dashboard groups (561x) — config-mapped (GroupToRole)
|
||||
[[groups]]
|
||||
name = "GwAdmin"
|
||||
gidnumber = 5610
|
||||
[[groups]]
|
||||
name = "GwReader"
|
||||
gidnumber = 5611
|
||||
|
||||
# OtOpcUa AdminUI role groups (57xx) — DB-mapped (LdapGroupRoleMapping)
|
||||
[[groups]]
|
||||
name = "OtOpcUa-Admins"
|
||||
gidnumber = 5701
|
||||
[[groups]]
|
||||
name = "OtOpcUa-Designers"
|
||||
gidnumber = 5702
|
||||
[[groups]]
|
||||
name = "OtOpcUa-Viewers"
|
||||
gidnumber = 5703
|
||||
|
||||
# ── Users ────────────────────────────────────────────────────────────
|
||||
# All passwords are "password". The serviceaccount password is rotated
|
||||
# out-of-band (SEC-36) and is not recorded here.
|
||||
# sha256("password") = 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
|
||||
|
||||
# The single bind account every app uses (search-then-bind).
|
||||
[[users]]
|
||||
name = "serviceaccount"
|
||||
uidnumber = 5999
|
||||
primarygroup = 5601
|
||||
passsha256 = "af29d0e5c9801ae98a999ed3915e1cf428a64b4b62b3cf221b6336cce0398419"
|
||||
[[users.capabilities]]
|
||||
action = "search"
|
||||
object = "*"
|
||||
|
||||
# Cross-app: member of EVERY group → all roles in all three apps.
|
||||
[[users]]
|
||||
name = "multi-role"
|
||||
givenname = "Multi"
|
||||
sn = "Role"
|
||||
mail = "multi-role@zb.local"
|
||||
uidnumber = 5005
|
||||
primarygroup = 5501
|
||||
othergroups = [5502, 5503, 5504, 5505, 5601, 5602, 5603, 5604, 5605, 5610, 5611, 5701, 5702, 5703]
|
||||
passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
|
||||
# Administrator everywhere (admin-equivalent of each app).
|
||||
[[users]]
|
||||
name = "admin"
|
||||
uidnumber = 5001
|
||||
primarygroup = 5501
|
||||
othergroups = [5610, 5701]
|
||||
passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
|
||||
# ScadaBridge single-role testers
|
||||
[[users]]
|
||||
name = "designer"
|
||||
uidnumber = 5002
|
||||
primarygroup = 5502
|
||||
passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
[[users]]
|
||||
name = "deployer"
|
||||
uidnumber = 5003
|
||||
primarygroup = 5503
|
||||
passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
[[users]]
|
||||
name = "site-deployer"
|
||||
uidnumber = 5004
|
||||
primarygroup = 5504
|
||||
passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
|
||||
# MxGateway dashboard Viewer tester.
|
||||
# NOTE: tester usernames must NOT case-collide with a group name — glauth exposes
|
||||
# each group as cn=<Group> under ou=users, so a case-insensitive (cn=X) search
|
||||
# would match both the user and the group (ambiguous → auth fails). Hence gw-viewer
|
||||
# (not "gwreader" which collides with the GwReader group), opc-* below, etc.
|
||||
[[users]]
|
||||
name = "gw-viewer"
|
||||
uidnumber = 5106
|
||||
primarygroup = 5611
|
||||
passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
|
||||
# OPC-UA permission testers
|
||||
[[users]]
|
||||
name = "opc-readonly"
|
||||
uidnumber = 5101
|
||||
primarygroup = 5601
|
||||
passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
[[users]]
|
||||
name = "opc-writeop"
|
||||
uidnumber = 5102
|
||||
primarygroup = 5602
|
||||
passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
[[users]]
|
||||
name = "opc-writetune"
|
||||
uidnumber = 5103
|
||||
primarygroup = 5603
|
||||
passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
[[users]]
|
||||
name = "opc-writeconfig"
|
||||
uidnumber = 5104
|
||||
primarygroup = 5604
|
||||
passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
[[users]]
|
||||
name = "opc-alarmack"
|
||||
uidnumber = 5105
|
||||
primarygroup = 5605
|
||||
passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
|
||||
# OtOpcUa single-role testers (admin covers OtOpcUa-Admins)
|
||||
[[users]]
|
||||
name = "otdesigner"
|
||||
uidnumber = 5202
|
||||
primarygroup = 5702
|
||||
passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
[[users]]
|
||||
name = "otviewer"
|
||||
uidnumber = 5203
|
||||
primarygroup = 5703
|
||||
passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
@@ -0,0 +1,36 @@
|
||||
# Redundant LDAP test pair — two GLAuth instances simulating a primary + backup DC,
|
||||
# for exercising ZB.MOM.WW.Auth.Ldap 0.2.x `FallbackServers` sticky failover.
|
||||
#
|
||||
# Runs LOCALLY on the dev Mac (not on 10.100.0.35) precisely so the "primary DC"
|
||||
# can be stopped/started during a failover drill without touching the shared
|
||||
# zb-shared-glauth that every other dev stack authenticates against.
|
||||
#
|
||||
# Both instances mount the SAME config.toml — identical directory contents, like a
|
||||
# real replicated DC pair. The config is a copy of ../glauth/config.toml with ONE
|
||||
# difference: the serviceaccount password here is the well-known local-dev value
|
||||
# `serviceaccount123` (the shared instance's is rotated out-of-band, SEC-36).
|
||||
#
|
||||
# primary : ldap://localhost:3893 (containers reach it at host.docker.internal:3893)
|
||||
# backup : ldap://localhost:3894 (containers reach it at host.docker.internal:3894)
|
||||
#
|
||||
# Drill: docker stop zb-ldap-primary → logins fail over to the backup (sticky)
|
||||
# docker start zb-ldap-primary → preference STAYS on the backup by design
|
||||
# docker stop zb-ldap-backup → next login walks back to the primary
|
||||
name: zb-ldap-redundant
|
||||
services:
|
||||
ldap-primary:
|
||||
image: glauth/glauth:latest
|
||||
container_name: zb-ldap-primary
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3893:3893"
|
||||
volumes:
|
||||
- ./config.toml:/app/config/config.cfg:ro
|
||||
ldap-backup:
|
||||
image: glauth/glauth:latest
|
||||
container_name: zb-ldap-backup
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3894:3893"
|
||||
volumes:
|
||||
- ./config.toml:/app/config/config.cfg:ro
|
||||
Reference in New Issue
Block a user