diff --git a/infra/glauth/README.md b/infra/glauth/README.md index b193c43..a07672f 100644 --- a/infra/glauth/README.md +++ b/infra/glauth/README.md @@ -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 ``` diff --git a/infra/glauth/config.toml b/infra/glauth/config.toml index 643b4b6..40b5ec8 100644 --- a/infra/glauth/config.toml +++ b/infra/glauth/config.toml @@ -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" diff --git a/infra/glauth/docker-compose.yml b/infra/glauth/docker-compose.yml index ee8c5b1..cb86922 100644 --- a/infra/glauth/docker-compose.yml +++ b/infra/glauth/docker-compose.yml @@ -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) diff --git a/infra/glauth/tls/.gitignore b/infra/glauth/tls/.gitignore new file mode 100644 index 0000000..d11f3cc --- /dev/null +++ b/infra/glauth/tls/.gitignore @@ -0,0 +1,2 @@ +key.pem +*.key diff --git a/infra/glauth/tls/cert.pem b/infra/glauth/tls/cert.pem new file mode 100644 index 0000000..1a1a865 --- /dev/null +++ b/infra/glauth/tls/cert.pem @@ -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-----