e541339c07
Resolve audit findings: correct WorkerEnvelope proto/route/metric/session facts; rewrite auth (ZB.MOM.WW.Auth migration), dashboard (ZB.MOM.WW.Theme), and StyleGuide (foreign-project copy-paste); document alarm subsystem, Ldap options, and gateway alarm broker; fix client CLI flags and package paths.
308 lines
12 KiB
Markdown
308 lines
12 KiB
Markdown
# GLAuth — LDAP authn reference for mxaccessgw
|
|
|
|
GLAuth is a lightweight LDAP server installed on this dev box at
|
|
`C:\publish\glauth\` and run as a Windows service via NSSM. It already
|
|
backs the LmxOpcUa OPC UA server's UserName-token authn and the LmxOpcUa
|
|
Admin UI's cookie login; this doc captures everything mxaccessgw needs
|
|
to consume the same directory so a single set of dev credentials covers
|
|
both stacks.
|
|
|
|
The authoritative copy of LmxOpcUa's reference lives at
|
|
`C:\publish\glauth\auth.md`. This doc is a redistilled view tailored to
|
|
mxaccessgw — what users + groups are already provisioned, how to bind
|
|
against them, and what's needed to add a gw-specific role.
|
|
|
|
## Connection details
|
|
|
|
| Setting | Value |
|
|
|---|---|
|
|
| Protocol | LDAP (unencrypted) |
|
|
| Host | `localhost` |
|
|
| Port | `3893` |
|
|
| LDAPS | disabled in dev (set `[ldaps]` block to enable) |
|
|
| Base DN | `dc=zb,dc=local` |
|
|
| Bind DN format | `cn={username},dc=zb,dc=local` |
|
|
| Group OU | `ou=<groupname>,ou=groups,dc=zb,dc=local` |
|
|
| Failed-bind throttle | 3 fails → 10-minute IP lockout (per `[behaviors]`) |
|
|
|
|
## Pre-existing groups (LmxOpcUa role taxonomy)
|
|
|
|
These map cleanly onto MxAccess capability boundaries — mxaccessgw
|
|
should reuse them rather than define parallel groups so an operator with
|
|
LmxOpcUa write rights doesn't need a second account for the gw.
|
|
|
|
| Group | GID | DN | LmxOpcUa meaning | Suggested mxgw mapping |
|
|
|---|---|---|---|---|
|
|
| ReadOnly | 5501 | `ou=ReadOnly,ou=groups,dc=zb,dc=local` | Browse + read OPC UA nodes | `Browse` + `Subscribe` (read paths only) |
|
|
| WriteOperate | 5502 | `ou=WriteOperate,ou=groups,dc=zb,dc=local` | Write FreeAccess / Operate attrs | `Write` (plain) |
|
|
| WriteTune | 5504 | `ou=WriteTune,ou=groups,dc=zb,dc=local` | Write Tune attrs | `WriteSecured` (Tune only) |
|
|
| WriteConfigure | 5505 | `ou=WriteConfigure,ou=groups,dc=zb,dc=local` | Write Configure attrs | `WriteSecured` (Configure) |
|
|
| AlarmAck | 5503 | `ou=AlarmAck,ou=groups,dc=zb,dc=local` | Acknowledge alarms | gw alarm-ack RPC, when added |
|
|
|
|
**A user can be in multiple groups** — `othergroups = [...]` in the
|
|
config is a list. `admin` is the canonical example (in every role
|
|
group below).
|
|
|
|
## Pre-provisioned users
|
|
|
|
| Username | Password | UID | Primary group | Other groups | Capabilities |
|
|
|---|---|---|---|---|---|
|
|
| `readonly` | `readonly123` | 5001 | ReadOnly | — | Browse, read |
|
|
| `writeop` | `writeop123` | 5002 | WriteOperate | — | + plain Write |
|
|
| `writetune` | `writetune123` | 5005 | WriteTune | — | + WriteSecured (Tune) |
|
|
| `writeconfig` | `writeconfig123` | 5006 | WriteConfigure | — | + WriteSecured (Configure) |
|
|
| `alarmack` | `alarmack123` | 5003 | AlarmAck | — | Alarm acknowledgment |
|
|
| `admin` | `admin123` | 5004 | ReadOnly | WriteOperate, AlarmAck, WriteTune, WriteConfigure | All roles |
|
|
| `serviceaccount` | `serviceaccount123` | 5999 | ReadOnly | — | LDAP search capability (for bind-then-search) |
|
|
|
|
For mxaccessgw dev, `admin` covers every gw-side capability test;
|
|
`readonly` is the right "negative" case for proving Browse-OK /
|
|
Write-denied.
|
|
|
|
The gateway dashboard adds one group beyond this LmxOpcUa taxonomy:
|
|
`GwAdmin`. There is no `RequiredGroup` option — dashboard authorization
|
|
is driven entirely by `MxGateway:Dashboard:GroupToRole`, which maps an
|
|
LDAP group to a dashboard role. A user whose groups produce no mapped
|
|
role is rejected at login. So for the dashboard to admit `admin`, a
|
|
group named in `GroupToRole` (by convention `GwAdmin` → `Administrator`)
|
|
must exist and `admin` must belong to it. `GwAdmin` is **not** in the
|
|
baseline GLAuth config — it must be provisioned before dashboard authn
|
|
or the `DashboardLdapLiveTests` (`MXGATEWAY_RUN_LIVE_LDAP_TESTS=1`)
|
|
work. See [Provisioning the GwAdmin group](#provisioning-the-gwadmin-group)
|
|
below.
|
|
|
|
> **Dashboard role value (Task 1.7):** the LDAP `GwAdmin` group now maps to
|
|
> the canonical dashboard role **`Administrator`** (was `Admin`); `GwReader`
|
|
> maps to `Viewer`. This is a pure value rename via
|
|
> `MxGateway:Dashboard:GroupToRole` — same operations are authorized. (This
|
|
> dashboard role is distinct from the lowercase gRPC `admin` *API-key scope*.)
|
|
|
|
## Two bind patterns
|
|
|
|
### 1. Direct bind (simplest)
|
|
|
|
```
|
|
DN: cn=admin,dc=zb,dc=local
|
|
Password: admin123
|
|
```
|
|
|
|
Construct the DN from the username; bind. Works on GLAuth because
|
|
`backend.nameformat = "cn"` and `groupformat = "ou"` are set in the
|
|
config. **Doesn't translate to Active Directory** — AD users are keyed
|
|
by `sAMAccountName`, not `cn`. Use this only for dev convenience.
|
|
|
|
### 2. Bind-then-search (production-grade)
|
|
|
|
```
|
|
1. Bind as the service account (cn=serviceaccount,dc=zb,dc=local
|
|
/ serviceaccount123).
|
|
2. Search under dc=zb,dc=local with filter
|
|
(uid=<entered-username>) — or any attribute the deployment
|
|
identifies users by. GLAuth populates uid + cn.
|
|
3. Read the returned entry's DN + memberOf list (groups).
|
|
4. Bind again as the discovered DN with the entered password. If that
|
|
succeeds, authn passes; the memberOf values become the role set.
|
|
```
|
|
|
|
The second bind is the actual password check — the search is just a DN
|
|
discovery. This is the AD-friendly path: AD's
|
|
`tokenGroups` / `LDAP_MATCHING_RULE_IN_CHAIN` flatten nested groups, but
|
|
that's an enhancement, not required for first-pass dev.
|
|
|
|
LmxOpcUa's `Server/Security/LdapUserAuthenticator.cs` ships a working
|
|
implementation of this pattern using `Novell.Directory.Ldap.NETStandard`
|
|
v3.6.0 — copy the bind-then-search loop from there if mxaccessgw wants
|
|
to avoid re-deriving the LDAP escape-string handling.
|
|
|
|
## Suggested mxgw configuration shape
|
|
|
|
The gateway binds the `MxGateway:Ldap` section onto `LdapOptions`. The
|
|
field names are PascalCase config keys (shown here as YAML; JSON
|
|
`appsettings` and env-var overrides use the same names). Note the keys
|
|
that changed from the older LmxOpcUa shape: `Transport` (an enum,
|
|
replacing the boolean `UseTls`), `AllowInsecure` (replacing
|
|
`AllowInsecureLdap`), and `UserNameAttribute` which defaults to `cn`:
|
|
|
|
```yaml
|
|
MxGateway:
|
|
Ldap:
|
|
Enabled: true
|
|
Server: localhost
|
|
Port: 3893
|
|
Transport: None # None | StartTls | Ldaps (dev: None)
|
|
AllowInsecure: true # dev only
|
|
SearchBase: "dc=zb,dc=local"
|
|
ServiceAccountDn: "cn=serviceaccount,dc=zb,dc=local"
|
|
ServiceAccountPassword: "serviceaccount123"
|
|
UserNameAttribute: "cn" # GLAuth keys users by cn; AD uses sAMAccountName
|
|
DisplayNameAttribute: "cn"
|
|
GroupAttribute: "memberOf"
|
|
Dashboard:
|
|
GroupToRole:
|
|
GwAdmin: "Administrator"
|
|
GwReader: "Viewer"
|
|
```
|
|
|
|
`Transport` is an `LdapTransport` enum (`None`, `StartTls`, `Ldaps`); it
|
|
replaces the old boolean `UseTls` (`true` ≈ `Ldaps`, `false` = `None`).
|
|
`UserNameAttribute` defaults to `cn` because GLAuth keys users by `cn`
|
|
(`backend.nameformat = "cn"`); only AD needs `sAMAccountName`. The
|
|
group-to-role mapping lives under `MxGateway:Dashboard:GroupToRole`, not
|
|
in the LDAP section, and its values must be dashboard roles
|
|
(`Administrator` or `Viewer`).
|
|
|
|
The shared `ZB.MOM.WW.Auth.Ldap` provider performs the runtime bind and
|
|
search; it returns each group already stripped to its short RDN value
|
|
(e.g. `GwAdmin` from `ou=GwAdmin,ou=groups,dc=zb,dc=local`) before the
|
|
gateway looks it up in `GroupToRole`. Keep `GroupToRole` keys as short
|
|
group names — a full-DN key will never match the short name the provider
|
|
returns.
|
|
|
|
## Provisioning the GwAdmin group
|
|
|
|
`GwAdmin` is the gateway-specific dashboard-admin group, mapped to the
|
|
`Administrator` role through `MxGateway:Dashboard:GroupToRole`. Because
|
|
dashboard login rejects any user who resolves to no role, the dashboard
|
|
cookie login and `DashboardLdapLiveTests`
|
|
(`MXGATEWAY_RUN_LIVE_LDAP_TESTS=1`) reject `admin` until a `GwAdmin`
|
|
group exists, `admin` is a member, and `GroupToRole` maps `GwAdmin` to a
|
|
role. GLAuth's baseline config ships only the five LmxOpcUa role groups,
|
|
so `GwAdmin` must be added to GLAuth rather than run from a separate LDAP
|
|
server:
|
|
|
|
1. Edit `C:\publish\glauth\glauth.cfg`
|
|
2. Append the group:
|
|
|
|
```toml
|
|
[[groups]]
|
|
name = "GwAdmin"
|
|
gidnumber = 5510 # pick the next free GID
|
|
```
|
|
|
|
3. Add `5510` to `admin`'s `othergroups` list so `admin` resolves the
|
|
`GwAdmin` role. Add it to any other user that needs dashboard-admin
|
|
rights. Or create a dedicated user:
|
|
|
|
```toml
|
|
[[users]]
|
|
name = "gwadmin"
|
|
givenname = "Gateway"
|
|
sn = "Admin"
|
|
mail = "gwadmin@lmxopcua.local"
|
|
uidnumber = 5010
|
|
primarygroup = 5510
|
|
passsha256 = "<sha256 of the password — see below>"
|
|
```
|
|
|
|
4. `nssm restart GLAuth`
|
|
|
|
After the restart, `admin`'s `memberOf` includes
|
|
`ou=GwAdmin,ou=groups,dc=zb,dc=local`. The shared LDAP provider strips
|
|
that to the short RDN `GwAdmin`, which the gateway looks up in
|
|
`MxGateway:Dashboard:GroupToRole` to resolve the dashboard role. The same
|
|
pattern applies to any future group that doesn't fit the existing five
|
|
roles — add the group, add the member, and add a `GroupToRole` entry.
|
|
|
|
Generate `passsha256` from a plaintext password:
|
|
|
|
```powershell
|
|
# Windows / PowerShell
|
|
$bytes = [System.Text.Encoding]::UTF8.GetBytes("yourpassword")
|
|
$hash = [System.Security.Cryptography.SHA256]::Create().ComputeHash($bytes)
|
|
-join ($hash | ForEach-Object { $_.ToString("x2") })
|
|
```
|
|
|
|
```bash
|
|
# WSL / git-bash
|
|
echo -n "yourpassword" | openssl dgst -sha256
|
|
```
|
|
|
|
## Quick verification
|
|
|
|
From mxaccessgw's dev box, prove the directory is reachable:
|
|
|
|
```powershell
|
|
# Plain bind via PowerShell + System.DirectoryServices.Protocols
|
|
$ldap = New-Object System.DirectoryServices.Protocols.LdapConnection("localhost:3893")
|
|
$ldap.AuthType = [System.DirectoryServices.Protocols.AuthType]::Basic
|
|
$ldap.SessionOptions.ProtocolVersion = 3
|
|
$ldap.SessionOptions.SecureSocketLayer = $false
|
|
$cred = New-Object System.Net.NetworkCredential("cn=admin,dc=zb,dc=local","admin123")
|
|
$ldap.Bind($cred)
|
|
"Bind OK"
|
|
```
|
|
|
|
Or via `ldapsearch` if you have OpenLDAP CLI tools:
|
|
|
|
```bash
|
|
ldapsearch -x -H ldap://localhost:3893 \
|
|
-D "cn=admin,dc=zb,dc=local" -w admin123 \
|
|
-b "dc=zb,dc=local" "(uid=admin)"
|
|
```
|
|
|
|
The response should list `admin`'s entry with `memberOf` populated for
|
|
all five role groups — plus `GwAdmin` once the gateway-specific group
|
|
is provisioned.
|
|
|
|
## Service management
|
|
|
|
```powershell
|
|
# Status / start / stop / restart
|
|
nssm status GLAuth
|
|
nssm start GLAuth
|
|
nssm stop GLAuth
|
|
nssm restart GLAuth
|
|
|
|
# Inspect what NSSM was told to launch
|
|
nssm get GLAuth Parameters
|
|
```
|
|
|
|
Logs:
|
|
|
|
| File | Purpose |
|
|
|---|---|
|
|
| `C:\publish\glauth\logs\stdout.log` | Bind events, search responses |
|
|
| `C:\publish\glauth\logs\stderr.log` | Startup errors, config parse failures |
|
|
|
|
After editing `glauth.cfg`, always tail `stderr.log` after the restart
|
|
to catch a fat-fingered TOML before it bites at first bind:
|
|
|
|
```powershell
|
|
nssm restart GLAuth
|
|
Get-Content C:\publish\glauth\logs\stderr.log -Tail 20 -Wait
|
|
```
|
|
|
|
## Active Directory migration cheat-sheet
|
|
|
|
These `MxGateway:Ldap` keys change when pointing the gateway at AD
|
|
instead of dev GLAuth:
|
|
|
|
| Field | GLAuth dev value | AD production value |
|
|
|---|---|---|
|
|
| `Server` | `localhost` | a domain controller FQDN, or the domain itself |
|
|
| `Port` | `3893` | `636` (LDAPS) — AD increasingly rejects plain bind under LDAP-signing enforcement |
|
|
| `Transport` | `None` | `Ldaps` (or `StartTls`) |
|
|
| `AllowInsecure` | `true` | `false` |
|
|
| `SearchBase` | `dc=zb,dc=local` | `DC=corp,DC=example,DC=com` |
|
|
| `ServiceAccountDn` | `cn=serviceaccount,dc=zb,dc=local` | `CN=MxGwSvc,OU=Service Accounts,DC=corp,...` |
|
|
| `UserNameAttribute` | `cn` | `sAMAccountName` (or `userPrincipalName`) |
|
|
| `GroupAttribute` | `memberOf` (unchanged) | `memberOf` (unchanged) |
|
|
|
|
`memberOf` returns full DNs; the shared LDAP provider strips each to its
|
|
leading RDN value (`CN=`/`OU=`) and the gateway uses that as the lookup
|
|
key in `MxGateway:Dashboard:GroupToRole`. Nested groups are **not**
|
|
auto-expanded; either flatten in the directory or add a `tokenGroups`
|
|
query as an enhancement.
|
|
|
|
## Security notes for production
|
|
|
|
- **Plaintext passwords in `glauth.cfg` are dev-only.** The config is
|
|
unencrypted on disk; anyone with read access to `C:\publish\glauth\`
|
|
can SHA256-rainbow-table the entries. Treat the dev creds as
|
|
throwaway. Production LDAP is Active Directory.
|
|
- The 3-fail / 10-minute lockout is per source IP, not per user — a
|
|
shared NAT can lock out a whole office. Tunable in `[behaviors]`.
|
|
- LDAPS isn't enabled in dev; binding sends passwords cleartext on the
|
|
wire. Fine for `localhost`, never expose port 3893 off-box without
|
|
enabling TLS first.
|