infra(glauth): enable LDAPS on :3894 alongside plain :3893 (#19, mxaccessgw#180)

Self-signed cert (SAN IP 10.100.0.35), public cert checked in, key host-only.
Deployed on 10.100.0.35: TLS chain verifies with the cert and fails without
it; raw LDAPS simple bind returns 0 for a good password and 49 for a bad one;
plain :3893 bind unchanged. README: thumbprint, client config, rotation; also
fixes the user-bind DN example (GLAuth puts users under their primary group OU).

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-11-10 11:46:48 -05:00
parent 736158f2ad
commit 59bf74760c
5 changed files with 63 additions and 5 deletions
+25 -3
View File
@@ -2,10 +2,32 @@
One [GLAuth](https://github.com/glauth/glauth) directory that **all three sister apps use for
dev/test auth** — OtOpcUa, MxAccessGateway, ScadaBridge. It runs as a single container on the
shared Docker host **`10.100.0.35:3893`** (plaintext LDAP; LDAPS/StartTLS → #19). This is the app-neutral source of
shared Docker host **`10.100.0.35`** — plain LDAP on **`:3893`** and LDAPS on **`:3894`** (since 2026-11-10, #19). This is the app-neutral source of
truth; each app just points its `…Ldap:Server` at `10.100.0.35`.
> Open work on this directory is tracked as Gitea issues on `dohertj2/scadaproj` (e.g. #19 LDAPS).
> Open work on this directory is tracked as Gitea issues on `dohertj2/scadaproj`.
## LDAPS (`:3894`)
LDAPS runs next to plain LDAP; `:3893` is unchanged, so every existing `Transport=None` consumer keeps working.
- **Cert:** self-signed, `CN=zb-shared-glauth`, SAN `IP:10.100.0.35, DNS:localhost, DNS:zb-shared-glauth`,
RSA-3072, valid to **2029-11-09**. The public cert is checked in at [`tls/cert.pem`](tls/cert.pem);
the private key exists only on the host (`~/zb-glauth/tls/key.pem`) and is gitignored here.
- **SHA-256 thumbprint** (for clients that pin, e.g. mxaccessgw `MxGateway:Ldap` — mxaccessgw#180):
`EE:B0:13:70:3F:B8:65:4C:9E:6F:39:0F:22:BE:A1:0B:6D:F5:C6:6E:56:57:84:59:E0:93:C6:07:69:D9:9B:BD`
(`openssl x509 -in tls/cert.pem -noout -fingerprint -sha256`).
- **Client config:** `Transport=Ldaps`, server `10.100.0.35`, port `3894`. A client must either pin the
thumbprint or trust `tls/cert.pem`; it is not in any OS trust store.
- **Verify the chain:** `echo | openssl s_client -connect 10.100.0.35:3894 -CAfile tls/cert.pem -verify_return_error -verify_ip 10.100.0.35`
→ `Verify return code: 0 (ok)`; without `-CAfile` it must fail with code 18 (self-signed).
macOS `ldapsearch` ignores `LDAPTLS_CACERT`, so test binds from the alpine container below with
`LDAPTLS_CACERT=/certs/cert.pem` and `-v $PWD/tls:/certs:ro`.
- **Rotating:** generate a new pair in `~/zb-glauth/tls/` on the host, copy `cert.pem` back here, update
the thumbprint above, `docker compose up -d --force-recreate`. Pinning clients should carry old + new
thumbprints across the swap.
- **Not covered:** the redundant local pair (`zb-ldap-primary`/`zb-ldap-backup` on the Mac) and each
wonder box's own `ScadaBridge-LDAP` GLAuth remain plain-only.
> Scope: **dev/test only**. Production uses real corporate AD. See
> [`../../docs/plans/2026-06-04-shared-glauth-standardization-design.md`](../../docs/plans/2026-06-04-shared-glauth-standardization-design.md).
@@ -73,7 +95,7 @@ the search capability, so a successful bind shows `result: 50 Insufficient acces
```bash
ldapsearch -x -H ldap://10.100.0.35:3893 \
-D cn=multi-role,dc=zb,dc=local -w password \
-D cn=multi-role,ou=SCADA-Admins,dc=zb,dc=local -w password \
-b dc=zb,dc=local "(cn=multi-role)" cn
```
+7 -1
View File
@@ -3,7 +3,13 @@
listen = "0.0.0.0:3893"
[ldaps]
enabled = false
# LDAPS on :3894 (mxaccessgw#180), alongside plain :3893, which stays for existing consumers.
# Self-signed cert generated on the host; the PUBLIC cert is checked in at tls/cert.pem, the
# private key lives only on 10.100.0.35 (~/zb-glauth/tls/key.pem) and is never committed.
enabled = true
listen = "0.0.0.0:3894"
cert = "/app/config/tls/cert.pem"
key = "/app/config/tls/key.pem"
[backend]
datastore = "config"
+3 -1
View File
@@ -11,6 +11,8 @@ services:
container_name: zb-shared-glauth
restart: unless-stopped
ports:
- "3893:3893"
- "3893:3893" # plain LDAP (Transport=None)
- "3894:3894" # LDAPS (Transport=Ldaps) — mxaccessgw#180
volumes:
- ./config.toml:/app/config/config.cfg:ro
- ./tls:/app/config/tls:ro # cert.pem (public, in repo) + key.pem (host only)
+2
View File
@@ -0,0 +1,2 @@
key.pem
*.key
+26
View File
@@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEaTCCAtGgAwIBAgIUFqbnjiomQ8qwlkqc09zz/cz33b0wDQYJKoZIhvcNAQEL
BQAwGzEZMBcGA1UEAwwQemItc2hhcmVkLWdsYXV0aDAeFw0yNjExMTAxNjQ1NDJa
Fw0yOTExMDkxNjQ1NDJaMBsxGTAXBgNVBAMMEHpiLXNoYXJlZC1nbGF1dGgwggGi
MA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDQCmS7kpTIQQUQW9y4lMSOqNYc
t5stvdZf+LlYQYqKydcdgSxJLeA7rIYaC3jGrsFjn/o26BLp0/jQLh5HDjmEwYEF
mTqvVdy0ef1AscxJmeCeCAVp5Tm5m5icvN9fLgY/e/mx+G4GJLXGy+xLTffh+dq2
nwXq4p7pS8vKlr+y9DGMAIJOJrEp5xTN3l4rTMujdmbiYiC7ymtNpHKgXAJ/zMKY
A9woUqD57tAm8w0HpplgSHoLrNMg7uUtan8nJjm355AWqKm4j8wRK6yPXPxPvZuw
WAI5wPGH8kjfm1AYFrTuFLZFReX6bbMv2LhQFI9MwBYk6PjCwjyOTjcrK/5xLm86
ZUsJ0qCo7Uud/R1kOvOcBp5KBN8Gs31NZx4t6j7brtSQNsZfu9T7x5krUGKVWErr
q26mQbf4HN+KORaVNJcW4BI96do1K/uGBUzakilsRh5s3VjkMGqNOCGny0T/vaS5
Y29X8CwEW2973MH1HnOJr8fWmKfVwOvhv41MCbkCAwEAAaOBpDCBoTAdBgNVHQ4E
FgQUNdXIl0ILdlh3AKXwmJV/KujaaegwHwYDVR0jBBgwFoAUNdXIl0ILdlh3AKXw
mJV/KujaaegwDwYDVR0TAQH/BAUwAwEB/zAsBgNVHREEJTAjhwQKZAAjgglsb2Nh
bGhvc3SCEHpiLXNoYXJlZC1nbGF1dGgwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoG
CCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IBgQAcEVL2fTbwS6T+u/e99fMbgmOm
31CefVYjJjjwge1tE4lw56cGygUCQtp90SXHnEXuJbSfTGlWCaXDH4fl064kqcOq
WFctP2oxLK1yrrVWNOu0BCi+o2B8P51f5vZt6xMA3nWjs5vPi+ajQ2BBV5cA2o9D
4cmOTcVR1ZEb4mSvcO6IZlTP1vT6HL4Sha/rJlWScEUJWUj0g7Tvw97737Ryqh3R
XKWW2STTlaYpIBGZGD6TiOjDuLYqZ4nla92yakS9tHrZfQ3LCVdfpnyalEfadkWd
lUJ4zK9S0EnbVyy+Wx1i0WZ52gwty09JAaiuNVudyrjLK473wV6kroVO3IzlMkw9
Z/kfdjRBfWp8VzwDCgJAUi4T5sp9VsbeeBdzE8twrKZJq5+UgMXH2X42Q0S/9nd2
807L00UNOSs8erYgcbpp8Pcs/BPy3SBy6evfmMI7qHQv9/tM1uja74wyqrZgs0Ju
FH9zSFseoWjUpdVN54eO74+4XzHFPmWUr4E5mrM=
-----END CERTIFICATE-----