Files
mxaccessgw/glauth.md
T
Joseph Doherty 96bea1d478 Apply technical-light design system to the gateway dashboard
Restyles the Blazor dashboard onto a portable token-based theme so it
reads like an instrument panel: warm-paper background, hairline-ruled
panels, IBM Plex type, monospace tabular numerics, and status carried by
colour chips. Vendors theme.css + IBM Plex fonts, rewrites dashboard.css
as a thin token-driven view layer, and swaps the Bootstrap navbar and
status badges for the design-system app bar and chips.

Also includes pending API-key management, Galaxy hierarchy projection,
and constraint-enforcement work with their tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:31:04 -04:00

9.5 KiB

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=lmxopcua,dc=local
Bind DN format cn={username},dc=lmxopcua,dc=local
Group OU ou=<groupname>,ou=groups,dc=lmxopcua,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=lmxopcua,dc=local Browse + read OPC UA nodes Browse + Subscribe (read paths only)
WriteOperate 5502 ou=WriteOperate,ou=groups,dc=lmxopcua,dc=local Write FreeAccess / Operate attrs Write (plain)
WriteTune 5504 ou=WriteTune,ou=groups,dc=lmxopcua,dc=local Write Tune attrs WriteSecured (Tune only)
WriteConfigure 5505 ou=WriteConfigure,ou=groups,dc=lmxopcua,dc=local Write Configure attrs WriteSecured (Configure)
AlarmAck 5503 ou=AlarmAck,ou=groups,dc=lmxopcua,dc=local Acknowledge alarms gw alarm-ack RPC, when added

A user can be in multiple groupsothergroups = [...] 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.

Two bind patterns

1. Direct bind (simplest)

DN:       cn=admin,dc=lmxopcua,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=lmxopcua,dc=local
   / serviceaccount123).
2. Search under dc=lmxopcua,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

A YAML/JSON section for mxaccessgw that mirrors LmxOpcUa's LdapOptions record:

ldap:
  enabled: true
  server: localhost
  port: 3893
  useTls: false
  allowInsecureLdap: true       # dev only
  searchBase: "dc=lmxopcua,dc=local"
  serviceAccountDn: "cn=serviceaccount,dc=lmxopcua,dc=local"
  serviceAccountPassword: "serviceaccount123"
  userNameAttribute: "uid"      # GLAuth populates this; AD uses sAMAccountName
  displayNameAttribute: "cn"
  groupAttribute: "memberOf"
  groupToRole:
    ReadOnly: "Browse"
    WriteOperate: "Write"
    WriteTune: "WriteSecured"
    WriteConfigure: "WriteSecured"
    AlarmAck: "AlarmAck"

groupAttribute returns full DNs like ou=ReadOnly,ou=groups,dc=lmxopcua,dc=local — the authenticator should strip the leading ou= (or cn= against AD) RDN value and look that up in groupToRole.

Adding a gw-specific group (when reuse isn't enough)

If mxaccessgw needs a permission that doesn't fit the existing five roles (e.g. GwAdmin for shutdown/recycle commands), add it to GLAuth rather than running a separate LDAP server:

  1. Edit C:\publish\glauth\glauth.cfg
  2. Append:
[[groups]]
  name = "GwAdmin"
  gidnumber = 5510              # pick the next free GID
  1. Add the group to whichever existing user(s) should have it via othergroups = [..., 5510]. Or create a new user:
[[users]]
  name = "gwadmin"
  givenname = "Gateway"
  sn = "Admin"
  mail = "gwadmin@lmxopcua.local"
  uidnumber = 5010
  primarygroup = 5510
  passsha256 = "<sha256 of the password — see below>"
  1. nssm restart GLAuth

Generate passsha256 from a plaintext password:

# Windows / PowerShell
$bytes = [System.Text.Encoding]::UTF8.GetBytes("yourpassword")
$hash  = [System.Security.Cryptography.SHA256]::Create().ComputeHash($bytes)
-join ($hash | ForEach-Object { $_.ToString("x2") })
# WSL / git-bash
echo -n "yourpassword" | openssl dgst -sha256

Quick verification

From mxaccessgw's dev box, prove the directory is reachable:

# 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=lmxopcua,dc=local","admin123")
$ldap.Bind($cred)
"Bind OK"

Or via ldapsearch if you have OpenLDAP CLI tools:

ldapsearch -x -H ldap://localhost:3893 \
  -D "cn=admin,dc=lmxopcua,dc=local" -w admin123 \
  -b "dc=lmxopcua,dc=local" "(uid=admin)"

The response should list admin's entry with memberOf populated for all five role groups.

Service management

# 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:

nssm restart GLAuth
Get-Content C:\publish\glauth\logs\stderr.log -Tail 20 -Wait

Active Directory migration cheat-sheet

LmxOpcUa's LdapOptions xml-doc captures the AD overrides; same set applies to mxaccessgw verbatim. Keys that change:

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
UseTls false true
AllowInsecureLdap true false
SearchBase dc=lmxopcua,dc=local DC=corp,DC=example,DC=com
ServiceAccountDn cn=serviceaccount,dc=lmxopcua,dc=local CN=MxGwSvc,OU=Service Accounts,DC=corp,...
UserNameAttribute uid sAMAccountName (or userPrincipalName)
GroupAttribute memberOf (unchanged) memberOf (unchanged)

memberOf returns full DNs; the authenticator strips the leading CN= value and uses it as the lookup key in 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.