Repository navigation
Install scripts: the service account gets Read & Execute on the install root and Modify only where it writes (#4052) - #4090
Conversation
… grant (incomplete, see PR body)
…ecute + Modify on pg-runtime/pg-runtime-prev Lock-DarlingInstallTree (install-darling.ps1, upgrade-darling.ps1) previously granted the service account Modify on the whole install tree. It now grants Read & Execute on the root and Modify only on pg-runtime\, pg-runtime-prev\, and any $extraServiceDirectories entry (created ahead of time if missing). The verify/close walk trusts the service SID only under those specific paths - a write grant it holds anywhere else in the tree is treated like a stranger's and closed - and re-grants Modify on a service write directory immediately after closing a planted ACE there, so the real grant is never left stripped. Byte-identical between both scripts, as DarlingInstallLocationTests checks. Continuation of #4052 (see PR #4090 body for the accepted research this builds on).
…a service directory helper, update tests
…4038-shaped leftover Modify ACE
…ver as a name, avoid PS 5.1's ambiguous Split-Path Found on a PowerShell 5.1 run of the lock on a standalone Windows 11 box, with a real (untrusted) service account and step 4b's darling.json ACL: - the narrowed trust stripped the service's own FullControl from darling.json and its backups (the explicit-ACE branch), so the service could not read its config, and reported both files as owned by a stranger; - Get-DarlingExtraServiceWriteDirectories returned a rooted path, which the lock Join-Paths onto the root (C:\a + C:\a\b = C:\a\C:\a\b); - Split-Path -LiteralPath -Parent is an ambiguous parameter set on 5.1 and threw on every bring-your-own-Postgres install. The live lock test runs as TrustedInstaller (trusted everywhere), so it could not see the first. A static pin covers all three.
|
Box run phase 1 (install, start, collect, restart): PASS, on a standalone Windows 11 ARM64 VM (x64 build under emulation), from this branch at d3b1815. The coordinator ran it after the worker it dispatched ran out of turns during staging. Differences from a real release, none of which touches the ACL behaviour under test:
Also found:
Not yet run (phase 2), each a separate step on this VM:
On the maintainer's machine after a nightly: a gMSA account, and nested domain admin. A VM snapshot from before the run is kept ( |
…change what runs from the root
…is PR depends on)
|
Box run phase 2 (upgrade with a runtime rescue, locked re-run): PASS once dev is merged in. It also showed why #4097 had to come first. Same VM and install as phase 1. The new build's
Not testable on this VM:
These go to the maintainer's Windows machine after a nightly, along with the gMSA and nested-domain-admin cases. Merge order: this branch now contains #4097, so it can merge on its own. |
… (the runtime folders), recursing like every real caller After #4052 the service holds RX on the root and Modify only on pg-runtime and pg-runtime-prev, so a root-only probe finds nothing untrusted and the test's premise (without the account > 0) cannot hold. The real checks all run Get-UntrustedWriteGrantees -Recurse, so the probe does too. Verified on a Windows 11 VM against the branch's own functions: tree without the account = 2 (the two runtime folders), with it = 0, root alone = 0 (pinned as the #4052 gain).
…ted (#4111) Refs #4052. Client-site PR #4090 narrowed the install-tree lock to grant the service account Modify only on pg-runtime\ and pg-runtime-prev\, creating each best-effort. A non-elevated shell cannot use the Administrators grant left on the locked root, so the best-effort create fails and the lock reports the directory in its open list instead of making it. Two tests read the ACL of pg-runtime-prev\ unconditionally, so they threw or asserted the wrong count outside CI's elevated run. Both tests now branch on the elevated= marker they already print: elevated assertions are unchanged, and the non-elevated branch asserts the lock's own open: report instead of reading a path that was never created. Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* Add 48 buffered CHANGELOG entries in one splice Adds 48 entries and 48 link refs (#4057, #4077, #4079, #4081, #4082, #4083, #4084, #4085, #4086, #4087, #4088, #4089, #4090, #4091, #4092, #4093, #4095, #4096, #4099, #4100, #4101, #4103, #4105, #4106, #4107, #4108, #4109, #4110, #4111, #4113, #4114, #4115, #4116, #4117, #4118, #4119, #4120, #4121, #4122, #4123, #4124, #4125, #4126, #4127, #4131, #4133, #4136, #4141). Each PR's entry was buffered, and this lands every entry whose PR was merged on origin/dev when it ran. Entries found under a bare section line (none unless the old script ran again) move into the matching ### section, below the new entries. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC * Changelog splice: add #4137's entry, which merged before the splice but was missed #4137 (pg_plan_capture reads csvlog) merged at 05:57Z with a CHANGELOG entry section in its body. It was neither spliced nor listed as needing no entry. Its entry goes under Fixed, right after #4136's, and #4136's last sentence now points to it instead of saying plan capture is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUU29PuGg9TUFBsACgZg2K --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This is part of #4052: the install scripts narrow the Darling service account's rights on its install tree. The service-side prep, #4097 (the service empties
pg-runtime-previn place instead of deleting and recreating it), merged first, and this branch now contains it (merge a4583d1). The box run showed the runtime upgrade fails without it.Part of #4052 (worded "Part of" on purpose: the merge watcher closes any issue a merged body says it closes).
What changed
Lock-DarlingInstallTreechanges the same way ininstall-darling.ps1andupgrade-darling.ps1; the function text stays byte-identical across the two scripts./grant:rRead & Execute. The:rmatters: Install and upgrade lock the Darling install folder, so an ordinary local user can no longer replace the service's binaries (#4034) #4038's lock left an explicit(OI)(CI)MACE for the service on upgraded installs, and a plain/grantonly adds to it.pg-runtime\andpg-runtime-prev\: the lock creates them if missing and grants the service Modify on each.darling-keys\, the bring-your-own-PostgreSQL key folder (DarlingLogHashKeyFile.BringYourOwnDirectoryName), gets the same Modify grant. It applies only whendarling.jsonsetspostgres.managed = falseand sits in the install root. The newGet-DarlingExtraServiceWriteDirectoriesworks this out, and every caller wires it in.darling.jsonand its backups. Step 4b gives the service an explicit FullControl on those files by design (Security: darling.json holds recoverable secrets and is never ACL-hardened #1647). A write grant the service holds anywhere else is closed like a stranger's. A service-write folder gets its Modify back right after its ACL is reset.Where the service writes under the install root
pg-runtime\(the runtime extract, thepg-runtime.sha256/.stamp/.blockedstamps,pgsql.failed)DarlingManagedPostgres,DarlingStoreUpgrade)pg-runtime-prev\(the rescued runtime)darling-keys\(bring-your-own PostgreSQL, outside a container)DarlingLogHashKeyFile)darling.json,darling.json.bak-*wwwroot\UseStaticFilesonly; no filesystem write API under the web rootFound by the coordinator, verified on Windows
Before this was pushed, the coordinator ran the lock as extracted from the scripts on PowerShell 5.1, on a standalone Windows 11 VM. It used a real, untrusted service account (NETWORK SERVICE),
darling.jsonand a backup carrying step 4b's ACL, and #4038's leftover root ACE. That run found three defects in the earlier commits, fixed in d3b1815:darling.jsonand its backup, through the walk's explicit-ACE branch. The service could not have read its config. The run also reported both files as owned by a stranger. The live lock test runs as TrustedInstaller, which is trusted everywhere, so it cannot see this.Join-Path 'C:\a' 'C:\a\b'=C:\a\C:\a\b.Split-Path -LiteralPath -Parentis an ambiguous parameter set on 5.1, so it threw on every bring-your-own-PostgreSQL install.The same run on the fixed functions, two passes (the second is an upgrade re-run):
svc.exeandwwwroot;pg-runtime,pg-runtime-prevanddarling-keys, and FullControl ondarling.jsonand its backup;darling-keysonly formanaged: falsewith the config in the root.A new static pin,
TheNarrowedLock_KeepsTheServiceOnItsConfig_AndHandsTheKeyFolderOverAsAName, covers all three defects.Tests
DarlingInstallLocationTestson macOS: no new failures against dev. The class's PowerShell-executing tests can't run there (nopowershell.exe), so they run in Windows CI. Its live lock test now expectsserviceRights=ReadAndExecute, Synchronizeon the root, and Modify on the two runtime folders.Box run (standalone Windows 11 VM)
pg-runtime-prev, then a locked re-run): PASS with Store upgrade keeps pg-runtime-prev in place instead of deleting and recreating it (prep for #4052) #4097 included. Without it, the rescue fails exactly as predicted. See the phase 2 comment.CHANGELOG entry