fix(scripting): ScriptWorker isolation denies the worker every sensitive sibling, not just app\ (#890)
v2-ci / unit-tests (push) Waiting to run
v2-ci / unit-tests (push) Waiting to run
The installer denied NT SERVICE\OtOpcUaScriptWorker only on -InstallRoot. On the wonder layout the node's keys and secrets sit beside it (E:\ApiInstall\OtOpcUa\pki with the application certificate's private key, data\ with secrets.db and LocalDb), inheriting BUILTIN\Users:RX. Users is a restricting SID of the worker token, so a worker could read them. - Install-ScriptWorkerIsolation.ps1 -DenyPaths <dir>,...: each folder gets the same inherited deny as InstallRoot (new SensitiveDirDeny steps). Refuses drive/share roots, script-worker\ and the PE cache; folders must exist; a comma/semicolon string works for powershell -File. - The deny is read AND write (0xD0157: ReadData, WriteData, AppendData, WriteEA, DeleteChild, WriteAttributes, Delete, WRITE_DAC, WRITE_OWNER). Traverse/Execute, ReadAttributes, ReadControl and Synchronize stay so the loader still reaches script-worker\. The write half stops a worker planting a cert in pki\trusted\certs where a data drive lets Users create files in subfolders. - The list is recorded under the anchor service's key (Parameters\DenyPaths, step DenyPathRecord): a re-run removes the deny from folders dropped from the list, -Uninstall removes every recorded one without repeating -DenyPaths. -WhatIf/-PlanOnly print every step. - After an apply, WARN for each protected-DACL item under a denied folder that the inherited deny cannot reach; without -DenyPaths, WARN for pki/data/logs/backup/config/secrets siblings of InstallRoot. Tests: - Dry-run harness 46 -> 86 assertions (QA layout plan, mask bits, refusals, stale removal, record-driven rollback, host account never denied). Mutations (no sibling deny, deny->allow, mask back to ReadData, no stale removal, rollback ignoring the record, script-worker not refused) each turn it red. - Test-ScriptWorkerIsolation.Windows.ps1 (Windows PowerShell 5.1, windev): 34/34, incl. -DenyPaths apply, 11/11 unchanged re-apply, shorter-list re-apply, uninstall from the record, second uninstall unchanged. - New InstallerLayoutIsolationTests (OTOPCUA_ISOLATION_INSTALLER_E2E=1, elevated Windows): a scratch wonder layout under the real restricted worker token. Before install and after uninstall the token reads pki/data/ logs/backup/appsettings and plants a trust-list file; installed it can do neither, still reads script-worker\, a real worker launched from the layout runs a script, and the host still reads everything. Mutating the -DenyPaths denies to a no-op mask turns it red on pki\own\private\otopcua.pfx (reproduces #890). QA vq03/vq04 are NOT re-applied; the command is in docs/deploy/qa-upgrade-b8b4c18f-to-fea7de0a.md. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
This commit is contained in:
@@ -152,6 +152,36 @@ siblings `E:\ApiInstall\OtOpcUa\pki` (including `own\private\*.pfx`) and `data\`
|
||||
`BUILTIN\Users:ReadAndExecute`. Users is one of the worker token's restricting SIDs, so those folders are not covered
|
||||
by the deny. File-level ACLs were not inspected. Follow-up: #890.
|
||||
|
||||
**#890 fix, PENDING re-apply on vq03 and vq04.** Not run on QA yet. This is a deploy step for a later, user-requested window.
|
||||
The installer now takes `-DenyPaths` and records them. Once the scripts at that commit are staged on each node, run the
|
||||
following in an elevated Windows PowerShell 5.1 session. The services can stay up: only folder ACLs change for the worker
|
||||
identity, and no worker runs on QA today.
|
||||
|
||||
```powershell
|
||||
cd <staged scripts\install>
|
||||
$deny = 'E:\ApiInstall\OtOpcUa','E:\ApiInstall\OtOpcUa\pki','E:\ApiInstall\OtOpcUa\data'
|
||||
.\Install-ScriptWorkerIsolation.ps1 -InstallRoot E:\ApiInstall\OtOpcUa\app -HostServiceAccount LocalSystem -ConfigureHostService -DenyPaths $deny -WhatIf
|
||||
.\Install-ScriptWorkerIsolation.ps1 -InstallRoot E:\ApiInstall\OtOpcUa\app -HostServiceAccount LocalSystem -ConfigureHostService -DenyPaths $deny
|
||||
.\Install-ScriptWorkerIsolation.ps1 -InstallRoot E:\ApiInstall\OtOpcUa\app -HostServiceAccount LocalSystem -ConfigureHostService -DenyPaths $deny
|
||||
```
|
||||
|
||||
Before running it, check `Secrets__SqlitePath`, its key file and `LocalDb__Path` in the service environment. Add any folder
|
||||
that sits outside `E:\ApiInstall\OtOpcUa` to `$deny`.
|
||||
|
||||
Expected results:
|
||||
- **First apply: 11 steps, 5 `changed`.** They are `DenyPathRecord`, `InstallRootDeny` (mask `0x1` → `0xD0157`, read + write)
|
||||
and the three `SensitiveDirDeny`. The other 6 steps are `unchanged`.
|
||||
- **Second apply: 11/11 `unchanged`.**
|
||||
- **Check:**
|
||||
- `icacls E:\ApiInstall\OtOpcUa`, `…\pki` and `…\data` and `…\app` each show
|
||||
`NT SERVICE\OtOpcUaScriptWorker:(OI)(CI)(DENY)(W,D,WDAC,WO,RD,DC)`, which is what windev's PowerShell 5.1 renders for
|
||||
`0xD0157`.
|
||||
- `reg query HKLM\SYSTEM\CurrentControlSet\Services\OtOpcUaScriptWorker\Parameters /v DenyPaths` lists the three folders.
|
||||
- The `coverage:` line reports the number of protected items the deny does not reach. Inspect each one WARNed (for
|
||||
example a `pki\own\private` file the SDK wrote with its own DACL).
|
||||
|
||||
Rollback stays `… -ConfigureHostService -Uninstall`. It now also removes the deny from the recorded folders.
|
||||
|
||||
## Configuration set (service registry environment; no file changes, no secrets added)
|
||||
|
||||
| Node | Added |
|
||||
@@ -165,7 +195,8 @@ by the deny. File-level ACLs were not inspected. Follow-up: #890.
|
||||
|
||||
- **Runner only:** set `Scripting__Runner=InProcess` (or remove the four `Scripting__Runner*` entries) and restart the
|
||||
node. To remove the isolation, run `Install-ScriptWorkerIsolation.ps1 -InstallRoot E:\ApiInstall\OtOpcUa\app
|
||||
-HostServiceAccount LocalSystem -ConfigureHostService -Uninstall`.
|
||||
-HostServiceAccount LocalSystem -ConfigureHostService -Uninstall`. After the #890 re-apply, this also removes the recorded
|
||||
`-DenyPaths` denies, so there is no need to repeat them.
|
||||
- **QA binaries:** run `E:\psrem-stage\otopcua-fea7de0a\rollback.ps1` on **both** nodes (stop both first), then start both.
|
||||
The script does three things:
|
||||
1. `app` → `app.failed-fea7de0a`, and `app.bak.20261112-pre-fea7de0a` → `app`;
|
||||
|
||||
Reference in New Issue
Block a user