Files
mxaccessgw/docs/GatewayConfiguration.md
T
Joseph Doherty e57d864ab2 fix(dashboard): make dashboard auth cookie name configurable
The dashboard auth cookie name was hardcoded to the constant
DashboardAuthenticationDefaults.CookieName (MxGatewayDashboard). Browser
cookies are scoped by host+path but NOT by port, so two gateway instances
sharing a hostname would clobber each other's dashboard session under the
shared name.

Add DashboardOptions.CookieName (MxGateway:Dashboard:CookieName); null/blank
keeps the canonical default. Applied in the existing dashboard cookie
PostConfigure (runs after the inline AddCookie default, so it wins). Behaviour
is unchanged when unset. Adds a Tests case for the override.
2026-06-03 13:11:29 -04:00

24 KiB
Raw Blame History

Gateway Configuration

This document describes every option bound under the MxGateway configuration section by GatewayOptions.

The gateway binds configuration at startup and validates it with GatewayOptionsValidator. Startup fails before the server listens when required paths, timeouts, queue sizes, enum values, or protocol values are invalid.

Configuration Shape

{
  "MxGateway": {
    "Authentication": {
      "Mode": "ApiKey",
      "SqlitePath": "C:\\ProgramData\\MxGateway\\gateway-auth.db",
      "PepperSecretName": "MxGateway:ApiKeyPepper",
      "RunMigrationsOnStartup": true
    },
    "Worker": {
      "ExecutablePath": "src\\ZB.MOM.WW.MxGateway.Worker\\bin\\x86\\Release\\ZB.MOM.WW.MxGateway.Worker.exe",
      "WorkingDirectory": null,
      "RequiredArchitecture": "X86",
      "StartupTimeoutSeconds": 30,
      "StartupProbeRetryAttempts": 3,
      "StartupProbeRetryDelayMilliseconds": 250,
      "PipeConnectAttemptTimeoutMilliseconds": 2000,
      "ShutdownTimeoutSeconds": 10,
      "HeartbeatIntervalSeconds": 5,
      "HeartbeatGraceSeconds": 15,
      "MaxMessageBytes": 16777216
    },
    "Sessions": {
      "DefaultCommandTimeoutSeconds": 30,
      "MaxSessions": 64,
      "MaxPendingCommandsPerSession": 128,
      "DefaultLeaseSeconds": 1800,
      "LeaseSweepIntervalSeconds": 30,
      "AllowMultipleEventSubscribers": false
    },
    "Events": {
      "QueueCapacity": 10000,
      "BackpressurePolicy": "FailFast"
    },
    "Dashboard": {
      "Enabled": true,
      "AllowAnonymousLocalhost": true,
      "RequireHttpsCookie": true,
      "SnapshotIntervalMilliseconds": 1000,
      "RecentFaultLimit": 100,
      "RecentSessionLimit": 200,
      "ShowTagValues": false,
      "GroupToRole": {
        "GwAdmin": "Admin",
        "GwReader": "Viewer"
      }
    },
    "Protocol": {
      "WorkerProtocolVersion": 1,
      "MaxGrpcMessageBytes": 16777216
    },
    "Galaxy": {
      "ConnectionString": "Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;",
      "CommandTimeoutSeconds": 60,
      "DashboardRefreshIntervalSeconds": 30,
      "PersistSnapshot": true,
      "SnapshotCachePath": "C:\\ProgramData\\MxGateway\\galaxy-snapshot.json"
    },
    "Alarms": {
      "Enabled": false,
      "SubscriptionExpression": "",
      "DefaultArea": "",
      "ReconcileIntervalSeconds": 30
    }
  }
}

Environment variables use the normal .NET double-underscore form. For example, MxGateway__Sessions__MaxSessions=20 overrides MxGateway:Sessions:MaxSessions.

Authentication Options

