Files
scadaproj/ZB.MOM.WW.Auth/README.md
T
Joseph Doherty 378880f0ed feat(apikeys): optional ExpiresUtc — create + verifier enforcement + sqlite v3 migration (archreview G-2)
ApiKeyRecord/ApiKeyListItem gain a nullable ExpiresUtc (NULL = never
expires); ApiKeyFailure gains KeyExpired. ApiKeyVerifier rejects a key
whose ExpiresUtc is at-or-before now (inclusive, injected clock), before
the secret comparison. CreateKeyAsync gets an expiresUtc overload; rotate
preserves existing expiry. SQLite schema bumps to v3: a nullable
expires_utc column, added to fresh DBs via CREATE and to existing v1/v2
DBs via an idempotent guarded ALTER — donor v2 gateway-auth.db upgrades
in place, no key invalidated. Version 0.1.3 -> 0.1.4 (not yet published;
nuget push is human-gated). Consumers (HistorianGateway, mxaccessgw) bump
to 0.1.4 to set/enforce expiry. 146 Auth.ApiKeys tests pass.
2026-07-09 06:04:54 -04:00

91 lines
5.0 KiB
Markdown

# 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`/`ApiKeyListItem` gain an optional `DateTimeOffset? ExpiresUtc`** (nullable; `NULL` = never expires). `ApiKeyFailure` gains `KeyExpired`.
- **`ApiKeyVerifier` enforces expiry** — a key whose `ExpiresUtc` is at or before "now" (inclusive, injected clock) fails with `KeyExpired`, decided before the secret comparison. Existing (never-expiring) keys are unaffected.
- **`ApiKeyAdminCommands.CreateKeyAsync`** has a new overload taking `DateTimeOffset? expiresUtc`; the old signature delegates with `null`. `RotateKeyAsync` deliberately preserves an existing expiry (rotation changes only the secret).
- **SQLite schema → v3**: a nullable `expires_utc` column, added to fresh DBs by the CREATE DDL and to existing v1/v2 DBs by an idempotent `ALTER TABLE` in the migrator (existing rows → `NULL`). A donor v2 `gateway-auth.db` is 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:
1. Start the GLAuth Docker stack from the sibling repo:
```
cd ~/Desktop/ScadaBridge/infra/glauth
docker compose up -d
```
2. Set the required environment variables and run the test:
```bash
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:
```bash
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