fix(sessions): keep named-pipe socket paths inside the macOS sun_path limit (NEXT-01)
The pipe name mxaccess-gateway-{pid}-session-{32hex} plus .NET's
CoreFxPipe_ prefix overflowed the 104-byte Unix-domain-socket path limit
under the default per-user macOS TMPDIR (~49 chars), so every test that
opened a real pipe threw ArgumentOutOfRangeException at pipe creation
unless TMPDIR=/tmp was exported. Rename to mxgw-{pid}-{sessionUid} (the
session guid hex without the session- prefix; worst-case 43 chars) and
shorten the three test-fixture names the same way. Uniqueness is
unchanged: gateway pid + full session guid. The worker receives the pipe
name via its launch command line, so mixed Server/Worker deploy SHAs are
unaffected. Docs updated in the same change (gateway.md,
GatewayProcessDesign, GatewayConfiguration, Sessions, CLAUDE.md); new
regression test pins the format and the length budget.
Verified: SessionManagerTests 39/39; SessionWorkerClientFactory,
GatewayEndToEndFakeWorkerSmoke, WorkerClient, and ReconnectReplay suites
33/33 under the default macOS TMPDIR — this also retires the
previously-misdiagnosed 'macOS pipe-timeout test failures': they were
this path-length throw, not a timeout-message defect.
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
# Follow-Ups: windev Redeploy, LDAP Test Fixtures, Runner Hygiene — Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:subagent-driven-development
|
||||
> (opus implementers; controller verifies ops evidence; final review pass).
|
||||
|
||||
**Goal:** Close the five items surfaced by the 2026-08-07 live-actions cycle: repair windev's
|
||||
crash-looping gateway and finish the deferred SEC-36 dashboard verification (NEXT-07), fix the
|
||||
DashboardLdapLiveTests fixture drift (NEXT-06), resolve the unexpected macOS instance runner,
|
||||
harden runner-1's plaintext registration token, and verify the cargo Bearer fix.
|
||||
|
||||
**Architecture:** Three independent live streams (windev serial: 1→2→3; repo test fix: 4;
|
||||
Gitea/runner hygiene: 5, 6) run concurrently; task 7 closes out docs/trackers. No contract,
|
||||
gateway-logic, or client changes — one test-file edit (Task 4) plus live ops plus docs.
|
||||
|
||||
**Tech Stack:** SSH + PowerShell `-EncodedCommand` (windev 10.100.0.48), SSH + docker compose
|
||||
(10.100.0.35), Gitea admin API (`gitea.dohertylan.com`, token via `~/.zshenv` `GITEA_TOKEN`),
|
||||
NSSM, xUnit live-LDAP suite, GLAuth at `10.100.0.35:3893`.
|
||||
|
||||
---
|
||||
|
||||
## Preflight facts (verified before planning)
|
||||
|
||||
- Unpushed local mxaccessgw commits: `0566716`, `9760497`, `5b153da`, `41e8648` (all docs-only).
|
||||
**`origin/main` = `a346d51`** — contains all current code, so windev can build from
|
||||
`origin/main` without any push.
|
||||
- windev (`10.100.0.48`): NSSM service `MxAccessGw`; deployed Server build of 2026-06-25
|
||||
(Auth 0.1.2.0, supports auth-DB schema 2) crash-loops on
|
||||
`C:\ProgramData\MxGateway\gateway-auth.db` migrated to schema 3 on 2026-07-15
|
||||
(`AuthStoreMigrationException`, ~3.8k–10k Hosting-failed events/day). The NEW LDAP secret is
|
||||
already staged as the 10th `AppEnvironmentExtra` entry (SEC-36 Task 3) — preserve it.
|
||||
- `DashboardLdapLiveTests.cs` (`src/ZB.MOM.WW.MxGateway.IntegrationTests/`): uses
|
||||
`admin`/`admin123` (3 tests) and `readonly`/`readonly123`. Directory reality
|
||||
(`scadaproj/infra/glauth/config.toml`): `admin` exists, password is the standard dev test
|
||||
password (`password`, hash `5e884898…42d8` — same as `multi-role`), and IS in GwAdmin
|
||||
(othergroups `[5610, 5701]`); `readonly` does not exist; `gw-viewer` (primarygroup 5611 =
|
||||
GwReader, NOT GwAdmin) is the natural not-an-admin fixture. Test binds
|
||||
`MxGateway:Ldap` from `appsettings.json` (**`Server: localhost`**) + env overrides — so the
|
||||
live run needs `MxGateway__Ldap__Server=10.100.0.35` as well as
|
||||
`MxGateway__Ldap__ServiceAccountPassword` (from Mac user-secrets, never printed).
|
||||
- Gitea instance runners (`GET /api/v1/admin/actions/runners`): id 1 `gitea-runner` (cap 4),
|
||||
id 4 `macos-local-Josephs-MacBook-Pro` (**unexpected, online, labels overlap
|
||||
ubuntu-latest**), id 5 `gitea-runner-2` (cap 2).
|
||||
- `10.100.0.35:/opt/gitea/docker-compose.yml` (+ `docker-compose.yml.bak-tst30`): runner-1's
|
||||
registration token inline in plaintext env, file world-readable. runner-2 uses
|
||||
`GITEA_RUNNER_REGISTRATION_TOKEN_FILE: /run/secrets/runner_token` ← 0600
|
||||
`/opt/gitea/runner_token`. runner-1 data volume `/opt/gitea/runner:/data` (its `.runner`
|
||||
credential persists — the registration env is only needed for first registration).
|
||||
- Cargo Bearer fix already applied (`~/.zshenv`, backup `~/.zshenv.bak-cli39`) and documented
|
||||
(`docs/ClientPackaging.md`, commit `5b153da`). Task 7 verifies; no further action expected.
|
||||
|
||||
## Secret hygiene (binding, all tasks)
|
||||
|
||||
- Never print the LDAP service-account password, `GITEA_TOKEN`, cargo token, runner
|
||||
registration tokens, or API keys — not in commands, logs, commits, or reports. Read the LDAP
|
||||
password from `dotnet user-secrets list` into an env var without echoing
|
||||
(e.g. `export MxGateway__Ldap__ServiceAccountPassword="$(dotnet user-secrets list --project src/ZB.MOM.WW.MxGateway.Server | awk -F' = ' '/ServiceAccountPassword/ {print $2}')"`).
|
||||
- Documented dev **test users** (`multi-role`/`password`, `admin`/`password`,
|
||||
`gw-viewer`/`password`) are NOT secrets — glauth.md publishes them; fine in code/commits.
|
||||
- SSH→windev PowerShell: always `powershell -NoProfile -EncodedCommand <base64-UTF16LE>`;
|
||||
never put secrets inside EncodedCommand blobs or argv.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: NEXT-07 — Recon windev deployment layout + schema support
|
||||
|
||||
**Classification:** standard — read-only recon, but its output gates a service redeploy
|
||||
**Estimated implement time:** ~5 min
|
||||
**Parallelizable with:** Task 4, Task 5, Task 6
|
||||
|
||||
**Files:** none edited. SSH recon on `10.100.0.48` + repo/scadaproj reads on the Mac.
|
||||
|
||||
Determine everything Task 2 needs, and confirm the fresh-deploy path is safe:
|
||||
|
||||
1. `nssm get MxAccessGw Application`, `AppDirectory`, `AppParameters`,
|
||||
`AppEnvironmentExtra` (count entries; do NOT print values of secret-bearing entries —
|
||||
names only).
|
||||
2. Inventory the deployed dir: path, `ZB.MOM.WW.MxGateway.Server.exe` timestamp, whether
|
||||
`appsettings.json`/`appsettings.Production.json` in the deploy dir differ from repo
|
||||
`origin/main` (diff; windev-specific config must survive the redeploy).
|
||||
3. Confirm build feasibility on windev: `dotnet --list-sdks` (need 10.x), locate an existing
|
||||
mxaccessgw checkout/worktree (CI uses `scripts/ci/windev-worker-ci.ps1` — find its
|
||||
worktree path) or pick a fresh clone location. Confirm `git fetch` reaches `origin/main`
|
||||
= `a346d514dd24e775640e5667aa7cd8e561fec68a`.
|
||||
4. Confirm current code supports auth-DB schema 3: find the auth-store supported-schema
|
||||
constant (ZB.MOM.WW.Auth packages — check the package version the Server at `origin/main`
|
||||
references, and/or the migration code in the shared scadaproj libs) and state the
|
||||
evidence. **If current code does NOT support schema 3, STOP — report, do not deploy.**
|
||||
5. Gateway endpoints for verification: bound URLs/ports (from deployed config/env), dashboard
|
||||
scheme (http vs https → cookie will be `MxGatewayDashboard` vs `__Host-…`).
|
||||
6. Check what migrated the DB to schema 3 on 2026-07-15 (event log / file timestamps) — only
|
||||
to confirm schema 3 is the shared-lib current version, not an anomaly.
|
||||
|
||||
**Step: report** all findings as structured text (no secrets); no changes, no commits.
|
||||
|
||||
### Task 2: NEXT-07 — Build current Server on windev and redeploy the service
|
||||
|
||||
**Classification:** high-risk — replaces a running (crash-looping) service's binaries
|
||||
**Estimated implement time:** ~10 min
|
||||
**Parallelizable with:** none (needs Task 1)
|
||||
|
||||
**Files:** none in repo. windev filesystem + NSSM only.
|
||||
|
||||
Using Task 1's facts:
|
||||
|
||||
1. On windev, fetch/checkout `origin/main` (`a346d51…`) in the build worktree/clone.
|
||||
2. `dotnet publish src/ZB.MOM.WW.MxGateway.Server -c Release` (match deployed layout/RID from
|
||||
Task 1; framework-dependent vs self-contained must match what NSSM `Application` points at).
|
||||
3. Stop the service (`nssm stop MxAccessGw`), confirm process exited.
|
||||
4. Backup: deployed dir → sibling `*.bak-next07` copy; copy
|
||||
`C:\ProgramData\MxGateway\gateway-auth.db` (+ `-wal`/`-shm` if present) to
|
||||
`gateway-auth.db.bak-next07`. **Never delete the live DB.**
|
||||
5. Deploy publish output over the deploy dir, then restore any windev-specific config files
|
||||
identified in Task 1 (do not clobber live overrides; NSSM env entries are untouched by
|
||||
file copies but verify count unchanged after start).
|
||||
6. `nssm start MxAccessGw`; verify: service state RUNNING and stable ≥60 s (no restart
|
||||
cycle), Application event log shows clean host start and **zero new
|
||||
`AuthStoreMigrationException` / `Hosting failed to start`** after the start timestamp,
|
||||
bound port answers (e.g. dashboard root or health endpoint returns HTTP).
|
||||
7. Rollback if unhealthy: stop, restore `*.bak-next07` dir, start, report.
|
||||
|
||||
**Step: report** deployed SHA, verification evidence, backup paths. No repo commits.
|
||||
|
||||
### Task 3: SEC-36 deferred verification + NEXT-07/runbook closeout
|
||||
|
||||
**Classification:** standard
|
||||
**Estimated implement time:** ~6 min
|
||||
**Parallelizable with:** none (needs Task 2)
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/runbooks/SEC-36-ldap-credential-rotation.md` (Correction 3 — mark the
|
||||
deferred dashboard check done, dated)
|
||||
- Modify: `archreview/2026-07-12/remediation/90-candidate-findings-next-cycle.md` (NEXT-07 →
|
||||
resolved 2026-08-07, evidence one-liner)
|
||||
|
||||
1. Complete SEC-36 step 4 against the repaired windev gateway: log in to the dashboard as
|
||||
`multi-role`/`password` end-to-end. Preferred: `curl` flow — GET `/login` (capture
|
||||
antiforgery token + cookie), POST credentials, expect success redirect + auth cookie
|
||||
(name per Task 1 scheme). If the login page resists scripting (Blazor circuit), report
|
||||
exactly why and fall back to asserting a fresh `DashboardLdapLiveTests` green run
|
||||
(Task 4) plus windev log evidence of successful LDAP bind on a manual attempt.
|
||||
2. Update the two docs; commit locally (`docs(sec-36,next-07): …`), do NOT push.
|
||||
|
||||
### Task 4: NEXT-06 — Fix DashboardLdapLiveTests fixtures to match the shared directory
|
||||
|
||||
**Classification:** small — one test file, but must go green against live GLAuth
|
||||
**Estimated implement time:** ~6 min
|
||||
**Parallelizable with:** Task 1, Task 5, Task 6
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/ZB.MOM.WW.MxGateway.IntegrationTests/DashboardLdapLiveTests.cs`
|
||||
- Modify: `archreview/2026-07-12/remediation/90-candidate-findings-next-cycle.md` (NEXT-06 →
|
||||
resolved)
|
||||
- Possibly modify: `docs/GatewayTesting.md` (live-LDAP opt-in row: document the
|
||||
`MxGateway__Ldap__Server` override needed when GLAuth is not localhost)
|
||||
|
||||
1. Read `DashboardAuthenticator` first: confirm a user who binds successfully but maps to no
|
||||
role yields `Succeeded == false` (drives the gw-viewer fixture).
|
||||
2. Fix fixtures: `admin`/`admin123` → `admin`/`password` (positive + wrong-password +
|
||||
unreachable tests); `readonly`/`readonly123` → `gw-viewer`/`password` (exercises
|
||||
user-binds-but-lacks-GwAdmin; keep the no-password-leak assertion, updating the asserted
|
||||
literal). Update XML doc comments to match. Keep MXAccess-repo style rules
|
||||
(TreatWarningsAsErrors).
|
||||
3. Build: `dotnet build src/ZB.MOM.WW.MxGateway.IntegrationTests` (macOS OK — net10.0).
|
||||
4. Live run (env only, never echo the password):
|
||||
`MXGATEWAY_RUN_LIVE_LDAP_TESTS=1 MxGateway__Ldap__Server=10.100.0.35 MxGateway__Ldap__ServiceAccountPassword=<from user-secrets> dotnet test … --filter FullyQualifiedName~DashboardLdapLiveTests`
|
||||
→ expect **5/5 passed** (this is also positive live proof of the SEC-36 service-account
|
||||
bind).
|
||||
5. Update tracker row (+ GatewayTesting.md if the Server-override note is missing); commit
|
||||
locally (`test(ldap): …`), do NOT push.
|
||||
|
||||
### Task 5: Resolve the unexpected macOS instance runner (id 4)
|
||||
|
||||
**Classification:** standard — evidence-gated removal of a live runner registration
|
||||
**Estimated implement time:** ~6 min
|
||||
**Parallelizable with:** Task 1, Task 4, Task 6
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/runbooks/TST-30-second-ci-runner.md` (the Correction paragraph mentions
|
||||
"id 4 — an unrelated local macOS runner" — update to final state)
|
||||
|
||||
1. Evidence, local: `pgrep -fl act_runner`, `launchctl list | grep -i act`,
|
||||
`brew services list | grep -i act`, look for `~/.runner`/act_runner config dirs. Evidence,
|
||||
Gitea (token from `~/.zshenv`, never printed): runner detail for id 4 (labels, last
|
||||
online), and whether any recent runs' jobs report `runner_id == 4`
|
||||
(`GET /repos/{owner}/{repo}/actions/runs?…` → `…/runs/{id}/jobs` for both `mxaccessgw`
|
||||
and `lmxopcua` recent runs).
|
||||
2. Decision rule: the runner advertises ubuntu labels from a macOS host, so it can steal
|
||||
Linux container jobs → **remove it** unless evidence shows it deliberately serves jobs
|
||||
the docker runners cannot (none expected). Removal = stop the local act_runner process
|
||||
AND disable its autostart (launchd/brew), then `DELETE /api/v1/admin/actions/runners/4`.
|
||||
Keep the local config file (renamed `*.disabled-2026-08-07`) so re-registering with
|
||||
mac-specific labels stays easy; note the re-registration recipe in the runbook edit.
|
||||
3. Verify: admin runner list shows only ids 1 and 5, both online; no act_runner process
|
||||
locally; a `pgrep` after 60 s still empty (nothing respawned).
|
||||
4. Update the TST-30 runbook correction paragraph; commit locally, do NOT push.
|
||||
|
||||
### Task 6: Harden runner-1's registration token on 10.100.0.35
|
||||
|
||||
**Classification:** standard — touches the live CI stack's compose file
|
||||
**Estimated implement time:** ~7 min
|
||||
**Parallelizable with:** Task 1, Task 4, Task 5
|
||||
|
||||
**Files:** none in repo (host `/opt/gitea/` only; runbook note lands in Task 7 if needed).
|
||||
|
||||
1. Preconditions on the host: confirm runner-1's `/data/.runner` exists in its volume
|
||||
(registration credential persists → the registration env var is no longer needed);
|
||||
confirm both runners idle (no `act_runner`-spawned job containers, no in-progress runs
|
||||
via API) before recreating.
|
||||
2. Edit `/opt/gitea/docker-compose.yml` (backup first → `docker-compose.yml.bak-tst30b`):
|
||||
replace runner-1's inline `GITEA_RUNNER_REGISTRATION_TOKEN: <plaintext>` with the same
|
||||
`_FILE`/secrets pattern runner-2 uses (`/opt/gitea/runner_token`, 0600). Do NOT touch the
|
||||
`gitea` service definition.
|
||||
3. `docker compose up -d --no-deps` the runner-1 service only; verify it comes back online
|
||||
in the admin runner list and its `.runner` identity is unchanged (still id 1).
|
||||
4. Tighten perms: `chmod 600 /opt/gitea/docker-compose.yml docker-compose.yml.bak-tst30 docker-compose.yml.bak-tst30b`
|
||||
(verify compose stack still operable by the deploy user).
|
||||
5. Rotate the leaked registration token if the deployment allows:
|
||||
`docker exec … gitea actions generate-runner-token` (or admin API) — if Gitea offers no
|
||||
invalidation of the old value, say so explicitly in the report (residual risk: LAN actor
|
||||
could register a rogue runner until rotation) rather than claiming it rotated.
|
||||
6. Verify CI still works: trigger nothing; just confirm both runners online and the token
|
||||
file perms; a real push lands naturally later. Report evidence.
|
||||
|
||||
### Task 7: Closeout — cargo Bearer verification, docs/tracker sync, commits
|
||||
|
||||
**Classification:** small
|
||||
**Estimated implement time:** ~5 min
|
||||
**Parallelizable with:** none (needs Tasks 3, 4, 5, 6)
|
||||
|
||||
**Files:**
|
||||
- Modify: `archreview/2026-07-12/remediation/90-candidate-findings-next-cycle.md` (final
|
||||
state of NEXT-06/NEXT-07 rows if Tasks 3/4 left anything)
|
||||
- Possibly modify: `docs/GatewayTesting.md` / TST-30 runbook (runner topology now ids 1+5
|
||||
only; token-hardening note)
|
||||
|
||||
1. Cargo Bearer verification (no printing): assert `~/.zshenv` line matches
|
||||
`CARGO_REGISTRIES_DOHERTJ2_GITEA_TOKEN="Bearer …"` via `grep -c`, confirm
|
||||
`docs/ClientPackaging.md` note present (commit `5b153da`); check no other credential
|
||||
location (CI secrets, windev profiles) publishes to cargo — expected none.
|
||||
2. Sweep: every doc touched this cycle consistent (runbooks, trackers, GatewayTesting.md);
|
||||
`git grep` for stale phrases ("crash-loop… pending", "id 4", "admin123") and fix.
|
||||
3. Commit remaining doc changes locally; do NOT push. List the full unpushed stack in the
|
||||
report.
|
||||
|
||||
---
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Pushing any mxaccessgw commits (user decides; stack listed at closeout).
|
||||
- The five next-cycle candidate findings other than NEXT-06/NEXT-07.
|
||||
- Auth-DB restore path for windev (fresh deploy chosen — preserves schema-3 data).
|
||||
- `ci.yml` changes (labels, concurrency groups).
|
||||
|
||||
## Dependency graph
|
||||
|
||||
```
|
||||
{1} → 2 → 3 ┐
|
||||
{4} ├→ 7
|
||||
{5} │
|
||||
{6} ─────────┘
|
||||
```
|
||||
Reference in New Issue
Block a user