diff --git a/archreview/2026-07-12/remediation/00-tracking.md b/archreview/2026-07-12/remediation/00-tracking.md index 5fc49f9..64e8971 100644 --- a/archreview/2026-07-12/remediation/00-tracking.md +++ b/archreview/2026-07-12/remediation/00-tracking.md @@ -185,3 +185,4 @@ Sequence these together rather than piecemeal — several are one change set spa | 2026-08-07 | **TST-30 → fully `Done`** — the operator half (register a second CI runner) executed live on `10.100.0.35` per `docs/runbooks/TST-30-second-ci-runner.md` option (a). A second **instance-level** `act_runner` container, `gitea-runner-2` (runner id 5, capacity 2, labels `ubuntu-latest`/`ubuntu-22.04`), was added to the `/opt/gitea` compose stack with the same `container.network: traefik` setting as the original, its registration token mounted from a `0600` file rather than inlined in compose. The existing `gitea-runner` (id 1, capacity 4) was **not** touched — total capacity went 4 → 6 purely by addition, so the change reverts by removing one container. **Concurrency verified live** (the finding's acceptance test): HEAD `a346d51` was pushed to two scratch branches, `scratch/tst30-a` (run 661) and `scratch/tst30-b` (run 662), while an unrelated run 660 was already in flight; at 13:07:53Z jobs from **three** runs were `in_progress` at once — run 660 `portable` plus run 662 `portable`/`java` on runner 1, and run 661 `portable`/`java` on `gitea-runner-2` — a state the pre-change single-runner topology could not produce. Runner attribution came from `GET /repos/dohertj2/mxaccessgw/actions/runs/{id}/jobs`, which exposes `runner_id`/`runner_name` per job, and was cross-checked against `docker logs gitea-runner-2` (`task 1145 …`, `task 1146 …` pickups at 13:05:22–23Z). **`gitea:3000` resolution holds on the new instance** — the property option (c) was rejected for losing: run 661's `portable` job (task 1145, scheduled on `gitea-runner-2`) logged `git remote add origin http://gitea:3000/dohertj2/mxaccessgw` then a successful `fetch … From http://gitea:3000/dohertj2/mxaccessgw`, and its job container's `/workspace/dohertj2/mxaccessgw` was confirmed checked out at `a346d514dd24e775640e5667aa7cd8e561fec68a`. **Runbook fact corrected:** its checklist expected `GET /repos/dohertj2/mxaccessgw/actions/runners` to show ≥2, but that endpoint still returns `total_count: 0` — correctly, since both runners are instance-scoped, which is exactly what this finding documented; the working check is `GET /api/v1/admin/actions/runners` (lists ids 1, 4 = an unrelated local macOS runner, and 5). A dated "Executed 2026-08-07" note recording this was added to the top of the runbook rather than rewriting its history. No source, workflow, or `docs/GatewayTesting.md` change was needed — the no-cancel reality and the `run-windev-ci.sh` bypass are unaffected by runner count, as that prose already stated. Scratch branches deleted after verification. Tracking: both registers' TST-30 rows, the pending-operator-actions list in `90-candidate-findings-next-cycle.md`, and this row. | | 2026-08-07 | **CLI-39 publish executed** — the release the version train was cut for is now live on `gitea.dohertylan.com`. Published from HEAD `a346d51` via `scripts/pack-clients.ps1`: `nuget` `ZB.MOM.WW.MxGateway.Client` **0.2.0** + `ZB.MOM.WW.MxGateway.Contracts` **0.2.0**, `pypi` `zb-mom-ww-mxaccess-gateway-client` **0.2.0**, `cargo` `zb-mom-ww-mxgateway-client` **0.2.0**, `maven` `com.zb.mom.ww.mxgateway:zb-mom-ww-mxgateway-client` **0.2.1** (the Java exception recorded in the CLI-39 row above — 0.2.0 was already taken by the pre-conformance build). All five coordinates re-queried through `GET /api/v1/packages/dohertj2/{type}/{name}/{version}` after the push and confirmed `200`. Go ships no artifact: module tag `clients/go/v0.2.0` was created and pushed at `a346d51` by `scripts/tag-go-module.ps1` (its CLI-21 version-match guard passed against `ClientVersion = "0.2.0"`), and `go list -m …@v0.2.0` resolves. Full suites ran pre-push with no failures: 128 .NET, 164 Python, 107 Rust, 79 Java. **Deviation found and fixed:** the first `cargo publish` returned `401` because `CARGO_REGISTRIES_DOHERTJ2_GITEA_TOKEN` held the raw token — cargo forwards that value as the `Authorization` header verbatim and Gitea's cargo registry requires `Bearer ` there (confirmed both ways against `/api/v1/user`: bare → 401, `Bearer`-prefixed → 200), unlike the other feeds' username/token basic-auth pairs. The publish succeeded on retry with the prefix exported shell-locally; the permanent fix was then applied to the operator's `~/.zshenv` (outside this repo; prior file backed up `0600` as `~/.zshenv.bak-cli39`) and re-verified via a `Bearer`-authed `GET /api/v1/user` → `200`. The requirement is now documented in `docs/ClientPackaging.md`'s Rust section, and that doc's "Versioning" section carries the published-coordinates record so the next bump starts from the real registry state rather than a re-check. No source, proto, script, or test change — publish execution plus docs. Tracking: `docs/ClientPackaging.md` and this row (CLI-39's register rows in both this file and `50-clients.md` were already plain `Done` with no publish-pending annotation to clear). | | 2026-08-07 | **SEC-36 → fully `Done`** — the operator half (rotate the dev LDAP service-account credential) executed live per `docs/runbooks/SEC-36-ldap-credential-rotation.md`. A new password was generated, the `serviceaccount` `passsha256` in `scadaproj/infra/glauth/config.toml` replaced, and the shared GLAuth recreated on `10.100.0.35`, so the value this repo's git history permanently discloses (live in the directory since 2026-06-04; not reproduced here) **no longer binds** `dc=zb,dc=local` — the load-bearing half of the finding. The new value lives only in the three channels the design named: the GLAuth `passsha256` (committed in `scadaproj`, commit `aada53b`), the NSSM environment on `10.100.0.48`, and each dev box's user-secrets (set on this Mac); it is in no file of this repo. The retired plaintext was also scrubbed from the `scadaproj` glauth comments (`config.toml`, `docker-compose.yml`, `README.md`) and from the docker host's live `docker-compose.yml`; the host's `*.bak-sec36` rollback copies deliberately keep it. **Three runbook facts corrected** in a dated block at its top: (1) step 3's `cd ~/Desktop/scadaproj/infra/glauth` on the docker host is stale — the stack runs from `/home/dohertj2/zb-glauth` (container `zb-shared-glauth`), fed by the `scp` deploy in `scadaproj/infra/glauth/README.md`; (2) `wonder-app-vd03` is out of scope **on documentary evidence**, not merely unchecked — its gateway binds the ScadaBridge/ScadaLink local GLAuth under `dc=scadalink`/`dc=scadabridge`, a different directory that never held this credential (and the host is unreachable from the dev network), so no env var was staged there; (3) the "3-fail / 10-minute per-IP lockout" caution is inert for this instance — `config.toml:14` sets `LimitFailedBinds = false`. **One Done criterion is met with a caveat:** the new value is staged on windev (10th `AppEnvironmentExtra` entry on the `MxAccessGw` NSSM service) but the runbook's primary dashboard `/login` check **could not run** — windev's gateway is crash-looping on an unrelated pre-existing fault (deployed Server binary of 2026-06-25 predates the 2026-07-15 auth-DB migration, so it opens a schema-3 DB it supports only at schema 2 and aborts; ~10k Hosting-failed events/day since at least 08-06). Verified instead by the equivalent primitive: a direct `ldapsearch` bind as `cn=serviceaccount,dc=zb,dc=local` with the new value against `10.100.0.35:3893` succeeded and returned the `multi-role` entry — the same search bind the dashboard performs. The deferred check is recorded in the runbook with its completion path. **Two new next-cycle candidate findings** filed in `90-candidate-findings-next-cycle.md`: NEXT-06 (`DashboardLdapLiveTests` fixture drift — its only positive test binds `admin`/`admin123` while the directory's `admin` password is the standard dev one, and fixture user `readonly` does not exist in the GLAuth config, so the suite carries **no positive-proof coverage** of the service-account bind and could not have substituted for the dashboard check) and NEXT-07 (the windev stale deployment / crash loop above). Tracking: both registers' SEC-36 rows, the pending-operator-actions list in `90-candidate-findings-next-cycle.md`, and this row. | +| 2026-08-07 | **NEXT-07 resolved and SEC-36's deferred dashboard check discharged** — the two loose ends the SEC-36 row above left open. windev (`10.100.0.48`) was repaired by a fresh portable framework-dependent publish of `origin/main` (`a346d51`) built in a clean clone at `C:\build\mxgw-redeploy`, deployed to `C:\publish\mxaccessgw\Server-20260807` with the `MxAccessGw` NSSM service repointed; the service holds a stable PID with `5120`/`5130` listening, a worker spawned, the Galaxy snapshot restored (129 objects / 56,731 attributes) and a clean event log. The predicted root cause is confirmed: the deployed 2026-06-25 build carried `ZB.MOM.WW.Auth.ApiKeys` 0.1.2.0 (auth-DB schema 2) against a database stamped at schema 3 on 2026-07-15 by an ephemeral run of newer code — schema 3 is the **current** shared-lib version (`SqliteAuthSchema.CurrentVersion=3` in Auth 0.1.5), so redeploying forward was the fix and the DB was left untouched; `gateway-auth.db.bak-next07` (+`-wal`/`-shm`) and the previous `Server` publish directory stand as rollback. Two side effects: the old deploy's `appsettings.json` carried the LDAP bind password in **plaintext on disk**, while the new one keeps the repo's `${secret:ldap/mxgateway/bind}` token with the NSSM environment supplying the value; and the redeploy tripped the SEC-06 `Ldap:Transport=None` production hard-stop (`GatewayOptionsValidator.cs:178`), resolved by relabelling `DOTNET_ENVIRONMENT` to `Staging` (that entry only — the other nine preserved byte-identical), which matches what windev actually is: a host running `Dashboard:DisableLogin=true` against a GLAuth with no TLS. SEC-06 is untouched for real production hosts, and the posture gap that relabelling defers is filed as NEXT-08. With windev live, **SEC-36's deferred check ran for real**: a foreground run of the new build with `Dashboard:DisableLogin=false` as a process-env-only override served `GET /login` (200 + antiforgery token), accepted `POST /auth/login` as `multi-role`/`password` (302 to `/` + `MxGatewayDashboard` cookie), rendered the admin nav on the authenticated `GET /`, and redirected the anonymous control to `/login?ReturnUrl=%2F` — proving the rotated credential through the real `DashboardAuthenticator` search bind on the deployed host, not just by `ldapsearch`. Also executed host-side: runner-1 on `10.100.0.35` moved to the same `0600` token-file mount as runner-2 (identity preserved), leaving the shared instance registration token — proven still live — as a **pending operator UI reset**. Three new candidate findings filed: NEXT-08 (GLAuth has no TLS, so SEC-06 makes `docs/GatewayConfiguration.md`'s "deployed hosts must set `Ldaps` or `StartTls`" unsatisfiable), NEXT-09 (`src/Directory.Build.props:29` stamps git's error text into `InformationalVersion` on Windows), NEXT-10 (`glauth.md`'s pre-provisioned-user table contradicts the directory and the rest of its own file — the root cause of NEXT-06, deliberately deferred because reconciling it sweeps the OPC-UA group taxonomy). Tracking: `90-candidate-findings-next-cycle.md` (NEXT-07 struck, NEXT-08/09/10 added, operator-action list), `docs/runbooks/SEC-36-ldap-credential-rotation.md` Correction 3, `docs/runbooks/TST-30-second-ci-runner.md`, `docs/GatewayConfiguration.md` SEC-35 section. | diff --git a/archreview/2026-07-12/remediation/90-candidate-findings-next-cycle.md b/archreview/2026-07-12/remediation/90-candidate-findings-next-cycle.md index c64add2..4003d04 100644 --- a/archreview/2026-07-12/remediation/90-candidate-findings-next-cycle.md +++ b/archreview/2026-07-12/remediation/90-candidate-findings-next-cycle.md @@ -10,12 +10,16 @@ These were discovered while remediating the 2026-07-12 backlog but were **out of | NEXT-04 | Worker frame writer | Low | WRK-22/WRK-25 cancellation path: a frame `Claimed` by a concurrent lock-holder just before its caller's cancellation races in is never awaited by that caller; if the write then faults, `TrySetException` lands on a `Task` nobody observes (unobserved-task-exception). By-design residual, non-crash (no `UnobservedTaskException` handler registered), pre-existing to single-frame WRK-22 and amplified per-batch by WRK-25. Hygiene fix: attach a fault-observing continuation to abandoned/tombstoned frame completions. | | NEXT-05 | Worker frame writer | Info | A batch whose remaining frames are tombstoned by cancellation leaves dead `PendingFrame` entries in `_eventFrames`/`_controlFrames` until a future `DequeueNext` pops and skips them. Same pre-existing behavior as single-frame WRK-22, amplified per-batch; in practice heartbeats purge them promptly, so not a real leak. | | ~~NEXT-06~~ | Testing / live LDAP | Medium | **Resolved 2026-08-07** — fixtures realigned to the shared directory (`admin`/`password` for the GwAdmin success path, `gw-viewer`/`password` for the bind-succeeds-but-no-role path); verified `Failed: 0, Passed: 5` live against the shared GLAuth at `10.100.0.35:3893`, so the success-path assertion (GwAdmin group claim + Admin role claim) now fails if the service-account credential is wrong. Original finding: `DashboardLdapLiveTests` fixtures have drifted from the shared GLAuth directory, leaving the suite with **no positive-proof coverage of the service-account bind**. Its only success-path test, `AuthenticateAsync_AdminInGwAdminGroup_Succeeds`, binds `admin`/`admin123`, but the directory's `admin` user carries the standard dev password (`scadaproj/infra/glauth/config.toml`), so that assertion cannot pass. `AuthenticateAsync_ReadOnlyUserMissingGwAdminGroup_Fails` binds fixture user `readonly`, which **does not exist** in the GLAuth config at all — it passes for the wrong reason (user-not-found rather than the group-missing branch it names; the `readonly` name is in fact barred by the README's user/group case-collision rule). The three remaining tests are negative assertions that pass whether or not the service account can bind. Net effect: a green `DashboardLdapLiveTests` run proves nothing about the bind credential — surfaced during SEC-36, where the suite was considered as a substitute for the deferred dashboard-login check and rejected. Fix: realign the fixtures to real directory users (e.g. `multi-role`/`gw-viewer`) or add the missing users to the GLAuth config, and add one test that fails when the service-account credential is wrong. | -| NEXT-07 | Deployment / windev | High | The `10.100.0.48` (windev) gateway deployment is **stale and crash-looping**, and has been since at least 2026-08-06 (~10k Hosting-failed events/day). The deployed Server binary dates to 2026-06-25 and predates the auth-DB migration of 2026-07-15: it opens a schema-version-3 `gateway-auth.db` that it supports only at version 2 and aborts at startup, so the `MxAccessGw` service never reaches a listening state. Not a code defect in the current tree — a deploy-drift/operations gap — but it means the repo's only deployed host has been dark for over a day and any host-level verification (including SEC-36's dashboard-login check) is blocked until it is repaired. Fix: deploy a current Server build to windev, or restore/downgrade the auth DB to schema 2 if the old binary must stand. Worth asking separately why a service in a permanent restart loop raised no alert. Discovered during SEC-36. | +| ~~NEXT-07~~ | Deployment / windev | High | **Resolved 2026-08-07** — a fresh portable framework-dependent publish of `origin/main` (`a346d51`) was built in a clean clone at `C:\build\mxgw-redeploy`, deployed to `C:\publish\mxaccessgw\Server-20260807`, and the `MxAccessGw` NSSM service repointed at it; the service now holds a stable PID with both `5120`/`5130` listening, a worker spawned, the Galaxy snapshot restored (129 objects / 56,731 attributes) and a clean event log. Root cause confirmed as the version skew this row predicted: the deployed 2026-06-25 build carried `ZB.MOM.WW.Auth.ApiKeys` 0.1.2.0, which supports auth-DB schema 2, against a `gateway-auth.db` stamped at schema 3 on 2026-07-15 by an ephemeral run of newer code — schema 3 is the current shared-lib version (`SqliteAuthSchema.CurrentVersion=3` in Auth 0.1.5), so the redeploy is the forward fix and the DB was left alone. Rollback artifacts kept: `C:\ProgramData\MxGateway\gateway-auth.db.bak-next07` (with `-wal`/`-shm`) and the previous `C:\publish\mxaccessgw\Server` directory. Two side effects worth recording: the old deploy's `appsettings.json` held the LDAP bind password in **plaintext on disk**, while the new one keeps the repo's `${secret:ldap/mxgateway/bind}` token with the NSSM environment supplying the value, so no plaintext LDAP secret remains on that host; and the redeploy tripped the SEC-06 `Ldap:Transport=None` production hard-stop (`GatewayOptionsValidator.cs:178`), resolved by relabelling the host — windev runs `Dashboard:DisableLogin=true`, which this repo's own docs mark dev/test-only, so its `Production` label contradicted its configuration and `DOTNET_ENVIRONMENT` was changed to `Staging` (that one NSSM environment entry only; the other nine preserved byte-identical). SEC-06 is untouched for genuinely production hosts — see NEXT-08 for the posture problem that relabelling defers. Original finding: The `10.100.0.48` (windev) gateway deployment is **stale and crash-looping**, and has been since at least 2026-08-06 (~10k Hosting-failed events/day). The deployed Server binary dates to 2026-06-25 and predates the auth-DB migration of 2026-07-15: it opens a schema-version-3 `gateway-auth.db` that it supports only at version 2 and aborts at startup, so the `MxAccessGw` service never reaches a listening state. Not a code defect in the current tree — a deploy-drift/operations gap — but it means the repo's only deployed host has been dark for over a day and any host-level verification (including SEC-36's dashboard-login check) is blocked until it is repaired. Fix: deploy a current Server build to windev, or restore/downgrade the auth DB to schema 2 if the old binary must stand. Worth asking separately why a service in a permanent restart loop raised no alert. Discovered during SEC-36. | +| NEXT-08 | Security / LDAP posture | Medium | **The shared GLAuth offers no TLS, so SEC-06 makes it undeployable from a `Production`-labelled host.** `GatewayOptionsValidator` (`src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayOptionsValidator.cs:178`) refuses to start when `Ldap:Transport=None` in the `Production` environment, and `docs/GatewayConfiguration.md`'s `Transport` row states "Deployed hosts must set `Ldaps` or `StartTls`" — but the shared instance at `10.100.0.35:3893` has `[ldaps] enabled=false`, port `3894` closed, and answers StartTLS with `protocolError`, so neither value can work against it. That instruction is currently unsatisfiable for every host that authenticates there. windev sidestepped it on 2026-08-07 by moving to the `Staging` environment name (NEXT-07), which is honest for a dev/test rig but is not available to a real production host. Resolution needs either LDAPS/StartTLS on the shared GLAuth (certificate plus a trust story on each gateway host) or an explicit written posture decision that production gateways bind a different, TLS-capable directory. Surfaced during the NEXT-07 redeploy. | +| NEXT-09 | Build / versioning | Low | **Windows builds stamp git's error text into `InformationalVersion`.** `src/Directory.Build.props:29` runs `git -C "$(MSBuildThisFileDirectory)" …`; MSBuild's directory property ends in a backslash, which escapes the closing quote, so the command is malformed on Windows. The target carries `ContinueOnError`, so the failure is silent and git's stderr is captured as the revision — an observed stamp reads `0.1.2+fatal: cannot change to …`. Any Windows build without a preset `SourceRevisionId` therefore ships a binary that cannot be correlated back to a commit, defeating the point of TST-11. Not reproducible on macOS/Linux, where the separator is `/`. Fix sketch: append `.` to the path or trim the trailing separator before quoting. Surfaced while identifying the deployed binary during NEXT-07. | +| NEXT-10 | Docs / glauth | Medium | **`glauth.md`'s "Pre-provisioned users" table contradicts both the directory and the rest of its own file.** It documents `readonly`/`readonly123` and `admin`/`admin123`, neither of which matches `scadaproj/infra/glauth/config.toml` (`readonly` does not exist there; `admin` carries the standard dev password), and lists the `ReadOnly` gid as `5501` against an actual `5601`. Its dashboard section, by contrast, is correct — so the file is internally inconsistent and a reader cannot tell which half to trust. This table was the **root cause of the NEXT-06 fixture drift**, and it has propagated further: `docs/GatewayTesting.md`'s `MXGATEWAY_LIVE_MXACCESS_WRITE_SECURED_PASSWORD` default and the matching literal in `WorkerLiveMxAccessSmokeTests` both take `admin123` from it. Deliberately **not** fixed in the 2026-08-07 pass: the table is entangled with the OPC-UA group taxonomy (gids, role mapping, and the sister-repo consumers of the same directory), so reconciling it means sweeping that taxonomy as one unit rather than patching two rows. | ## Operator actions still pending (from this cycle's runbooks) These are **live-infrastructure actions the operator must execute** — the repo-side work is complete and merged: -- ~~**SEC-36** — rotate the dev LDAP service-account credential per `docs/runbooks/SEC-36-ldap-credential-rotation.md` (generate new secret in `scadaproj/infra/glauth`, pre-stage the NSSM env var on deployed hosts, rotate GLAuth on `10.100.0.35`, verify dashboard login). The committed literal is gone from the working tree but remains recoverable from git history until rotation completes — **rotation is the load-bearing half.**~~ **Executed 2026-08-07**: the `serviceaccount` `passsha256` was replaced in `scadaproj/infra/glauth/config.toml` (commit `aada53b`) and the shared GLAuth recreated on `10.100.0.35`, so the literal recoverable from this repo's history no longer binds `dc=zb,dc=local`. The new value lives only in the GLAuth hash, windev's NSSM environment, and dev user-secrets. `wonder-app-vd03` was out of scope (it binds a different, `dc=scadalink`/`dc=scadabridge` directory). **Caveat:** windev's dashboard `/login` verification is deferred — that host's gateway is crash-looping on the unrelated stale-deployment fault filed as NEXT-07 — so the bind was verified directly by `ldapsearch` as `cn=serviceaccount,dc=zb,dc=local` instead. SEC-36 is now fully `Done`. +- ~~**SEC-36** — rotate the dev LDAP service-account credential per `docs/runbooks/SEC-36-ldap-credential-rotation.md` (generate new secret in `scadaproj/infra/glauth`, pre-stage the NSSM env var on deployed hosts, rotate GLAuth on `10.100.0.35`, verify dashboard login). The committed literal is gone from the working tree but remains recoverable from git history until rotation completes — **rotation is the load-bearing half.**~~ **Executed 2026-08-07**: the `serviceaccount` `passsha256` was replaced in `scadaproj/infra/glauth/config.toml` (commit `aada53b`) and the shared GLAuth recreated on `10.100.0.35`, so the literal recoverable from this repo's history no longer binds `dc=zb,dc=local`. The new value lives only in the GLAuth hash, windev's NSSM environment, and dev user-secrets. `wonder-app-vd03` was out of scope (it binds a different, `dc=scadalink`/`dc=scadabridge` directory). **Caveat, since closed:** windev's dashboard `/login` verification was deferred while that host's gateway was crash-looping on the unrelated stale-deployment fault filed as NEXT-07, so the bind was verified directly by `ldapsearch` as `cn=serviceaccount,dc=zb,dc=local` instead. After the 2026-08-07 redeploy the real check ran on windev (login as `multi-role` → 302 + dashboard cookie, anonymous control → `/login`), so the rotated credential is now proven through the `DashboardAuthenticator` path itself; see `docs/runbooks/SEC-36-ldap-credential-rotation.md` Correction 3. SEC-36 is fully `Done`. - ~~**TST-30** — register a second Gitea `act_runner` on `10.100.0.35` per `docs/runbooks/TST-30-second-ci-runner.md` to relieve the single-shared-runner bottleneck.~~ **Executed 2026-08-07**: `gitea-runner-2` (id 5, capacity 2) is online on `10.100.0.35` via the `/opt/gitea` compose stack, same `container.network: traefik`, token from a `0600` file mount; the existing runner (id 1, capacity 4) was untouched. Concurrency verified — jobs from three runs ran simultaneously across both runners, and a `gitea-runner-2` job cloned successfully from `http://gitea:3000`. TST-30 is now fully `Done`. +- **TST-30 follow-up — reset the Gitea instance runner registration token.** Runner-1's compose block was moved to the same `0600` file-mount pattern as runner-2 on 2026-08-07 (compose and both backups now `0600 root:root`, runner-1 recreated with its identity intact), but hygiene alone does not retire the token: both runners share **one instance-scope registration token** that was world-readable for roughly five months and is still live — a probe registered runner id 6 with it, then deleted it. Gitea 1.26.4 exposes no rotation via CLI or API (both paths are get-or-create and hand back the same value), so the reset must be done in the admin web UI ("Reset registration token"). Afterwards, refresh `/opt/gitea/runner_token` on `10.100.0.35` and shred the two token-bearing compose backups — they are the last copies of the old value. See `docs/runbooks/TST-30-second-ci-runner.md`. - **TST-25 follow-ups** — old **TST-05** (scheduled live-MXAccess smoke) is now covered by the `nightly-windev` job; old **TST-24** (client wire tests in CI) is unblocked by the working Windows tier. diff --git a/docs/runbooks/TST-30-second-ci-runner.md b/docs/runbooks/TST-30-second-ci-runner.md index 3d785d7..1ab5260 100644 --- a/docs/runbooks/TST-30-second-ci-runner.md +++ b/docs/runbooks/TST-30-second-ci-runner.md @@ -23,6 +23,17 @@ > re-registered with mac-specific labels if a mac-only job ever needs one). For per-job runner > attribution, `GET /repos/{owner}/{repo}/actions/runs/{id}/jobs` > exposes `runner_id`/`runner_name` on each job; the `actions/tasks` listing does not. +> +> **Follow-up 2026-08-07 — token hygiene on the host.** `gitea-runner` (id 1) now takes its +> registration token from the same `0600` file mount runner-2 uses instead of an inline plaintext +> value in compose, and `/opt/gitea/docker-compose.yml` plus both `.bak` copies are `0600 root:root`; +> runner-1 was recreated alone and kept its identity (`.runner` byte-identical). Both runners share +> **one instance-scope registration token**, which was world-readable for roughly five months and is +> still live — a probe registered runner id 6 with it, then deleted it. Gitea 1.26.4 cannot rotate +> that token from the CLI or the API (both endpoints are get-or-create and return the same value), +> so **the reset is a pending operator action in the admin web UI** ("Reset registration token"). +> After the reset, refresh `/opt/gitea/runner_token` with the new value and shred the two +> token-bearing compose backups, which are the last copies of the old one. Operator steps to relieve the single shared Gitea Actions runner that CI depends on. The repo-side half of TST-30 (documenting the shared-runner/no-cancel reality and the