6.7 KiB
Auth — normalized target spec
Status: Draft. The single design the sister projects converge on. Derived from the
three code-verified current-state docs (../current-state/). Goal is path to shared code
(../shared-contract/ZB.MOM.WW.Auth.md), so each normalized section maps to a shared library seam.
0. Scope
Normalized here: LDAP/identity config schema; bind-then-search behavior; the
group→role mapping seam; the standardized canonical role set every project maps onto
(CANONICAL-ROLES.md); the API-key contract; cookie/claim conventions;
dev conventions; secret handling.
Explicitly NOT normalized (domain-specific — keep per project): the authorization
enforcement (OtOpcUa NodePermissions ACL trie, mxaccessgw gRPC scopes + constraints,
ScadaBridge native roles + site-scoping) — each project maps its native model onto the
canonical roles but keeps its own enforcement; OPC UA transport security; OtOpcUa's
generation/staleness session model; ScadaBridge's site-scope rules; mxaccessgw's hub-token model.
1. LDAP / identity configuration schema
One nested Ldap options object, bound under each app's own root section
(OtOpcUa:Authentication:Ldap, MxGateway:Ldap, ScadaBridge:Security:Ldap). Canonical keys/types:
| Key | Type | Notes |
|---|---|---|
Enabled |
bool | |
Server |
string | host |
Port |
int | 389 / 636 / 3893 (GLAuth dev) |
Transport |
enum Ldaps | StartTls | None |
adopt ScadaBridge's enum over a UseTls bool — it expresses StartTLS, which the bool can't |
AllowInsecure |
bool (default false) | dev-only escape hatch; pairs with Transport=None |
SearchBase |
string | base DN |
ServiceAccountDn |
string | for bind-then-search |
ServiceAccountPassword |
string | from secret store, never source |
UserNameAttribute |
string | canonical name (default cn GLAuth / sAMAccountName AD). Supersedes ScadaBridge's LdapUserIdAttribute |
DisplayNameAttribute |
string | default cn |
GroupAttribute |
string | default memberOf |
ConnectionTimeoutMs |
int | per-operation socket timeout |
Migration: OtOpcUa Authentication.Ldap.UseTls→Transport; ScadaBridge flat Ldap*→nested + rename LdapUserIdAttribute→UserNameAttribute; gateway MxGateway:Ldap already close.
2. Bind-then-search behavior (canonical algorithm)
- Connect to
Server:Port. IfTransport != Ldaps/StartTlsand notAllowInsecure→ refuse (config error). - Bind the service account; a failure here is a system misconfiguration, surfaced distinctly from bad user creds.
- Search under
SearchBasewith filter({UserNameAttribute}={escapedUsername}). Escape the username (LDAP filter rules:\ * ( ) NUL). Reject multiple matches (ambiguous); no match → auth failure. - Re-bind as the resolved user DN with the supplied password to verify it. RFC 4514-escape DN components.
- Read
GroupAttribute; normalize group names (strip leadingCN=/RDN). If the group lookup fails, fail the login — never admit a user with zero resolved groups. - Trim-normalize the username once at entry so one person ≠ two identities.
Generic, injection-safe, fail-closed. (ScadaBridge's current impl is the closest reference.)
3. Group → role mapping seam
Resolved LDAP groups (and API-key principals) map to the standardized canonical role set
(CANONICAL-ROLES.md) via IGroupRoleMapper<CanonicalRole>; each project
then expands a canonical role into its native permissions/scopes and applies its own scope
payload. The backing store is project-chosen — config dict (OtOpcUa GroupToRole, gateway
Dashboard:GroupToRole) or database (ScadaBridge LdapGroupMapping). The shared library
defines the seam + the CanonicalRole enum and ships both a config-backed and a delegate/DB-backed
mapper; native enforcement stays per-project.
4. API-key contract (machine-to-machine)
For projects with a programmatic surface (mxaccessgw gRPC, ScadaBridge Inbound API):
- Token:
<prefix>_<keyId>_<secret>(prefix configurable, e.g.mxgw). Parsed/validated before any store hit. - Hashing: HMAC-SHA256 with an external pepper (from secret store/config, never stored beside the hash). Secret = 32 random bytes, URL-safe base64.
- Verify: lookup by keyId → reject if revoked → hash presented secret with pepper → constant-time compare (
CryptographicOperations.FixedTimeEquals). Discriminated failure reasons for audit; opaque error to the caller. - Store: abstraction with a default SQLite implementation (hash, scopes, optional constraints, created/last-used/revoked) + append-only audit. Revoke = timestamp; delete only when revoked.
- Scopes: a
stringset gating operations (project supplies the catalog). - Constraints: an opaque, project-supplied policy object (mxaccessgw subtree/tag globs +
MaxWriteClassification; ScadaBridge per-method approval). The contract carries it; it does not hard-code either shape. - Admin CLI:
init-db / create-key / list-keys / revoke-key / rotate-key / delete-key.
(mxaccessgw Model A is the reference implementation to extract.)
5. Cookie / claim / session conventions
- Cookie: HttpOnly,
SameSite=Strict,Secureconfigurable for dev (RequireHttpsCookie), sliding idle expiry. Name pattern<App>.Auth. - Claims: canonical claim types for name, display name, username, role (one per role), and any project scope id — defined once in
ZB.MOM.WW.Auth.AspNetCore. - DataProtection keys persisted to shared storage so cookies/tokens survive node failover (OtOpcUa already does this via Config DB).
- Session lifetime policy (refresh windows, staleness, generation binding) stays per-project.
6. Dev & secret conventions
- Dev directory: GLAuth. Unify the dev base DN — today OtOpcUa/gateway use
dc=lmxopcua,dc=localand ScadaBridge usesdc=scadabridge,dc=local; pick one shared dev base DN (see../GAPS.md). - Dev escape hatches named consistently:
AllowInsecure(LDAP), plus each project's documented bypass (AllowAnonymousLocalhost,DevStubMode) clearly dev-only and off in prod. - Secrets: service-account passwords, JWT signing keys, and API-key peppers come from a secret store / env, never source; never logged (API keys, passwords, secured payloads, credentials).
7. Acceptance (what "converged" means)
A project is converged when: (a) its LDAP authn + (if applicable) API-key auth run on the
ZB.MOM.WW.Auth packages; (b) its config matches §1's schema; (c) its group→role mapping
implements the §3 seam; (d) cookie/claim conventions match §5; with all project-specific
authorization unchanged.