Option Default Description
MxGateway:Authentication:Mode ApiKey Selects public gRPC authentication. Supported values are ApiKey and Disabled. Disabled bypasses API-key verification and is for local development only.
MxGateway:Authentication:SqlitePath C:\ProgramData\MxGateway\gateway-auth.db SQLite database path for API-key records and audit rows when API-key authentication is enabled.
MxGateway:Authentication:PepperSecretName MxGateway:ApiKeyPepper Configuration key used to read the HMAC pepper for API-key secret hashing. The dashboard effective configuration redacts this value.
MxGateway:Authentication:RunMigrationsOnStartup true Runs SQLite auth schema migrations at gateway startup when API-key authentication is enabled.

When Mode is ApiKey, SqlitePath and PepperSecretName must be present. SqlitePath must be a valid filesystem path.

Worker Options

Option Default Description
MxGateway:Worker:ExecutablePath src\ZB.MOM.WW.MxGateway.Worker\bin\x86\Release\ZB.MOM.WW.MxGateway.Worker.exe Path to the x86 worker executable launched for each gateway session. The path must be valid and point to a .exe file.
MxGateway:Worker:WorkingDirectory null Optional working directory for the worker process. When set, it must be a valid filesystem path.
MxGateway:Worker:RequiredArchitecture X86 Required Portable Executable architecture for the worker. Supported values are X86 and X64; MXAccess parity uses X86.
MxGateway:Worker:StartupTimeoutSeconds 30 Total startup budget for process launch, startup probe, pipe connect, handshake, and worker readiness.
MxGateway:Worker:StartupProbeRetryAttempts 3 Number of retry attempts for transient worker startup probe failures before pipe connection and handshake continue.
MxGateway:Worker:StartupProbeRetryDelayMilliseconds 250 Delay between transient startup probe retry attempts.
MxGateway:Worker:PipeConnectAttemptTimeoutMilliseconds 2000 Per-attempt timeout used by the worker named-pipe connect retry path. The overall pipe connection still stays under the startup budget.
MxGateway:Worker:ShutdownTimeoutSeconds 10 Grace period for worker shutdown before the gateway treats shutdown as failed and may kill the worker process tree.
MxGateway:Worker:HeartbeatIntervalSeconds 5 Worker heartbeat send interval and gateway heartbeat check cadence input.
MxGateway:Worker:HeartbeatGraceSeconds 15 Maximum age of the last worker heartbeat before the gateway faults the worker. This must be greater than or equal to HeartbeatIntervalSeconds.
MxGateway:Worker:MaxMessageBytes 16777216 Maximum worker IPC frame payload size in bytes. The validator allows values from 1024 through 268435456.

StartupProbeRetryAttempts, StartupProbeRetryDelayMilliseconds, PipeConnectAttemptTimeoutMilliseconds, timeout values, heartbeat values, and MaxMessageBytes must be positive. MaxMessageBytes is intentionally bounded to avoid accidental large allocations from malformed or oversized frames.

Session Options

Option Default Description
MxGateway:Sessions:DefaultCommandTimeoutSeconds 30 Default timeout used while the gateway waits for a worker command reply when an open-session request does not provide a positive command timeout.
MxGateway:Sessions:MaxSessions 64 Maximum number of concurrently open gateway sessions. Session opens reserve a slot atomically before worker creation.
MxGateway:Sessions:MaxPendingCommandsPerSession 128 Maximum number of pending worker commands for one session. Excess commands fail fast instead of queueing indefinitely.
MxGateway:Sessions:DefaultLeaseSeconds 1800 Initial session lease and refresh duration. Unary client activity extends the lease by this duration.
MxGateway:Sessions:LeaseSweepIntervalSeconds 30 Hosted monitor interval for closing expired leases. Active event-stream subscribers keep a session from expiring while the stream remains attached.
MxGateway:Sessions:AllowMultipleEventSubscribers false Controls whether multiple StreamEvents subscribers may attach to one session. true is rejected until event fan-out is implemented.

All numeric session options must be greater than zero. The current event stream implementation supports one active subscriber per session; this preserves event ordering and avoids competing consumers.

