Commit Graph

2 Commits

Author SHA1 Message Date
Joseph Doherty f75ee7d089 fix(scripting): ScriptWorker isolation denies the worker every sensitive sibling, not just app\ (#890)
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
2026-11-12 17:31:28 -05:00
Joseph Doherty 88e53f7f84 fix(install): Install-ScriptWorkerIsolation.ps1 works on PowerShell 7 and 5.1, -Uninstall included (#883)
First real run of the #780 installer (windev, #800 gate) failed on both hosts and its rollback failed too:
- PowerShell 7: DirectoryInfo.GetAccessControl/SetAccessControl do not exist on .NET. Use Get-Acl / Set-Acl.
- Windows PowerShell 5.1: an if-expression unrolled the wanted ACE to a scalar ($null on -Remove), so .Count threw under
  StrictMode. Wrap it in @(...).
- Found on the re-run: an apply from 5.1 was not idempotent. 5.1 passes the literal quotes in sc.exe binPath= with legacy
  quoting, so the anchor path was stored unquoted and AnchorService reported 'changed' on every run. sc create/config now
  runs through Invoke-OtOpcUaSc with an explicit command line, identical on both hosts.

Adds scripts/install/tests/Test-ScriptWorkerIsolation.Windows.ps1: a Windows-executed dry run -> apply -> re-apply ->
uninstall -> uninstall test (scratch names, stand-in host service) for either host. The plan-only harness cannot see these.

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
2026-11-12 11:24:01 -05:00