docs(env2): live-gate docker-env2 on the gRPC PSK build — PASS 3/3 (Gitea #31)
Rebuilt scadabridge:latest from main @ 8524a7f7 and recreated only the
env2 containers. All three gate checks pass on site-x:
1. both site nodes boot with the key (StartupValidator fail-closed →
reaching 'Application started' proves the key present);
2. control-plane PSK auth: no-header / wrong-key ⇒ PermissionDenied,
correct key ⇒ success, on both nodes (:9123, :9124);
3. LocalDb unaffected (local-only; 0 errors, healthy boot).
Bonus: central registers site-x online via gRPC heartbeat; no real
ClusterClient/receptionist (only the benign ClusterClientSiteAuditClient
label, same as the primary rig). Noted a seed-data gap (ScadaBridgeConfig2
dbo.Sites is empty) — orthogonal to the transport.
This commit is contained in:
@@ -475,5 +475,68 @@ default (Phase 4 flipped the shipped appsettings to `CentralGrpcEndpoints` — u
|
||||
redeploy from `main` now yields all-gRPC, not all-Akka). No ClusterClient remains anywhere in the
|
||||
running system or the source tree. Deferred, unchanged from earlier phases: the instance-dependent
|
||||
central→site RPC matrix (`ExecuteOpcUa`/`ExecuteRoute`/standby parked retry) and live
|
||||
`TriggerFailover`, all unit-proven; PSK **rotation** on a live pair; `docker-env2` was updated with
|
||||
its own key but not gated here.
|
||||
`TriggerFailover`, all unit-proven; PSK **rotation** on a live pair. `docker-env2` is now gated —
|
||||
see below.
|
||||
|
||||
---
|
||||
|
||||
## Env2 gate — `docker-env2` on the gRPC PSK build — **PASS** (2026-07-23, Gitea #31)
|
||||
|
||||
The secondary Transport-testing topology (`docker-env2/`: 2 central + 1 site-x × 2 nodes, host
|
||||
ports 91XX, shared infra) carried its `GrpcPsk` in config since Phase 0 but had never been
|
||||
redeployed or gated against the migration build. Rebuilt `scadabridge:latest` from `main` @
|
||||
`8524a7f7` (includes the #28 Transport execution-strategy fix) and recreated only the env2
|
||||
containers (`bash docker-env2/deploy.sh`; the primary rig's running image was untouched). Site-x
|
||||
PSK `dev-grpc-psk-docker-env2-site-x` on both site nodes matches central's
|
||||
`ScadaBridge__Communication__SitePsks__site-x`.
|
||||
|
||||
| # | Check | Result |
|
||||
|---|-------|--------|
|
||||
| 1 | Both site nodes boot with the key (StartupValidator passes) | **PASS** |
|
||||
| 2 | Unauthenticated control-plane call ⇒ PermissionDenied; correct key ⇒ success | **PASS** |
|
||||
| 3 | LocalDb unaffected | **PASS** |
|
||||
|
||||
### Check 1 — both site nodes boot
|
||||
|
||||
`scadabridge-env2-site-x-a` and `-b` both reached `Application started` with `Now listening on:
|
||||
http://[::]:8083` (the gRPC control/data listener). `StartupValidator` is fail-closed on a missing
|
||||
`ScadaBridge:Communication:GrpcPsk` — a site node without it refuses to boot — so reaching
|
||||
`Application started` is itself the proof the key is present and valid. Each node also logged a clean
|
||||
`Reconcile pass for site site-x node node-{a,b} complete: 0 fetched, 0 failed, 0 orphan(s)`, i.e.
|
||||
the site→central gRPC path was already carrying traffic.
|
||||
|
||||
### Check 2 — PSK auth on the control plane
|
||||
|
||||
`grpcurl` against the site-hosted `SiteStreamService/PullAuditEvents` on both nodes
|
||||
(`localhost:9123` = site-x-a, `localhost:9124` = site-x-b):
|
||||
|
||||
- **No auth header** ⇒ `PermissionDenied: Control plane authentication failed.`
|
||||
- **Wrong bearer key** (`Bearer WRONG-KEY`, `x-scadabridge-site: site-x`) ⇒ `PermissionDenied`.
|
||||
- **Correct key** (`Bearer dev-grpc-psk-docker-env2-site-x`) ⇒ `{}` (empty audit set — the env has
|
||||
no S&F driver seeded — but **not** a permission error: auth accepted). Identical on both nodes.
|
||||
|
||||
(Same contract clarification as the primary rig: the site interceptor authenticates the node's single
|
||||
`GrpcPsk` and does not require `x-scadabridge-site`; per-site isolation is proven by the wrong-key
|
||||
rejection.)
|
||||
|
||||
### Check 3 — LocalDb unaffected
|
||||
|
||||
Env2 site-x runs LocalDb **local-only** (no `LocalDb:Replication:PeerAddress` configured — the rig's
|
||||
replication posture is proven on the primary `docker/` stack, not here), so "unaffected" means the
|
||||
consolidated site DB stays operational on the gRPC build. Site logs hold **0** LocalDb
|
||||
errors/exceptions and both nodes boot healthy with the LocalDb at `/app/data/site-localdb.db`; the
|
||||
clean reconcile passes above confirm the site DB + site→central path work end to end.
|
||||
|
||||
### Bonus / observations
|
||||
|
||||
- **No real ClusterClient.** Central logs hold **0** ClusterClient/receptionist references; the site
|
||||
logs' only match is the benign string label `client=ClusterClientSiteAuditClient` in a
|
||||
`SiteAuditTelemetryActor created` line (a legacy identifier, **not** an Akka `ClusterClient`) —
|
||||
identical to the primary rig's site nodes. No real receptionist/ClusterClient actor path exists.
|
||||
- **Central registers site-x over gRPC.** `Site site-x registered online via heartbeat` — the
|
||||
site→central gRPC command/control path is live.
|
||||
- **Seed-data note (not a migration issue).** `ScadaBridgeConfig2.dbo.Sites` has **0 rows**, so
|
||||
central logs `Reconcile request from unknown site 'site-x' … replying with empty gap` and evicts
|
||||
it from the health aggregator as "no longer configured." This is a first-time-setup seeding gap
|
||||
(`docker-env2/seed-sites.sh` not yet run on the fresh DB), orthogonal to the transport — which
|
||||
demonstrably carries the heartbeat and reconcile regardless.
|
||||
|
||||
Reference in New Issue
Block a user