Event Options

Option Default Description
MxGateway:Events:QueueCapacity 10000 Capacity for bounded per-session event queues used by the gateway worker event channel and the public gRPC event stream queue.
MxGateway:Events:BackpressurePolicy FailFast Event backpressure behavior. FailFast faults the session on public stream queue overflow. DisconnectSubscriber disconnects only the slow stream.

QueueCapacity must be greater than zero. With FailFast, queue overflow faults the affected worker or session instead of silently dropping MXAccess events. With DisconnectSubscriber, public gRPC stream overflow terminates only the affected stream while the MXAccess session remains active.

Dashboard Options

Option Default Description
MxGateway:Dashboard:Enabled true Enables Blazor Server dashboard route mapping. The dashboard mounts at the host root (/); there is no separate path-base prefix.
MxGateway:Dashboard:AllowAnonymousLocalhost true Allows loopback dashboard requests to bypass the dashboard cookie requirement for local development. Remote requests still require dashboard authentication.
MxGateway:Dashboard:RequireHttpsCookie true Sets the dashboard auth cookie's secure policy. true keeps CookieSecurePolicy.Always — the cookie is only sent over HTTPS, which matches a production HTTPS deployment. Set to false for plain-HTTP dev deployments to use CookieSecurePolicy.SameAsRequest; the cookie is still flagged Secure on HTTPS requests, but it can round-trip over HTTP. Browsers drop Secure cookies set over HTTP from non-localhost hosts, so leaving this true while serving the dashboard over plain HTTP will break login from any remote browser.
MxGateway:Dashboard:CookieName MxGatewayDashboard Dashboard auth cookie name. Leave unset (null/blank) to use the default. Override it to give a distinct name to a gateway that shares a hostname with another gateway instance: browser cookies are scoped by host+path but not by port, so two instances on the same host would otherwise clobber each other's dashboard session under a shared cookie name. Changing it signs out existing dashboard sessions on next deploy.
MxGateway:Dashboard:SnapshotIntervalMilliseconds 1000 Dashboard snapshot refresh interval used by the snapshot SignalR hub and the pages that subscribe to it.
MxGateway:Dashboard:RecentFaultLimit 100 Maximum number of fault summaries projected into each dashboard snapshot.
MxGateway:Dashboard:RecentSessionLimit 200 Maximum number of session summaries projected into each dashboard snapshot.
MxGateway:Dashboard:ShowTagValues false Reserved display control for tag values. The dashboard does not show full tag values by default.
MxGateway:Dashboard:GroupToRole (empty) LDAP group → dashboard role mapping. Keys are LDAP group names (short CN or full DN — leading-RDN match). Values must be Admin (read/write, API-key CRUD) or Viewer (read-only). A user whose LDAP groups don't intersect this map cannot sign in; with no mapping at all, only the loopback bypass admits anyone.

SnapshotIntervalMilliseconds must be greater than zero. RecentFaultLimit and RecentSessionLimit must be greater than or equal to zero. GroupToRole values are validated at startup; invalid role names fail validation. Emptiness is allowed (a closed deployment that admits no LDAP users) but practical deployments populate at least one Admin group.

Authorization policies

Three authorization policies are registered out of these options:

  • MxGateway.Dashboard.Viewer — gates the Razor component routes. Satisfied by either dashboard role (Admin or Viewer), by AllowAnonymousLocalhost on loopback, or by Authentication.Mode = Disabled.
  • MxGateway.Dashboard.Admin — gates write-capable surfaces (API-key CRUD). Satisfied only by the Admin role (same environmental bypasses).
  • MxGateway.Dashboard.HubClients — attached to the SignalR hubs. Accepts either the dashboard cookie scheme or the MxGateway.Dashboard.HubToken bearer scheme (used by SignalR's WebSocket upgrade path where the HttpOnly cookie can't be forwarded).

SignalR hubs

