Files
ScadaBridge/docs/former-api-specs/mes/authgaps.md
T
Joseph Doherty 8a78e759c0 docs: former-api-specs (MES + DNC/Delmia) + inbound compile-error known issue
- former-api-specs/mes: Alarm-API, MoveIn-MoveOut-API, API-key authgaps (from ~/Desktop/mesapi)
- former-api-specs/dnc: Delmia-Integration-API — Delmia document service + WW recipe-download notify (from ~/Desktop/delmiaintegration)
- known-issues: inbound API compile error not client-visible; no api-method validate
2026-06-26 04:13:19 -04:00

6.7 KiB
Raw Blame History

WWSupport MES API — API key authentication: support & gaps

How the legacy WWSupport / APIServer ServiceStack service authenticates API-key callers, and the API-key-specific gaps in it as of 2026-06-25 (the state of the ~/Desktop/mesapi copy). Captured as reference for the ScadaBridge migration so the replacement Inbound API does not inherit these weaknesses.

Scope: defensive review of an internally-owned legacy service — weaknesses and remediations, no exploit steps. Limited to API-key auth (other auth/config concerns are out of scope here).

Source files: APIServer/AppHost.cs, APIServer.ServiceInterface/MesServices.cs, APIServer/App.config.


What's supported (API key)

  • ApiKeyAuthProvider is registered in the host AuthFeature (AppHost.cs:63-70) with SessionCacheDuration = 30 min and RequireSecureConnection = false.

  • Keys are DB-backed. Users, roles, and keys live in SQL via OrmLiteAuthRepository (UseDistinctRoleTables = true); InitSchema() creates the tables at startup (AppHost.cs:53-59). There is no API-key config in App.config — keys are issued/stored by the ServiceStack auth repository.

  • Authorization. A valid key authenticates the request; the key's user must also hold the MESAPI role to reach any operation (MesServices.cs:6-8):

    [Authenticate]
    [RequiredRole("MESAPI")]
    public class MesServices : Service { ... }   // movein, moveout, alarmstatus, simplealarmstatus
    
  • Transports for the key (ServiceStack defaults, nothing overridden): Authorization: Bearer <key>, HTTP Basic with the key as the username, or — since AllowInHttpParams defaults on — ?apikey=<key> in the query string/form.

  • Per-key environment tag. A key whose Environment == "test" is routed to a TestDb connection instead of production (AppHost.GetDbConnection, AppHost.cs:102-108).

  • Transport. The listener is plain HTTP — http://*:9501/ (DEV) / http://*:9500/ (QA/PROD) (App.config:57,68,80); no HTTPS listener is configured (relevant to gap #1).


Gaps & risks (worst first)

1. 🟠 High — the key is exposed in transit and in logs

Three settings compound:

  • No TLS — plain-HTTP listeners (App.config:57) and RequireSecureConnection = false (AppHost.cs:69), so the key crosses the network in cleartext to any on-path observer.
  • Key accepted in the URLAllowInHttpParams (default on) makes ?apikey=<key> valid, so keys land in proxy logs and browser history.
  • Request logging persists it — the enabled RequestLogsFeature writes request data to a CSV on disk (AppHost.cs:79-86), so a query-string key can be written to the log file.

Fix: terminate TLS and set RequireSecureConnection = true; set ApiKeyAuthProvider { AllowInHttpParams = false } so keys must travel in the Authorization header; redact auth fields from the request log and restrict the log file's ACLs.

2. 🟡 Medium — keys are not scoped to methods

A valid key + the MESAPI role grants access to every [RequiredRole("MESAPI")] endpoint (move-in, move-out, both alarm reads). There is no per-key allow-list of methods, so one leaked integration key exposes the entire MES surface.

Fix: scope keys per integration (separate roles/keys for read vs. move-in vs. move-out), the way the ScadaBridge Inbound API does (keys carry an explicit method allow-list).

3. 🟢 LowMedium — the per-key test redirect is half-wired

GetDbConnection opens a "TestDb" connection for Environment == "test" keys (AppHost.cs:104-106), but no TestDb connection string is defined in App.config — such requests would throw. Separately, the move-out cycle-data read uses a raw ConnectionStrings["BatchDB"] connection (see MoveIn-MoveOut-API.md), so it ignores the redirect and reads production data even with a test key.

Fix: define TestDb (or remove the redirect), and route the raw cycle-data read through the same environment-aware connection so a test key never touches production.


Remediation checklist (priority order)

  • Enforce TLS + RequireSecureConnection = true; stop accepting the key in the URL (AllowInHttpParams = false); redact/secure the request logs (#1).
  • Scope API keys per method/integration instead of one coarse MESAPI role (#2).
  • Fix or remove the testTestDb redirect and make the raw cycle-data read environment-aware (#3).

Contrast: ScadaBridge Inbound API

ScadaBridge uses an X-API-Key header on the data plane (POST /api/{method}), validated server-side, with each key scoped to an explicit method allow-list — versus one coarse MESAPI role granting all endpoints here. This file documents the legacy API-key posture so that difference is intentional and verifiable during cutover.

Accepted auth transports

Transport mesapi (ServiceStack ApiKeyAuthProvider) ScadaBridge Inbound API
Authorization: Bearer <key> Bearer sbk_<keyId>_<secret> (the Bearer prefix is optional; a bare token in Authorization also works)
Authorization: Basic <base64("<key>:")> (key as username)
X-API-Key: <key> (not supported by stock ServiceStack) — raw token sbk_<keyId>_<secret>
?apikey=<key> (query string / form param) (AllowInHttpParams defaults on) — headers only
Session cookie after first auth (ss-id/ss-pid) (30-min SessionCacheDuration) — stateless; every request re-presents the key

Evidence: mesapi is stock new ApiKeyAuthProvider(AppSettings) with no header customization (so ServiceStack defaults apply); ScadaBridge logic is EndpointExtensions.cs:83-95.

Notes:

  • The only common header is Authorization: Bearer — the portable choice for a client that must talk to both.
  • X-API-Key is ScadaBridge-only; Basic and ?apikey= are mesapi-only. A curl -H "X-API-Key: …" authenticates ScadaBridge but is rejected by mesapi.
  • Precedence when two are sent: ScadaBridge — Authorization wins over X-API-Key; mesapi — ServiceStack checks Authorization (Bearer/Basic) before the apikey param.
  • mesapi is looser, ScadaBridge is tighter: mesapi accepts the key in the URL and over a long-lived session cookie (more leak surface — see gap #1); ScadaBridge restricts to two headers, no URL param, no session.
  • Token shape & verification: mesapi keys are opaque ServiceStack keys checked against the auth repo; ScadaBridge keys are structured sbk_<keyId>_<secret> verified by a peppered-HMAC constant-time compare and scoped to specific method names (case-sensitive).