Secrets/push.sh, Theme/pack.sh+push.sh, Audit/pack.sh+push.sh and Auth/pack.sh+push.sh
all carried the CWD-pack bug LocalDb's push.sh was cured of in 0038d46: a bare
'dotnet pack -c Release -o ./artifacts' packs whatever solution sits in the CALLER's
working directory, and the push scripts then shove the caller's ./artifacts/*.nupkg
onto the live Gitea feed (the incident that briefly published 25 unrelated packages).
Each script now cds to its own repo root, names its .slnx explicitly, writes to an
absolute artifacts path, and (push.sh) pushes an anchored glob. Verified by bash -n on
all seven plus a positive control: Audit's pack.sh run from a foreign CWD packed only
ZB.MOM.WW.Audit into its own artifacts dir and left the caller's directory untouched.
Push scripts deliberately NOT executed (they publish); pack half verified only.
Closes ScadaBridge deferred-work register row 42 (cross-repo).
ZB.MOM.WW.Auth
Authentication and authorisation libraries for the ZB.MOM.WW SCADA family (OtOpcUa, MxAccessGateway, ScadaBridge). These are libraries, not a service — each package is linked directly into the consuming application at build time. There is no central authentication process or network hop; auth logic runs in-process alongside the application.
Packages
| Package | Description | Key Dependencies |
|---|---|---|
ZB.MOM.WW.Auth.Abstractions |
Auth contracts, canonical role constants, and shared types (LdapOptions, LdapAuthResult, ILdapAuthService, IApiKeyStore). No runtime dependencies beyond the BCL. |
— |
ZB.MOM.WW.Auth.Ldap |
LDAP authentication service: bind-then-search-then-bind against GLAuth or Active Directory; RFC 4514-aware group extraction; fail-closed. | Abstractions, Novell.Directory.Ldap.NETStandard |
ZB.MOM.WW.Auth.ApiKeys |
SQLite-backed API-key store with pepper-keyed HMAC-SHA256 secret hashing, rotation, and audit log. DI wiring is AddZbApiKeyAuth; an opt-in MigrationHostedService runs schema migrations on startup. |
Abstractions, Microsoft.Data.Sqlite |
ZB.MOM.WW.Auth.AspNetCore |
ASP.NET Core wiring for the LDAP provider only: AddZbLdapAuth (binds + start-time-validates LdapOptions, registers ILdapAuthService), plus ZbCookieDefaults.Apply (hardened cookie helper the consumer calls itself) and ZbClaimTypes constants. It does not wire API keys or cookie middleware — API-key DI is AddZbApiKeyAuth in the ApiKeys package. |
Abstractions, Ldap, Microsoft.AspNetCore.App |
Consumer Matrix
| Consumer | Abstractions | Ldap | ApiKeys | AspNetCore |
|---|---|---|---|---|
| OtOpcUa | yes | yes | — | yes |
| MxAccessGateway | yes | yes | yes | yes |
| ScadaBridge | yes | yes | yes | yes |
ApiKeys is NOT used by OtOpcUa (that app authenticates human operators via LDAP + cookies only; machine-to-machine access is out of scope).
Versioning
All four packages are versioned lockstep from Directory.Build.props. The current release is 0.1.4. A single version bump in Directory.Build.props bumps all four packages simultaneously — consumers should reference the same version for all ZB.MOM.WW.Auth packages.
0.1.4 — API-key expiry (archreview G-2)
ApiKeyRecord/ApiKeyListItemgain an optionalDateTimeOffset? ExpiresUtc(nullable;NULL= never expires).ApiKeyFailuregainsKeyExpired.ApiKeyVerifierenforces expiry — a key whoseExpiresUtcis at or before "now" (inclusive, injected clock) fails withKeyExpired, decided before the secret comparison. Existing (never-expiring) keys are unaffected.ApiKeyAdminCommands.CreateKeyAsynchas a new overload takingDateTimeOffset? expiresUtc; the old signature delegates withnull.RotateKeyAsyncdeliberately preserves an existing expiry (rotation changes only the secret).- SQLite schema → v3: a nullable
expires_utccolumn, added to fresh DBs by the CREATE DDL and to existing v1/v2 DBs by an idempotentALTER TABLEin the migrator (existing rows →NULL). A donor v2gateway-auth.dbis accepted (2 ≤ 3) and upgraded in place — no key is invalidated. Old-build readers tolerate the extra column. - Consumers must bump to 0.1.4 to set/enforce expiry (mxaccessgw + HistorianGateway benefit from the same verifier).
Running the opt-in LDAP integration test
The GLAuth integration test (GLAuthIntegrationTests) is skipped by default and does not affect the normal test run. To exercise it against a live GLAuth instance:
-
Start the GLAuth Docker stack from the sibling repo:
cd ~/Desktop/ScadaBridge/infra/glauth docker compose up -d -
Set the required environment variables and run the test:
export ZB_LDAP_IT=1 export ZB_LDAP_SVC_DN="cn=svc,dc=lmxopcua,dc=local" export ZB_LDAP_SVC_PW="svcpass" export ZB_LDAP_USER="alice" export ZB_LDAP_PW="alicepass" dotnet test tests/ZB.MOM.WW.Auth.Ldap.Tests \ --filter "FullyQualifiedName~GLAuthIntegrationTests"
All other variables (ZB_LDAP_SERVER, ZB_LDAP_PORT, ZB_LDAP_BASE, ZB_LDAP_USERATTR) default to sensible GLAuth values and are optional. The test also probes TCP reachability before attempting auth and skips if the server is not contactable.
Publishing packages
Use build/push.sh to pack and push to the Gitea NuGet feed:
export GITEA_NUGET_SOURCE="https://gitea.dohertylan.com/api/packages/dohertj2/nuget/index.json"
export GITEA_NUGET_KEY="your-gitea-token"
./build/push.sh
The script runs dotnet pack -c Release then dotnet nuget push --skip-duplicate.
Design documentation
Full design docs live in the components/auth folder of the SCADA project notes:
~/Desktop/scadaproj/components/auth/spec/SPEC.md— overall auth specification~/Desktop/scadaproj/components/auth/spec/CANONICAL-ROLES.md— role taxonomy~/Desktop/scadaproj/components/auth/shared-contract/— shared contract types