When the dashboard is enabled, three hubs are mapped under /hubs/*:

  • GET /hubs/snapshot — pushes DashboardSnapshot whenever the snapshot service produces a new one. Drives every page that inherits DashboardPageBase; replaces the earlier polling loop.
  • GET /hubs/alarms — re-broadcasts the AlarmFeedMessage stream from the central alarm monitor to all connected clients (group __alarms__).
  • GET /hubs/events — per-session MxEvent feed. Clients call SubscribeSession(sessionId) to join session:{id}. Events are mirrored from the corresponding gRPC StreamEvents call as a fire-and-forget side-effect; the dashboard only sees events while a gRPC client is also subscribed to that session.

GET /hubs/token (cookie-only) mints a 30-minute data-protected bearer token for the calling user; the Blazor pages use it via DashboardHubConnectionFactory to authenticate the SignalR connection.

Protocol Options

Option Default Description
MxGateway:Protocol:WorkerProtocolVersion 1 Worker IPC protocol version expected by the gateway and worker. This must match GatewayContractInfo.WorkerProtocolVersion.
MxGateway:Protocol:MaxGrpcMessageBytes 16777216 Public gRPC max send and receive message size in bytes. The same default is used by official clients. The validator allows values from 1024 through 268435456.

The protocol option is exposed for diagnostics and explicit deployment configuration, not for compatibility negotiation. A mismatch fails validation at startup.

Galaxy Options

Option Default Description
MxGateway:Galaxy:ConnectionString Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False; SQL Server connection string for the Galaxy Repository (ZB) used by the GalaxyRepository browse RPCs. Override in production via MxGateway__Galaxy__ConnectionString.
MxGateway:Galaxy:CommandTimeoutSeconds 60 Per-command SQL timeout for all Galaxy browse RPCs.
MxGateway:Galaxy:DashboardRefreshIntervalSeconds 30 Interval between background refreshes of the dashboard Galaxy summary cache. SQL is hit at most once per interval regardless of dashboard render rate.
MxGateway:Galaxy:PersistSnapshot true Persists the latest successful Galaxy browse dataset to disk. When true, the cache reloads that snapshot at startup so clients can still browse last-known data while the Galaxy database is unreachable. The restored data is served with Stale status until a live query confirms it.
MxGateway:Galaxy:SnapshotCachePath C:\ProgramData\MxGateway\galaxy-snapshot.json File path for the persisted Galaxy browse snapshot. Ignored when PersistSnapshot is false. The snapshot is written atomically (temp file plus rename).

See Galaxy Repository Browse for the RPC surface and behavior.

Alarm Options

Option Default Description
MxGateway:Alarms:Enabled false Gates the gateway's always-on central alarm monitor. When true, the gateway opens one gateway-owned worker session dedicated to alarms, caches the active-alarm set, and fans it out to every client through the StreamAlarms RPC — no client opens its own session to see alarms.
MxGateway:Alarms:SubscriptionExpression (empty) AVEVA alarm-subscription expression the monitor subscribes on startup, in canonical \\<machine>\Galaxy!<area> form. The literal Galaxy provider is correct regardless of the Galaxy database name. When empty and Enabled is true, the gateway falls back to \\<MachineName>\Galaxy!<DefaultArea> if DefaultArea is set.
MxGateway:Alarms:DefaultArea (empty) Area name used to compose a default subscription when SubscriptionExpression is empty. If both are empty while Enabled is true, the monitor faults with a configuration diagnostic.
MxGateway:Alarms:ReconcileIntervalSeconds 30 How often the monitor reconciles its in-process alarm cache against the worker's authoritative active-alarm snapshot, catching transitions the live poll-and-diff feed missed. Floored at 5 seconds.

The alarm monitor is independent of client sessions: AcknowledgeAlarm and StreamAlarms are session-less RPCs served by the monitor.

Host Endpoints and Transport Security (Kestrel)

The listening endpoints are not part of the MxGateway section. The gateway uses the stock ASP.NET Core host (WebApplication.CreateBuilder) with no ConfigureKestrel call in code, so endpoints come entirely from the standard Kestrel configuration section. On the deployed hosts these values are supplied as NSSM environment variables (Kestrel__Endpoints__...), not from appsettings.json.

Two named endpoints are bound:

Endpoint name Purpose Protocol requirement
Http Public gRPC API (sessions, invoke, events, Galaxy browse) HTTP/2
Dashboard Blazor dashboard and SignalR hubs HTTP/1.1 (HTTP/2 optional)

Both endpoints share one routing pipeline; the names only select which TCP port serves which traffic. The gRPC endpoint must negotiate HTTP/2, which drives the protocol settings below.

Plaintext (current deployments)

Both running hosts (10.100.0.48 and wonder-app-vd03) serve the gRPC port in cleartext HTTP/2 (h2c). Because cleartext HTTP/2 has no ALPN to negotiate the protocol, the gRPC endpoint must be pinned to Http2 with prior knowledge:

Kestrel__Endpoints__Http__Url=http://0.0.0.0:5120
Kestrel__Endpoints__Http__Protocols=Http2
Kestrel__Endpoints__Dashboard__Url=http://0.0.0.0:5130

In this mode all client↔gateway traffic — including the authorization: Bearer mxgw_... API key and any WriteSecured / AuthenticateUser payloads — crosses the network unencrypted. This is acceptable only on a trusted/isolated network segment. Prefer TLS for anything else.

TLS

To encrypt the gRPC channel, give the Http endpoint an https:// URL and a certificate. Over TLS, ALPN negotiates HTTP/2, so the explicit Protocols=Http2 pin is no longer required (the default Http1AndHttp2 works for gRPC over TLS).

appsettings.json form:

{
  "Kestrel": {
    "Endpoints": {
      "Http": {
        "Url": "https://0.0.0.0:5120",
        "Certificate": {
          "Path": "C:\\ProgramData\\MxGateway\\certs\\gateway.pfx",
          "Password": "<pfx-password>"
        }
      },
      "Dashboard": {
        "Url": "https://0.0.0.0:5130",
        "Certificate": {
          "Path": "C:\\ProgramData\\MxGateway\\certs\\gateway.pfx",
          "Password": "<pfx-password>"
        }
      }
    }
  }
}

Equivalent NSSM environment-variable form (how config is delivered on the hosts — see [server deploy mechanics in the project notes]):

Kestrel__Endpoints__Http__Url=https://0.0.0.0:5120
Kestrel__Endpoints__Http__Certificate__Path=C:\ProgramData\MxGateway\certs\gateway.pfx
Kestrel__Endpoints__Http__Certificate__Password=<pfx-password>
Kestrel__Endpoints__Dashboard__Url=https://0.0.0.0:5130
Kestrel__Endpoints__Dashboard__Certificate__Path=C:\ProgramData\MxGateway\certs\gateway.pfx
Kestrel__Endpoints__Dashboard__Certificate__Password=<pfx-password>

Certificate sourcing options (any standard ASP.NET Core form is accepted):

Form Keys
PFX file Certificate:Path (+ Certificate:Password if encrypted)
PEM pair Certificate:Path (cert) + Certificate:KeyPath (private key)
Windows cert store Certificate:Subject, Certificate:Store (e.g. My), Certificate:Location (LocalMachine), Certificate:AllowInvalid

The certificate's CN/SAN must cover the host name clients dial (or clients must set a server-name override — see below). The dashboard endpoint can keep its own certificate independent of the gRPC endpoint; pair this with MxGateway:Dashboard:RequireHttpsCookie (true) for production HTTPS.

Automatic self-signed certificate

mxaccessgw is an internal tool with no PKI to issue certificates, so requiring an operator to supply one before TLS works pushed deployments toward plaintext. To avoid that, the gateway fills in a self-signed certificate when an HTTPS endpoint is configured without one.

Trigger. At startup the gateway inspects Kestrel:Endpoints:*. If any endpoint has an https:// URL and no Certificate subsection of its own, and no Kestrel:Certificates:Default is set, the gateway generates (or loads) a persisted self-signed certificate and wires it in as the HTTPS default via ConfigureHttpsDefaults. All-plaintext deployments are untouched: when no HTTPS endpoint is configured, no certificate or key material is generated or written.

Generated certificate. ECDSA P-256, serverAuth EKU, validity ≈ ValidityYears (default 10 years, with one day of clock-skew slack before notBefore). SANs cover localhost, the machine name (and its FQDN when resolvable), each entry in AdditionalDnsNames, and the loopback addresses 127.0.0.1 and ::1.

MxGateway:Tls:* options. All optional; the zero-config path needs none of them.

Option Default Purpose
Tls:SelfSignedCertPath C:\ProgramData\MxGateway\certs\gateway-selfsigned.pfx Where the generated certificate is persisted
Tls:ValidityYears 10 Lifetime of the generated certificate (validated 1100)
Tls:AdditionalDnsNames [] Extra DNS SANs (e.g. a load-balancer name)
Tls:RegenerateIfExpired true Replace an expired persisted certificate instead of failing

ValidityYears is validated by GatewayOptionsValidator (range 1100); the "HTTPS endpoint configured but no certificate available" fail-fast lives in the bootstrap/provider, because the validator only sees the MxGateway section, not Kestrel:Endpoints.

Persistence. The PFX is written with an empty export password — a random in-memory password could not be reused across restarts, which the persist-and-reuse model requires. The private key is instead protected at rest by filesystem permissions: a restrictive ACL on Windows (SYSTEM + Administrators, inherited ACEs stripped) on the certs directory and file, and mode 0600 on non-Windows. The write is atomic (hardened temp file, then move). The persisted certificate is reused across restarts (stable thumbprint, so CA-pinning clients keep working) and regenerated only when it is missing, expired (and RegenerateIfExpired is true), or unreadable/corrupt. If the directory is not writable or the ACL cannot be applied, the gateway fails fast with a diagnostic naming the path rather than falling back to an in-memory certificate.

Logging. On generate or load, the gateway logs the certificate thumbprint, SAN list, and notAfter at Information. The PFX bytes, export password, and private key are never logged.

Operator override. The generated certificate is only the HTTPS default. To use a real certificate, configure one explicitly — either per endpoint via Kestrel:Endpoints:<name>:Certificate (Path/Subject/Thumbprint, etc., as in the table above) or globally via Kestrel:Certificates:Default. An explicitly-configured certificate takes precedence, and the gateway then writes no self-signed material.

Client side

Each official client opts into TLS explicitly. For the .NET client (MxGatewayClientOptions):

Option Effect
UseTls (default false) Enables TLS. Requires an https:// endpoint; an https:// endpoint without UseTls fails validation, and vice versa.
CaCertificatePath Pins a custom root (self-signed / private CA) using CustomRootTrust chain validation instead of the OS trust store; the .NET client also enforces the certificate hostname/SAN match on this path.
RequireCertificateValidation (default false) Forces OS/system-trust verification on a TLS connection with no pinned CA. Leave false for the lenient default.
ServerNameOverride SNI / certificate host name override when the dialed host differs from the certificate CN/SAN.

To pair with the auto-generated self-signed certificate above, the clients are lenient by default: a TLS connection with no pinned CA accepts whatever certificate the gateway presents. Pin CaCertificatePath to verify, or set RequireCertificateValidation to force system-trust verification without pinning. The other language clients expose the equivalent options; the exact behavior differs per stack — Python uses trust-on-first-use and Rust is pin-only. See each client README for the as-built behavior.

Gateway↔worker IPC

Transport security here applies only to the public gRPC channel. The gateway↔worker link is a per-session named pipe (mxaccess-gateway-{gatewayPid}-{sessionId}), not a network socket. It is not TLS-encrypted and does not need to be: it never leaves the local Windows host and is secured by the OS pipe ACL. See Worker Frame Protocol.