9a87d7a328
Second slice of the 2026-09-09 whole-system review remediation
(docs/plans/2026-09-09-arch-review.md §10), on top of the High slice 146a1323.
Runtime/devices: SR-2 alarm on-trigger integration scope; SR-3 correlated NativeAlarmActor
subscribe retry; SR-4 immutable audited DB target; SR-5 ordered InstanceActor stop/flush;
SR-12 rehydration fence; SR-13 S&F gate recheck per delivery; DC-4 no OPC UA security
downgrade; DC-5 alarm snapshot for every new subscriber; ORCH-1 sync-dispose joins the pump.
Management/UI: MG-2 site scope on audit tree + Flow Debugger Replay; MG-3 stale-probe memo
removed; MG-4 server-owned bulk-deploy deadline; UI-5 sandbox runs in a killable child
process (new CentralUI.SandboxWorker project); UI-14 bounded sandbox output/IPC.
Edge/audit: EI-1 relay audit ownership; EI-2 SkipBodyCapture suppresses error text; EI-3/EI-11
error-field redaction + caps; EI-4 single relay deadline, pre-execution-only retry; EI-5
authenticated relay provenance; EI-10 body cap before audit read.
Persistence/contracts: DL-1 global Instance.UniqueName unique index + conflict translation;
DL-2 DataConnection protocol config encrypted at rest (migration
20260909203046_EnforceGlobalInstanceNamesAndProtectProtocolConfiguration — coordinated
central upgrade, old binaries cannot read protected rows); DL-3 bounded Replay paging;
DL-6 cross-site Overwrite of a deployed instance rejected; XC-4 proto descriptor contract test.
Config/hygiene: XC-1 central listener isolation by local port; XC-2/XC-3 inert SMTP + cron
keys removed from rig settings; CM-2 env2 bootstrap guard enabled; DC-6 deploy site exit code;
DC-7 notifier retries only explicit standby rejection; XC-5 ClusterClient purged from docs.
Gates recorded in the report: build 0/0, 9 397 passed / 0 failed, rig redeployed on all
eight nodes with the migration applied. 46 Low findings remain open.
103 lines
4.8 KiB
JSON
103 lines
4.8 KiB
JSON
{
|
|
"ScadaBridge": {
|
|
"Node": {
|
|
"Role": "Central",
|
|
"NodeName": "central-a",
|
|
"NodeHostname": "scadabridge-env2-central-a",
|
|
"RemotingPort": 8081,
|
|
"_metricsPort": "Register row 56 — mirrors docker/central-node-a. Dedicated HTTP/1.1 Prometheus scrape listener; central binds it only when this key is present. In-container port only, no host mapping.",
|
|
"MetricsPort": 8084
|
|
},
|
|
"Cluster": {
|
|
"SeedNodes": [
|
|
"akka.tcp://scadabridge@scadabridge-env2-central-a:8081",
|
|
"akka.tcp://scadabridge@scadabridge-env2-central-b:8081"
|
|
],
|
|
"SplitBrainResolverStrategy": "auto-down",
|
|
"StableAfter": "00:00:15",
|
|
"HeartbeatInterval": "00:00:02",
|
|
"FailureDetectionThreshold": "00:00:10",
|
|
"MinNrOfMembers": 1,
|
|
"_bootstrapGuard": "Deterministic two-node bootstrap for simultaneous container recreation.",
|
|
"BootstrapGuard": {
|
|
"Enabled": true,
|
|
"PartnerProbeSeconds": 25,
|
|
"PartnerProbeIntervalMs": 500,
|
|
"ProbeConnectTimeoutMs": 1000
|
|
}
|
|
},
|
|
"Database": {
|
|
"_comment": "ConfigurationDb/MachineDataDb (which carry the dev SQL password) are supplied as ScadaBridge__Database__* whole-key env overrides in docker-env2/docker-compose.yml (dev-only-insecure, mirroring docker/docker-compose.yml and the ApiKeyPepper convention). The same dev password already ships committed for the sibling scadabridge-mssql container (infra/docker-compose.yml MSSQL_SA_PASSWORD) — this is consolidation, not new exposure. Env overrides layer over JSON before the ${secret:} expander runs, so the dev cluster boots with no KEK/secret store. Real/prod config uses ${secret:} tokens in src/.../appsettings.Central.json (T4)."
|
|
},
|
|
"Security": {
|
|
"Ldap": {
|
|
"Server": "host.docker.internal",
|
|
"Port": 3893,
|
|
"FallbackServers": [
|
|
"host.docker.internal:3894"
|
|
],
|
|
"_comment_FallbackServers": "Redundant local GLAuth pair (scadaproj/infra/glauth-redundant): zb-ldap-primary :3893 + zb-ldap-backup :3894 on the Docker host — mirrors docker/central-node-a/appsettings.Central.json. Replaces the shared 10.100.0.35 GLAuth, whose serviceaccount password was rotated (SEC-36) away from the rig's dev bind creds; env2 hit this directly (every login failing) before this fix.",
|
|
"Transport": "None",
|
|
"AllowInsecure": true,
|
|
"SearchBase": "dc=zb,dc=local",
|
|
"ServiceAccountDn": "cn=serviceaccount,dc=zb,dc=local",
|
|
"_comment_ServiceAccountPassword": "Supplied as ScadaBridge__Security__Ldap__ServiceAccountPassword env override in docker-env2/docker-compose.yml (dev-only-insecure)."
|
|
},
|
|
"Auth": {
|
|
"DisableLogin": false,
|
|
"User": "multi-role"
|
|
},
|
|
"_comment_JwtSigningKey": "Supplied as ScadaBridge__Security__JwtSigningKey env override in docker-env2/docker-compose.yml (dev-only-insecure).",
|
|
"JwtExpiryMinutes": 15,
|
|
"IdleTimeoutMinutes": 30,
|
|
"RequireHttpsCookie": false,
|
|
"CookieName": "ZB.MOM.WW.ScadaBridge.Auth.env2",
|
|
"ForwardedHeaders": {
|
|
"_comment": "Traefik fronts this node on the external scadabridge-net docker network. Trust X-Forwarded-For from the docker bridge address pool so LoginThrottle keys on the real client IP (arch-review R2 N2). Narrow to the Traefik container IP if the network is pinned.",
|
|
"Enabled": true,
|
|
"KnownNetworks": [ "172.16.0.0/12" ]
|
|
}
|
|
},
|
|
"Communication": {
|
|
"DeploymentTimeout": "00:02:00",
|
|
"LifecycleTimeout": "00:00:30",
|
|
"QueryTimeout": "00:00:30",
|
|
"TransportHeartbeatInterval": "00:00:05",
|
|
"TransportFailureThreshold": "00:00:15",
|
|
"CentralFetchBaseUrl": "http://scadabridge-env2-traefik"
|
|
},
|
|
"ManagementService": {
|
|
"CommandTimeout": "00:00:30",
|
|
"LongRunningCommandTimeout": "00:05:00"
|
|
},
|
|
"HealthMonitoring": {
|
|
"ReportInterval": "00:00:30",
|
|
"OfflineTimeout": "00:01:00"
|
|
},
|
|
"InboundApi": {
|
|
"DefaultMethodTimeout": "00:00:30",
|
|
"_standbyForwarding": "2026-08-28 — a standby central node relays POST /api/{methodName} to the active peer instead of answering 503 STANDBY_NODE. List the OTHER node only.",
|
|
"StandbyForwardPeerEndpoints": [ "http://scadabridge-env2-central-b:5000" ]
|
|
},
|
|
"Notification": {
|
|
"_comment": "SMTP delivery configuration is stored in the database; manage it with notification smtp or /notifications/smtp."
|
|
},
|
|
"NotificationOutbox": {
|
|
"DispatchInterval": "00:00:05",
|
|
"DispatchBatchSize": 1000
|
|
},
|
|
"KpiHistory": {
|
|
"SampleInterval": "00:01:00",
|
|
"RetentionDays": 90,
|
|
"PurgeInterval": "1.00:00:00",
|
|
"DefaultMaxSeriesPoints": 200
|
|
},
|
|
"Transport": {
|
|
"SourceEnvironment": "docker-cluster-env2"
|
|
},
|
|
"Logging": {
|
|
"MinimumLevel": "Information"
|
|
}
|
|
}
|
|
}
|