Files
MBN_STOCK_WEBVIEW/tests/MBN_STOCK_WEBVIEW.LegacyWeb.Tests/LegacyGateAHarnessContractTests.cs

319 lines
15 KiB
C#

namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;
public sealed class LegacyGateAHarnessContractTests
{
private const string CurrentMsixSha256 =
"0E44F87FDD31852B7DDFCD6CF3A6325DA674F1F5A23AD39CB262A725AEB84367";
private static readonly string RepositoryRoot = FindRepositoryRoot();
private static readonly string Runner = ReadScript("Invoke-LegacyPgmPrepareGate.ps1");
private static readonly string PlanBuilder = ReadScript("New-LegacyPgmPreparePlan.ps1");
private static readonly string NodeHelper = ReadScript("Test-LegacyPgmPrepareGate.mjs");
[Fact]
public void Node_receives_the_bearer_only_from_the_parent_owned_JIT_pipe()
{
Assert.Contains("import net from \"node:net\";", NodeHelper, StringComparison.Ordinal);
Assert.Contains("\"capability-sha256\", \"permit-pipe\"", NodeHelper,
StringComparison.Ordinal);
Assert.Contains(
"name.startsWith(\"MBN_LEGACY_PGM_GATE_A_\")",
NodeHelper,
StringComparison.Ordinal);
Assert.DoesNotContain(
"process.env[capabilityName]",
NodeHelper,
StringComparison.Ordinal);
var receive = Slice(
NodeHelper,
"async function receiveJitCapability(expiresAt)",
"async function prepareOnce(expiresAt)");
Assert.Contains("net.createConnection(pipePath)", receive, StringComparison.Ordinal);
Assert.Contains("bytes.length !== 64", receive, StringComparison.Ordinal);
Assert.Contains("sha256(bytes) !== configuration.capabilitySha256", receive,
StringComparison.Ordinal);
Assert.Contains("jitPermitReceived = true", receive, StringComparison.Ordinal);
var prepare = Slice(
NodeHelper,
"async function prepareOnce(expiresAt)",
"async function captureScreenshot()");
var boundary = RequiredIndex(prepare, "const preRevision = before.state.revision;");
var permit = RequiredIndex(prepare, "await receiveJitCapability(expiresAt)");
var arm = RequiredIndex(prepare, "__legacyPgmPrepareGate.arm(");
var dispatchPossible = RequiredIndex(prepare, "prepareDispatchPossible = true;");
Assert.True(boundary < permit && permit < arm && arm < dispatchPossible);
var execute = Slice(NodeHelper, "async function execute()", "try {");
Assert.Contains(
"installGate(configuration.planSha256, configuration.capabilitySha256)",
execute,
StringComparison.Ordinal);
}
[Fact]
public void Runner_authenticates_the_exact_Node_client_before_one_JIT_delivery()
{
var start = Slice(
Runner,
"function Start-NodeHelper([object] $Plan)",
"function Write-Evidence([string] $Path)");
Assert.Contains("New-CurrentUserOneShotPipe 'Node'", start, StringComparison.Ordinal);
Assert.Contains("'--capability-sha256'", start, StringComparison.Ordinal);
Assert.Contains("'--permit-pipe'", start, StringComparison.Ordinal);
Assert.DoesNotContain(
"$info.EnvironmentVariables[$capabilityName]",
start,
StringComparison.Ordinal);
Assert.Contains("MBN_LEGACY_PGM_GATE_A_*", start, StringComparison.Ordinal);
Assert.Contains("Assert-NodePermitClient", start, StringComparison.Ordinal);
var client = Slice(
Runner,
"function Assert-NodePermitClient(",
"function Stop-NodeHelperFailStop(");
Assert.Contains("ReadNamedPipeClientProcessId", client, StringComparison.Ordinal);
Assert.Contains("$clientProcessId -ne $Process.Id", client, StringComparison.Ordinal);
Assert.Contains("$Process.StartTime.ToUniversalTime().Ticks", client,
StringComparison.Ordinal);
Assert.Contains("[int]$row.ParentProcessId -ne $PID", client,
StringComparison.Ordinal);
Assert.Contains("SamePath ([string]$row.ExecutablePath) $ExpectedExecutable", client,
StringComparison.Ordinal);
var delivery = RequiredIndex(start, "$permitPipe.Stream.Write($bytes,0,$bytes.Length)");
foreach (var marker in new[]
{
"Assert-NodePermitClient",
"Assert-Authorization $script:authorization $Plan",
"Assert-Application $script:application",
"Assert-Pgm $Plan.pgm",
"Assert-Cdp ([int]$Plan.webView.port)"
})
{
Assert.True(RequiredIndex(start, marker) < delivery, marker);
}
var stop = Slice(
Runner,
"function Stop-NodeHelperFailStop(",
"function Start-NodeHelper([object] $Plan)");
Assert.Contains("Close-OneShotPipe $Pipe", stop, StringComparison.Ordinal);
Assert.Contains("$Process.Kill()", stop, StringComparison.Ordinal);
Assert.Contains("$Process.WaitForExit(0)", stop, StringComparison.Ordinal);
Assert.Contains("$Process.WaitForExit(5000)", stop, StringComparison.Ordinal);
Assert.Contains("was not proven stopped", stop, StringComparison.Ordinal);
Assert.Contains("only the control-plane Node helper", stop, StringComparison.Ordinal);
Assert.DoesNotContain("$script:application", stop, StringComparison.Ordinal);
Assert.DoesNotContain("Assert-Pgm", stop, StringComparison.Ordinal);
var catchBlock = start[RequiredIndex(start, " catch {")..];
Assert.Contains("Stop-NodeHelperFailStop $process $permitPipe", catchBlock,
StringComparison.Ordinal);
Assert.Contains("exceeded its outer timeout; parent fail-stop is required", start,
StringComparison.Ordinal);
var monitorTry = Slice(start, " try {\n $process.Refresh()", " catch {");
Assert.True(
RequiredIndex(monitorTry, "$process.StartTime.ToUniversalTime().Ticks") <
RequiredIndex(monitorTry, "while (-not $process.HasExited"));
Assert.Contains("if (-not $process.HasExited)", monitorTry, StringComparison.Ordinal);
Assert.Contains("$process.WaitForExit(0)", monitorTry, StringComparison.Ordinal);
}
[Fact]
public void Package_wrapper_receives_launch_environment_over_an_authenticated_pipe_not_its_command_line()
{
var package = Slice(
Runner,
"function Start-Package([object] $Plan)",
"function Quote-Argument([string] $Value)");
Assert.DoesNotContain("$encodedEnvironment", package, StringComparison.Ordinal);
Assert.Contains("New-CurrentUserOneShotPipe 'Package'", package,
StringComparison.Ordinal);
Assert.Contains("Assert-PackagePermitClient", package, StringComparison.Ordinal);
Assert.Contains("[LegacyPgmPreparePackageIdentity]::Read($process.Handle)",
Runner,
StringComparison.Ordinal);
Assert.Contains("$payload = $utf8NoBom.GetBytes(($environment | ConvertTo-Json -Compress))",
package,
StringComparison.Ordinal);
Assert.Contains("$launchPipe.Stream.Write($payload,0,$payload.Length)", package,
StringComparison.Ordinal);
Assert.Contains("$client.ReadByte() -ne -1", package, StringComparison.Ordinal);
var wrapperStop = Slice(
Runner,
"function Stop-PackageWrapperFailStop(",
"function Start-Package([object] $Plan)");
Assert.Contains("$Process.Kill()", wrapperStop, StringComparison.Ordinal);
Assert.Contains("$Process.WaitForExit(0)", wrapperStop, StringComparison.Ordinal);
Assert.Contains("$Process.WaitForExit(5000)", wrapperStop, StringComparison.Ordinal);
Assert.Contains("was not proven stopped", wrapperStop, StringComparison.Ordinal);
Assert.DoesNotContain("catch { return }", wrapperStop, StringComparison.Ordinal);
Assert.Contains("$wrapper.WaitForExit(0)", package, StringComparison.Ordinal);
Assert.Contains("without proving the exact wrapper exited", package,
StringComparison.Ordinal);
var child = Slice(package, " $child = @\"", "\"@\n $encodedChild");
Assert.DoesNotContain("$encodedEnvironment", child, StringComparison.Ordinal);
Assert.DoesNotContain("$script:gateCapability", child, StringComparison.Ordinal);
Assert.DoesNotContain("$capabilityName", child, StringComparison.Ordinal);
Assert.DoesNotContain("$environment", child, StringComparison.Ordinal);
var pipes = Slice(
Runner,
"function New-CurrentUserOneShotPipe([string] $Purpose)",
"function Close-OneShotPipe([object] $Pipe)");
Assert.Contains("[IO.Pipes.PipeDirection]::InOut", pipes, StringComparison.Ordinal);
Assert.Contains("[IO.Pipes.PipeSecurity]::new()", pipes, StringComparison.Ordinal);
Assert.Contains("$identity.User", pipes, StringComparison.Ordinal);
Assert.Contains("SetAccessRuleProtection($true, $false)", pipes,
StringComparison.Ordinal);
}
[Fact]
public void Managed_runtime_injection_environment_is_rejected_and_removed_from_children()
{
var preflight = Slice(
Runner,
"function Assert-NoUnsafeEnvironment",
"function Assert-Pgm([object] $Pgm)");
var package = Slice(
Runner,
"function Start-Package([object] $Plan)",
"function Quote-Argument([string] $Value)");
var child = Slice(package, " $child = @\"", "\"@\n $encodedChild");
var node = Slice(
Runner,
"function Start-NodeHelper([object] $Plan)",
"function Write-Evidence([string] $Path)");
foreach (var prefix in new[] { "DOTNET_*", "CORECLR_*", "COR_*", "COMPlus_*" })
{
Assert.Contains(prefix, preflight, StringComparison.Ordinal);
Assert.True(Count(child, prefix) >= 2, $"{prefix} must be removed before and after app launch.");
Assert.Contains(prefix, node, StringComparison.Ordinal);
}
foreach (var scope in new[]
{
"[EnvironmentVariableTarget]::Process",
"[EnvironmentVariableTarget]::User",
"[EnvironmentVariableTarget]::Machine"
})
{
Assert.Contains(scope, preflight, StringComparison.Ordinal);
}
}
[Fact]
public void Database_source_is_pinned_leased_and_cannot_be_overridden_by_environment()
{
Assert.Contains("approvedDatabaseConfigPath", PlanBuilder, StringComparison.Ordinal);
Assert.Contains(
"database = New-FilePin $approvedDatabaseConfigPath 'database config'",
PlanBuilder,
StringComparison.Ordinal);
Assert.Contains(
"Assert-FilePin $script:plan.configuration.database 'database config'",
Runner,
StringComparison.Ordinal);
Assert.Contains(
"Add-ReadLease ([string]$script:plan.configuration.database.path) 'database config'",
Runner,
StringComparison.Ordinal);
Assert.Contains("filePinsValidated = 12", Runner, StringComparison.Ordinal);
Assert.Contains("const databasePin = plan?.configuration?.database;", NodeHelper,
StringComparison.Ordinal);
Assert.Contains("!isSha256(databasePin?.sha256)", NodeHelper,
StringComparison.Ordinal);
var preflight = Slice(
Runner,
"function Assert-NoUnsafeEnvironment",
"function Assert-Pgm([object] $Pgm)");
var package = Slice(
Runner,
"function Start-Package([object] $Plan)",
"function Quote-Argument([string] $Value)");
var child = Slice(package, " $child = @\"", "\"@\n $encodedChild");
foreach (var prefix in new[]
{
"MBN_STOCK_ORACLE_*", "MBN_STOCK_MARIADB_*", "MBN_STOCK_DB_*"
})
{
Assert.Contains(prefix, preflight, StringComparison.Ordinal);
Assert.True(Count(child, prefix) >= 2, $"{prefix} must be removed before and after app launch.");
}
}
[Fact]
public void Plan_contract_requires_helper_fail_stop_and_the_current_package_pin()
{
foreach (var source in new[] { Runner, PlanBuilder })
{
Assert.Contains(CurrentMsixSha256, source, StringComparison.Ordinal);
}
Assert.Contains("nodeHelperFailStopRequired = $true", PlanBuilder,
StringComparison.Ordinal);
Assert.Contains("packageWrapperFailStopRequired = $true", PlanBuilder,
StringComparison.Ordinal);
Assert.Contains("'nodeHelperFailStopRequired'", Runner, StringComparison.Ordinal);
Assert.Contains("'packageWrapperFailStopRequired'", Runner, StringComparison.Ordinal);
Assert.Contains("plan?.safety?.nodeHelperFailStopRequired !== true", NodeHelper,
StringComparison.Ordinal);
Assert.Contains("plan?.safety?.packageWrapperFailStopRequired !== true", NodeHelper,
StringComparison.Ordinal);
Assert.Contains(
"Assert-JsonInt $Plan.safety.nodeHelperInvocationMaximum 1",
Runner,
StringComparison.Ordinal);
}
private static string Slice(string source, string startMarker, string endMarker)
{
var start = RequiredIndex(source, startMarker);
var end = RequiredIndex(source, endMarker, start + startMarker.Length);
return source[start..end];
}
private static int RequiredIndex(string source, string marker, int startIndex = 0)
{
var index = source.IndexOf(marker, startIndex, StringComparison.Ordinal);
Assert.True(index >= 0, $"Required source marker was not found: {marker}");
return index;
}
private static int Count(string source, string marker)
{
var count = 0;
var index = 0;
while ((index = source.IndexOf(marker, index, StringComparison.Ordinal)) >= 0)
{
count++;
index += marker.Length;
}
return count;
}
private static string ReadScript(string fileName) =>
File.ReadAllText(Path.Combine(RepositoryRoot, "scripts", fileName));
private static string FindRepositoryRoot()
{
var current = new DirectoryInfo(AppContext.BaseDirectory);
while (current is not null)
{
if (File.Exists(Path.Combine(current.FullName, "MBN_STOCK_WEBVIEW.sln")))
{
return current.FullName;
}
current = current.Parent;
}
throw new DirectoryNotFoundException("Repository root was not found.");
}
}