Phase 3 PR 39 — LiveStackFixture pre-flight detect for elevated shell. The OtOpcUaGalaxyHost named-pipe ACL allows the configured SID but explicitly DENIES Administrators per decision #76 / PipeAcl.cs (production-hardening — keeps an admin shell on a deployed box from connecting to the IPC channel without going through the configured service principal). A test process running with a high-integrity elevated token carries the Administrators group in its security context regardless of whose user it 'is', so the deny rule trumps the user's allow and the pipe connect returns UnauthorizedAccessException at the prerequisite-probe stage. Functionally correct but operationally confusing — when this hit during the PR 38 install workflow it took five steps to diagnose ('the user IS in the allow list, why is the pipe denying access?'). The pre-existing ParityFixture (PR 18) already documents this with an explicit early-skip; LiveStackFixture (PR 37) didn't.

PR 39 closes the gap. New IsElevatedAdministratorOnWindows static helper (Windows-only via RuntimeInformation.IsOSPlatform; non-Windows hosts return false and let the prerequisite probe own the skip-with-reason path) checks WindowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator) on the current process token. When true, InitializeAsync short-circuits to a SkipReason that names the cause directly: 'elevated token's Admins group membership trumps the allow rule — re-run from a NORMAL (non-admin) PowerShell window'. Catches and swallows any probe-side exception so a Win32 oddity can't crash the test fixture; failed probe falls through to the regular prerequisite path.
The check fires BEFORE AvevaPrerequisites.CheckAllAsync runs because the prereq probe's own pipe connect hits the same admin-deny and surfaces UnauthorizedAccessException with no context. Short-circuiting earlier saves the 10-second probe + produces a single actionable line.
Tests — verified manually from an elevated bash session against the just-installed OtOpcUaGalaxyHost service: skip message reads 'Test host is running with elevated (Administrators) privileges, but the OtOpcUaGalaxyHost named-pipe ACL explicitly denies Administrators per the IPC security design (decision #76 / PipeAcl.cs). Re-run from a NORMAL (non-admin) PowerShell window — even when your user is already in the pipe's allow list, the elevated token's Admins group membership trumps the allow rule.' Proxy.Tests Unit: 17 pass / 0 fail (unchanged — fixture change is non-breaking; existing tests don't run as admin in normal CI flow). Build clean.
Bonus: gitignored .local/ directory (a previous direct commit on local v2 that I'm now landing here) so per-install secrets like the Galaxy.Host shared-secret file don't leak into the repo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-18 19:17:43 -04:00
parent a61e637411
commit 8fb3dbe53b

View File

@@ -1,3 +1,6 @@
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
@@ -40,6 +43,25 @@ public sealed class LiveStackFixture : IAsyncLifetime
public async ValueTask InitializeAsync()
{
// 0. Elevated-shell short-circuit. The OtOpcUaGalaxyHost pipe ACL allows the configured
// SID but explicitly DENIES Administrators (decision #76 — production hardening).
// A test process running with a high-integrity token (any elevated shell) carries the
// Admins group in its security context, so the deny rule trumps the user's allow and
// the pipe connect returns UnauthorizedAccessException — technically correct but
// the operationally confusing failure mode that ate most of the PR 37 install
// debugging session. Surfacing it explicitly here saves the next operator the same
// five-step diagnosis. ParityFixture has the same skip with the same rationale.
if (IsElevatedAdministratorOnWindows())
{
SkipReason =
"Test host is running with elevated (Administrators) privileges, but the " +
"OtOpcUaGalaxyHost named-pipe ACL explicitly denies Administrators per the IPC " +
"security design (decision #76 / PipeAcl.cs). Re-run from a NORMAL (non-admin) " +
"PowerShell window — even when your user is already in the pipe's allow list, " +
"the elevated token's Admins group membership trumps the allow rule.";
return;
}
// 1. AVEVA + OtOpcUa service state — actionable diagnostic if anything is missing.
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
PrerequisiteReport = await AvevaPrerequisites.CheckAllAsync(
@@ -111,6 +133,28 @@ public sealed class LiveStackFixture : IAsyncLifetime
{
if (SkipReason is not null) Assert.Skip(SkipReason);
}
private static bool IsElevatedAdministratorOnWindows()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return false;
return CheckWindowsAdminToken();
}
[SupportedOSPlatform("windows")]
private static bool CheckWindowsAdminToken()
{
try
{
using var identity = WindowsIdentity.GetCurrent();
return new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator);
}
catch
{
// Probe shouldn't crash the test; if we can't determine elevation, optimistically
// continue and let the actual pipe connect surface its own error.
return false;
}
}
}
[CollectionDefinition(Name)]