using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Net.Sockets; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using ServerMonitorManager.Agent; using ServerMonitorManager.Core; using ServerMonitorManager.Provisioning.Helper; using Xunit; namespace ServerMonitorManager.Control.Tests; public sealed class ProvisioningHelperTests { [Fact] public async Task ConfiguredAgentUidMustMatchTheInstalledAgentUser() { var path = Path.Combine(Path.GetTempPath(), $"smm-passwd-{Guid.NewGuid():N}"); await File.WriteAllTextAsync( path, "root:x:0:0:root:/root:/bin/bash\n" + "ochenstarik-smm-agent:x:1234:1234::/var/lib/ochenstarik-server-monitor-manager:/usr/sbin/nologin\n", TestContext.Current.CancellationToken); try { Assert.True(ProvisioningAgentIdentity.MatchesConfiguredUid(path, 1234)); Assert.False(ProvisioningAgentIdentity.MatchesConfiguredUid(path, 1235)); } finally { File.Delete(path); } } [Fact] public void HelperRejectsEveryActionOutsideFixedAllowlist() { using var document = JsonDocument.Parse("{}"); var response = ProvisioningHelperServer.Execute(new ProvisioningHelperRequest( "1", new string('a', 32), "shell", 1, ProvisioningActionCatalog.PreflightModuleHash, document.RootElement.Clone())); Assert.False(response.Success); Assert.Equal("action.denied", response.Code); Assert.Null(response.Preflight); } [Fact] public void HelperAcceptsOnlyEmptyPreflightSchemaOne() { if (!OperatingSystem.IsLinux()) { return; } using var document = JsonDocument.Parse("{}"); var response = ProvisioningHelperServer.Execute(new ProvisioningHelperRequest( "1", new string('b', 32), "preflight", 1, ProvisioningActionCatalog.PreflightModuleHash, document.RootElement.Clone())); Assert.True(response.Success); Assert.Equal("preflight.completed", response.Code); Assert.NotNull(response.Preflight); Assert.NotEmpty(response.Preflight.OperatingSystem); Assert.NotEmpty(response.Preflight.Architecture); } [Fact] public void HelperContractRejectsUnknownJsonMembers() { const string json = """ {"protocolVersion":"1","jobId":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "actionType":"preflight","schemaVersion":1, "moduleHash":"2dc48fb4528a291221954fc2dd3478d431b66fe34228f29684ce1648dbe2f32b", "parameters":{},"command":"id"} """; Assert.Throws(() => JsonSerializer.Deserialize(json, SmmJsonContext.Default.ProvisioningHelperRequest)); } [Fact] public void HelperRejectsMissingJobIdWithoutThrowing() { using var document = JsonDocument.Parse("{}"); var response = ProvisioningHelperServer.Execute(new ProvisioningHelperRequest( "1", null!, "preflight", 1, ProvisioningActionCatalog.PreflightModuleHash, document.RootElement.Clone())); Assert.False(response.Success); Assert.Equal("request.invalid-job", response.Code); } [Fact] public void BaseInstallSchemaRejectsCommandText() { const string json = """ {"timezone":"UTC","locale":"en_US.UTF-8","aptUpdate":true,"aptUpgrade":false, "packageCatalogVersion":1,"packageGroupIds":["core"],"swapMode":"disabled", "swapSizeMiB":null,"vmSwappiness":60,"enableUnattendedUpgrades":true, "rebootPolicy":"never","command":"id"} """; Assert.Throws(() => JsonSerializer.Deserialize(json, SmmJsonContext.Default.SystemBaseInstallParameters)); } [Fact] public void HelperBuildsDeterministicBaseInstallPlanWithoutCommands() { var parameters = new SystemBaseInstallParameters( "UTC", "en_US.UTF-8", true, false, 1, ["development", "core"], "disabled", null, 60, true, "never"); var json = JsonSerializer.SerializeToElement( parameters, SmmJsonContext.Default.SystemBaseInstallParameters); var response = ProvisioningHelperServer.Execute(new ProvisioningHelperRequest( "1", new string('c', 32), "system.base-install", 1, ProvisioningActionCatalog.SystemBaseInstallModuleHash, json)); Assert.True(response.Success); Assert.Equal("system.base-install.plan-ready", response.Code); Assert.Null(response.Preflight); Assert.Equal( ["ca-certificates", "curl", "jq", "build-essential", "git"], response.BaseInstallPlan!.Packages); Assert.Equal("never", response.BaseInstallPlan.RebootPolicy); } [Fact] public async Task ConfirmedTimezoneOnlyPlanUsesAllowlistedBinaryCreatesBackupAndVerifies() { using var authority = CreateAuthority(out var signingKey); using (signingKey) { var plan = CreateTimezoneOnlyPlan("Europe/Berlin"); var grant = SignGrant(authority, signingKey, plan); var events = new List(); var files = new FakeFileSystem(events); files.Files.Add("/usr/share/zoneinfo/Europe/Berlin"); var process = new FakeProcessRunner(events, new(0, "UTC\n", ""), new(0, "", ""), new(0, "Europe/Berlin\n", "")); var executor = new TimezoneProvisioningExecutor( authority, "home", files, process, TimeProvider.System, "/var/lib/ochenstarik-server-monitor-manager/provisioning/rollback"); var result = await executor.ExecuteAsync( CreateExecutionRequest(plan, grant), TestContext.Current.CancellationToken); Assert.True(result.Success); Assert.True(result.Changed); Assert.True(result.Verified); Assert.False(result.RollbackAttempted); Assert.Equal("Europe/Berlin", result.ObservedTimezone); Assert.Equal(2, files.Writes.Count); Assert.True(events.IndexOf("write-backup") < events.IndexOf("process:set-timezone Europe/Berlin")); Assert.All(process.Calls, call => Assert.Equal("/usr/bin/timedatectl", call.FileName)); Assert.Equal( [ "show --property=Timezone --value", "set-timezone Europe/Berlin", "show --property=Timezone --value" ], process.Calls.Select(call => string.Join(' ', call.Arguments))); } } [Theory] [InlineData("null")] [InlineData("[]")] public async Task ExecutionDispatchRejectsNonObjectParametersBeforeMutation(string parametersJson) { using var authority = CreateAuthority(out var signingKey); using (signingKey) using (var parameters = JsonDocument.Parse(parametersJson)) { var plan = CreateTimezoneOnlyPlan("Europe/Berlin"); var events = new List(); var files = new FakeFileSystem(events); files.Files.Add("/usr/share/zoneinfo/Europe/Berlin"); var process = new FakeProcessRunner(events, new(0, "UTC\n", ""), new(0, "", ""), new(0, "Europe/Berlin\n", "")); var executor = new TimezoneProvisioningExecutor( authority, "home", files, process, TimeProvider.System, "/var/lib/ochenstarik-server-monitor-manager/provisioning/rollback"); var server = new ProvisioningHelperServer("unused", 0, executor); var request = CreateExecutionRequest(plan, SignGrant(authority, signingKey, plan)) with { Parameters = parameters.RootElement.Clone() }; var dispatch = typeof(ProvisioningHelperServer).GetMethod( "ExecuteRequestAsync", BindingFlags.Instance | BindingFlags.NonPublic)!; var task = (Task)dispatch.Invoke( server, [request, TestContext.Current.CancellationToken])!; var response = await task; Assert.False(response.Success); Assert.Equal("action.denied", response.Code); Assert.Empty(files.Writes); Assert.Empty(process.Calls); } } [Fact] public async Task ValidNoOpTimezoneCompletesOnlyAfterFactualVerification() { using var authority = CreateAuthority(out var signingKey); using (signingKey) { var plan = CreateTimezoneOnlyPlan("UTC"); var process = new FakeProcessRunner( [], new ProvisioningProcessResult(0, "UTC\n", "")); var files = new FakeFileSystem([]); files.Files.Add("/usr/share/zoneinfo/UTC"); var executor = new TimezoneProvisioningExecutor( authority, "home", files, process, TimeProvider.System, "/var/lib/ochenstarik-server-monitor-manager/provisioning/rollback"); var result = await executor.ExecuteAsync( CreateExecutionRequest(plan, SignGrant(authority, signingKey, plan)), TestContext.Current.CancellationToken); Assert.True(result.Success); Assert.False(result.Changed); Assert.True(result.Verified); Assert.Single(files.Writes); Assert.Single(process.Calls); } } [Fact] public async Task ConsumedGrantCannotBeReplayedEvenAfterVerifiedNoOp() { using var authority = CreateAuthority(out var signingKey); using (signingKey) { var plan = CreateTimezoneOnlyPlan("UTC"); var grant = SignGrant(authority, signingKey, plan); var files = new FakeFileSystem([]); files.Files.Add("/usr/share/zoneinfo/UTC"); var firstProcess = new FakeProcessRunner( [], new ProvisioningProcessResult(0, "UTC\n", "")); var executor = new TimezoneProvisioningExecutor( authority, "home", files, firstProcess, TimeProvider.System, "/var/lib/ochenstarik-server-monitor-manager/provisioning/rollback"); var first = await executor.ExecuteAsync( CreateExecutionRequest(plan, grant), TestContext.Current.CancellationToken); var secondProcess = new FakeProcessRunner([]); var replayExecutor = new TimezoneProvisioningExecutor( authority, "home", files, secondProcess, TimeProvider.System, "/var/lib/ochenstarik-server-monitor-manager/provisioning/rollback"); var replay = await replayExecutor.ExecuteAsync( CreateExecutionRequest(plan, grant), TestContext.Current.CancellationToken); Assert.True(first.Success); Assert.False(replay.Success); Assert.Equal("execution.grant-consumed", replay.Code); Assert.Empty(secondProcess.Calls); } } [Fact] public async Task ForgedMismatchedAndExpiredGrantsCauseZeroMutation() { using var authority = CreateAuthority(out var signingKey); using (signingKey) { var plan = CreateTimezoneOnlyPlan("Europe/Berlin"); var valid = SignGrant(authority, signingKey, plan); var invalid = new[] { valid with { Signature = valid.Signature[..^1] + (valid.Signature[^1] == 'A' ? "B" : "A") }, valid with { JobId = new string('f', 32) }, valid with { NodeId = "other-node" }, valid with { PlanSha256 = new string('0', 64) }, SignGrant(authority, signingKey, plan, DateTimeOffset.UtcNow.AddMinutes(-10)) }; foreach (var grant in invalid) { var files = new FakeFileSystem([]); files.Files.Add("/usr/share/zoneinfo/Europe/Berlin"); var process = new FakeProcessRunner([]); var executor = new TimezoneProvisioningExecutor( authority, "home", files, process, TimeProvider.System, "/var/lib/ochenstarik-server-monitor-manager/provisioning/rollback"); var result = await executor.ExecuteAsync( CreateExecutionRequest(plan, grant), TestContext.Current.CancellationToken); Assert.False(result.Success); Assert.Equal("execution.authorization-denied", result.Code); Assert.Empty(files.Writes); Assert.Empty(process.Calls); } } } [Fact] public async Task ValidGrantForAnotherNodeIsRejectedBeforeMutation() { using var authority = CreateAuthority(out var signingKey); using (signingKey) { var plan = CreateTimezoneOnlyPlan("Europe/Berlin"); var otherNodeGrant = SignGrant(authority, signingKey, plan, nodeId: "other-node"); var localGrant = SignGrant(authority, signingKey, plan); var requests = new[] { CreateExecutionRequest(plan, otherNodeGrant) with { Execution = new ProvisioningBaseInstallExecutionAuthorization( "other-node", plan, otherNodeGrant) }, CreateExecutionRequest(plan, localGrant) with { Execution = new ProvisioningBaseInstallExecutionAuthorization( "other-node", plan, localGrant) } }; foreach (var request in requests) { var files = new FakeFileSystem([]); files.Files.Add("/usr/share/zoneinfo/Europe/Berlin"); var process = new FakeProcessRunner([]); var executor = new TimezoneProvisioningExecutor( authority, "home", files, process, TimeProvider.System, "/var/lib/ochenstarik-server-monitor-manager/provisioning/rollback"); var result = await executor.ExecuteAsync( request, TestContext.Current.CancellationToken); Assert.False(result.Success); Assert.Equal("execution.authorization-denied", result.Code); Assert.Empty(files.Writes); Assert.Empty(process.Calls); } } } [Fact] public async Task UnsupportedBaseInstallFieldsCauseZeroMutation() { using var authority = CreateAuthority(out var signingKey); using (signingKey) { var baseline = CreateTimezoneOnlyPlan("Europe/Berlin"); var unsupportedPlans = new[] { baseline with { Locale = "en_US.UTF-8" }, baseline with { AptUpdate = true }, baseline with { AptUpgrade = true }, baseline with { Packages = ["curl"] }, baseline with { SwapMode = "automatic" }, baseline with { VmSwappiness = 10 }, baseline with { EnableUnattendedUpgrades = true }, baseline with { RebootPolicy = "always" } }; foreach (var plan in unsupportedPlans) { var files = new FakeFileSystem([]); files.Files.Add("/usr/share/zoneinfo/Europe/Berlin"); var process = new FakeProcessRunner([]); var executor = new TimezoneProvisioningExecutor( authority, "home", files, process, TimeProvider.System, "/var/lib/ochenstarik-server-monitor-manager/provisioning/rollback"); var result = await executor.ExecuteAsync( CreateExecutionRequest(plan, SignGrant(authority, signingKey, plan)), TestContext.Current.CancellationToken); Assert.False(result.Success); Assert.Equal("system.base-install.unsupported-fields", result.Code); Assert.Empty(files.Writes); Assert.Empty(process.Calls); } } } [Fact] public void AgentExecutionRequestUsesTheControlAuthorizedPlanAndGrant() { using var authority = CreateAuthority(out var signingKey); using (signingKey) using (var parameters = JsonDocument.Parse("{}")) { var plan = CreateTimezoneOnlyPlan("Europe/Berlin"); var grant = SignGrant(authority, signingKey, plan); var authorization = new ProvisioningBaseInstallExecutionAuthorization("home", plan, grant); var job = new ProvisioningJob( new string('d', 32), "home", "system.base-install", 1, parameters.RootElement.Clone(), ProvisioningJobStates.Running, true, "Change timezone", "operator", DateTimeOffset.UtcNow.AddMinutes(-1), DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddMinutes(30), DateTimeOffset.UtcNow, null, 3, 40, "execute", null); var request = ProvisioningHelperClient.CreateBaseInstallExecutionRequest(job, authorization); Assert.Same(authorization, request.Execution); Assert.Equal(job.Id, request.JobId); Assert.Equal(job.ActionType, request.ActionType); Assert.Equal(ProvisioningActionCatalog.SystemBaseInstallModuleHash, request.ModuleHash); Assert.Equal(JsonValueKind.Object, request.Parameters.ValueKind); } } [Fact] public void AgentRejectsInconsistentOrUnverifiedHelperSuccess() { using var authority = CreateAuthority(out var signingKey); using (signingKey) { var plan = CreateTimezoneOnlyPlan("UTC"); var authorization = new ProvisioningBaseInstallExecutionAuthorization( "home", plan, SignGrant(authority, signingKey, plan)); var unverified = new ProvisioningBaseInstallExecutionResult( true, "bad", "bad", false, false, false, false, "UTC"); var contradictory = new ProvisioningHelperResponse( false, "bad", "bad", null, null, unverified with { Verified = true }); Assert.Throws(() => ProvisioningHelperClient.ValidateBaseInstallExecutionResponse( new ProvisioningHelperResponse(true, "bad", "bad", null, null, unverified), authorization)); Assert.Throws(() => ProvisioningHelperClient.ValidateBaseInstallExecutionResponse( contradictory, authorization)); } } [Fact] public async Task SymlinkedBackupPathIsRejectedBeforeTimezoneMutation() { using var authority = CreateAuthority(out var signingKey); using (signingKey) { var plan = CreateTimezoneOnlyPlan("Europe/Berlin"); var files = new FakeFileSystem([]); files.Files.Add("/usr/share/zoneinfo/Europe/Berlin"); files.SymbolicLinks.Add("/var/lib/ochenstarik-server-monitor-manager/provisioning"); var process = new FakeProcessRunner( [], new ProvisioningProcessResult(0, "UTC\n", "")); var executor = new TimezoneProvisioningExecutor( authority, "home", files, process, TimeProvider.System, "/var/lib/ochenstarik-server-monitor-manager/provisioning/rollback"); var result = await executor.ExecuteAsync( CreateExecutionRequest(plan, SignGrant(authority, signingKey, plan)), TestContext.Current.CancellationToken); Assert.False(result.Success); Assert.Equal("backup.unsafe-path", result.Code); Assert.Empty(files.Writes); Assert.DoesNotContain(process.Calls, call => call.Arguments.Count > 0 && call.Arguments[0] == "set-timezone"); } } [Fact] public async Task VerificationFailureAttemptsAndFactuallyVerifiesRollback() { using var authority = CreateAuthority(out var signingKey); using (signingKey) { var plan = CreateTimezoneOnlyPlan("Europe/Berlin"); var events = new List(); var files = new FakeFileSystem(events); files.Files.Add("/usr/share/zoneinfo/Europe/Berlin"); var process = new FakeProcessRunner(events, new(0, "UTC\n", ""), new(0, "", ""), new(0, "Etc/Unknown\n", ""), new(0, "", ""), new(0, "UTC\n", "")); var executor = new TimezoneProvisioningExecutor( authority, "home", files, process, TimeProvider.System, "/var/lib/ochenstarik-server-monitor-manager/provisioning/rollback"); var result = await executor.ExecuteAsync( CreateExecutionRequest(plan, SignGrant(authority, signingKey, plan)), TestContext.Current.CancellationToken); Assert.False(result.Success); Assert.Equal("timezone.verification-failed", result.Code); Assert.True(result.RollbackAttempted); Assert.True(result.RollbackSucceeded); Assert.Equal( ["set-timezone Europe/Berlin", "set-timezone UTC"], process.Calls .Where(call => call.Arguments.Count > 0 && call.Arguments[0] == "set-timezone") .Select(call => string.Join(' ', call.Arguments))); } } [Fact] public async Task SilentClientDoesNotBlockASecondValidRequest() { if (!OperatingSystem.IsLinux()) { return; } var socketPath = Path.Combine(Path.GetTempPath(), $"smm-{Guid.NewGuid():N}.sock"); using var shutdown = CancellationTokenSource.CreateLinkedTokenSource( TestContext.Current.CancellationToken); shutdown.CancelAfter(TimeSpan.FromSeconds(5)); var server = new ProvisioningHelperServer( socketPath, expectedPeerUserId: GetEffectiveUserId(), connectionTimeout: TimeSpan.FromSeconds(1)); var serverTask = server.RunAsync(shutdown.Token); using var silentClient = new Socket( AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); try { await WaitForSocketAsync(socketPath, shutdown.Token); await silentClient.ConnectAsync( new UnixDomainSocketEndPoint(socketPath), shutdown.Token); var response = await SendPreflightAsync(socketPath, shutdown.Token); Assert.True(response.Success); Assert.Equal("preflight.completed", response.Code); } finally { shutdown.Cancel(); silentClient.Dispose(); try { await serverTask; } catch (OperationCanceledException) { } } Assert.False(File.Exists(socketPath)); } [Fact] public async Task SynchronousExecutionCannotBlockAcceptLoopAndIsCancelledOnShutdown() { if (!OperatingSystem.IsLinux()) { return; } using var authority = CreateAuthority(out var signingKey); using (signingKey) using (var shutdown = CancellationTokenSource.CreateLinkedTokenSource( TestContext.Current.CancellationToken)) { shutdown.CancelAfter(TimeSpan.FromSeconds(5)); var plan = CreateTimezoneOnlyPlan("Europe/Berlin"); var files = new FakeFileSystem([]); files.Files.Add("/usr/share/zoneinfo/Europe/Berlin"); var process = new SynchronouslyBlockingProcessRunner(); var executor = new TimezoneProvisioningExecutor( authority, "home", files, process, TimeProvider.System, "/var/lib/ochenstarik-server-monitor-manager/provisioning/rollback"); var socketPath = Path.Combine(Path.GetTempPath(), $"smm-{Guid.NewGuid():N}.sock"); var server = new ProvisioningHelperServer( socketPath, GetEffectiveUserId(), executor, connectionTimeout: TimeSpan.FromSeconds(5)); var serverTask = server.RunAsync(shutdown.Token); Task? executionTask = null; try { await WaitForSocketAsync(socketPath, shutdown.Token); executionTask = SendRequestPayloadAsync( socketPath, CreateExecutionRequest(plan, SignGrant(authority, signingKey, plan)), shutdown.Token); Assert.True(process.Started.Wait( TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken)); using var secondRequest = CancellationTokenSource.CreateLinkedTokenSource( TestContext.Current.CancellationToken); secondRequest.CancelAfter(TimeSpan.FromSeconds(1)); var response = await SendPreflightAsync(socketPath, secondRequest.Token); Assert.True(response.Success); Assert.Equal("preflight.completed", response.Code); } finally { shutdown.Cancel(); await serverTask; if (executionTask is not null) { try { await executionTask; } catch (OperationCanceledException) when (shutdown.IsCancellationRequested) { } } } Assert.True(process.CancellationObserved); Assert.True(process.RecoveryObserved); Assert.False(File.Exists(socketPath)); } } [Fact] public async Task UnexpectedPeerUserIdIsRejected() { if (!OperatingSystem.IsLinux()) { return; } var socketPath = Path.Combine(Path.GetTempPath(), $"smm-{Guid.NewGuid():N}.sock"); using var shutdown = CancellationTokenSource.CreateLinkedTokenSource( TestContext.Current.CancellationToken); shutdown.CancelAfter(TimeSpan.FromSeconds(5)); var currentUserId = GetEffectiveUserId(); var expectedUserId = currentUserId == uint.MaxValue ? uint.MaxValue - 1 : currentUserId + 1; var server = new ProvisioningHelperServer(socketPath, expectedUserId); var serverTask = server.RunAsync(shutdown.Token); try { await WaitForSocketAsync(socketPath, shutdown.Token); Assert.Null(await SendPreflightPayloadAsync(socketPath, shutdown.Token)); } finally { shutdown.Cancel(); await serverTask; } } [Fact] public async Task PartialConnectionIsClosedAfterTimeout() { if (!OperatingSystem.IsLinux()) { return; } var socketPath = Path.Combine(Path.GetTempPath(), $"smm-{Guid.NewGuid():N}.sock"); using var shutdown = CancellationTokenSource.CreateLinkedTokenSource( TestContext.Current.CancellationToken); shutdown.CancelAfter(TimeSpan.FromSeconds(5)); var server = new ProvisioningHelperServer( socketPath, GetEffectiveUserId(), connectionTimeout: TimeSpan.FromMilliseconds(100)); var serverTask = server.RunAsync(shutdown.Token); try { await WaitForSocketAsync(socketPath, shutdown.Token); using var client = new Socket( AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); await client.ConnectAsync(new UnixDomainSocketEndPoint(socketPath), shutdown.Token); await client.SendAsync(new byte[] { (byte)'{' }, shutdown.Token); var buffer = new byte[1]; var count = await client.ReceiveAsync(buffer, shutdown.Token); Assert.Equal(0, count); } finally { shutdown.Cancel(); await serverTask; } } [Fact] public async Task RequestRateLimitRejectsExcessConnections() { if (!OperatingSystem.IsLinux()) { return; } var socketPath = Path.Combine(Path.GetTempPath(), $"smm-{Guid.NewGuid():N}.sock"); using var shutdown = CancellationTokenSource.CreateLinkedTokenSource( TestContext.Current.CancellationToken); shutdown.CancelAfter(TimeSpan.FromSeconds(5)); var server = new ProvisioningHelperServer( socketPath, GetEffectiveUserId(), requestsPerMinute: 1); var serverTask = server.RunAsync(shutdown.Token); try { await WaitForSocketAsync(socketPath, shutdown.Token); Assert.NotNull(await SendPreflightPayloadAsync(socketPath, shutdown.Token)); Assert.Null(await SendPreflightPayloadAsync(socketPath, shutdown.Token)); } finally { shutdown.Cancel(); await serverTask; } } [Fact] public async Task RequestFramingIsBoundedAndListenerSurvivesInvalidInput() { if (!OperatingSystem.IsLinux()) { return; } var socketPath = Path.Combine(Path.GetTempPath(), $"smm-{Guid.NewGuid():N}.sock"); using var shutdown = CancellationTokenSource.CreateLinkedTokenSource( TestContext.Current.CancellationToken); shutdown.CancelAfter(TimeSpan.FromSeconds(5)); var server = new ProvisioningHelperServer(socketPath, GetEffectiveUserId()); var serverTask = server.RunAsync(shutdown.Token); try { await WaitForSocketAsync(socketPath, shutdown.Token); var exactPayload = await SendPayloadAsync( socketPath, new string(' ', 16 * 1024) + "\n", shutdown.Token); var oversizedPayload = await SendPayloadAsync( socketPath, new string('x', 16 * 1024 + 1), shutdown.Token); var validResponse = await SendPreflightAsync(socketPath, shutdown.Token); var oversizedResponse = JsonSerializer.Deserialize( oversizedPayload!, SmmJsonContext.Default.ProvisioningHelperResponse)!; Assert.DoesNotContain("too large", exactPayload, StringComparison.OrdinalIgnoreCase); Assert.Equal("request.invalid-size", oversizedResponse.Code); Assert.Equal("Invalid helper request size.", oversizedResponse.Message); Assert.True(validResponse.Success); } finally { shutdown.Cancel(); await serverTask; } } private static async Task WaitForSocketAsync(string path, CancellationToken cancellationToken) { while (!File.Exists(path)) { await Task.Delay(10, cancellationToken); } } private static async Task SendPreflightAsync( string socketPath, CancellationToken cancellationToken) { var responsePayload = await SendPreflightPayloadAsync(socketPath, cancellationToken); return JsonSerializer.Deserialize( responsePayload!, SmmJsonContext.Default.ProvisioningHelperResponse)!; } private static async Task SendPreflightPayloadAsync( string socketPath, CancellationToken cancellationToken) { using var document = JsonDocument.Parse("{}"); var request = new ProvisioningHelperRequest( "1", new string('e', 32), "preflight", 1, ProvisioningActionCatalog.PreflightModuleHash, document.RootElement.Clone()); return await SendRequestPayloadAsync(socketPath, request, cancellationToken); } private static Task SendRequestPayloadAsync( string socketPath, ProvisioningHelperRequest request, CancellationToken cancellationToken) { var payload = JsonSerializer.Serialize( request, SmmJsonContext.Default.ProvisioningHelperRequest) + "\n"; return SendPayloadAsync(socketPath, payload, cancellationToken); } private static async Task SendPayloadAsync( string socketPath, string payload, CancellationToken cancellationToken) { using var socket = new Socket( AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); await socket.ConnectAsync(new UnixDomainSocketEndPoint(socketPath), cancellationToken); try { await using var stream = new NetworkStream(socket, ownsSocket: false); await stream.WriteAsync(Encoding.UTF8.GetBytes(payload), cancellationToken); using var reader = new StreamReader(stream, Encoding.UTF8); return await reader.ReadLineAsync(cancellationToken); } catch (IOException) { return null; } catch (SocketException) { return null; } } [DllImport("libc")] private static extern uint geteuid(); private static uint GetEffectiveUserId() => geteuid(); private static ProvisioningHelperRequest CreateExecutionRequest( SystemBaseInstallPlan plan, ProvisioningExecutionGrant grant) { using var document = JsonDocument.Parse("{}"); return new ProvisioningHelperRequest( "1", new string('d', 32), "system.base-install", 1, ProvisioningActionCatalog.SystemBaseInstallModuleHash, document.RootElement.Clone(), new ProvisioningBaseInstallExecutionAuthorization("home", plan, grant)); } private static SystemBaseInstallPlan CreateTimezoneOnlyPlan(string timezone) => new(timezone, "unchanged", false, false, [], "unchanged", null, 60, false, "never", []); private static X509Certificate2 CreateAuthority(out ECDsa key) { key = ECDsa.Create(ECCurve.NamedCurves.nistP256); var request = new CertificateRequest("CN=SMM Test CA", key, HashAlgorithmName.SHA256); request.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true)); return request.CreateSelfSigned( DateTimeOffset.UtcNow.AddMinutes(-1), DateTimeOffset.UtcNow.AddDays(1)); } private static ProvisioningExecutionGrant SignGrant( X509Certificate2 authority, ECDsa key, SystemBaseInstallPlan plan, DateTimeOffset? issuedAt = null, string nodeId = "home") { _ = authority; var now = issuedAt ?? DateTimeOffset.UtcNow; var grant = new ProvisioningExecutionGrant( "1", new string('d', 32), nodeId, "system.base-install", 1, ProvisioningExecutionGrantCodec.ComputePlanSha256(plan), now.ToUnixTimeSeconds(), now.AddMinutes(2).ToUnixTimeSeconds(), new string('e', 32), ProvisioningExecutionGrantCodec.SignatureAlgorithm, string.Empty); var signature = key.SignData( ProvisioningExecutionGrantCodec.CreateSigningPayload(grant), HashAlgorithmName.SHA256, DSASignatureFormat.IeeeP1363FixedFieldConcatenation); return grant with { Signature = ProvisioningExecutionGrantCodec.EncodeBase64Url(signature) }; } private sealed class FakeFileSystem(List events) : IProvisioningFileSystem { public HashSet Files { get; } = new(StringComparer.Ordinal); public HashSet SymbolicLinks { get; } = new(StringComparer.Ordinal); public List<(string Path, string Content)> Writes { get; } = []; public bool FileExists(string path) => Files.Contains(path); public bool IsSymbolicLink(string path) => SymbolicLinks.Contains(path); public void CreateOwnerOnlyDirectory(string path) => events.Add($"mkdir:{path}"); public void WriteOwnerOnlyFile(string path, string content) { if (Writes.Any(write => string.Equals(write.Path, path, StringComparison.Ordinal))) { throw new IOException("Owner-only file already exists."); } Writes.Add((path, content)); events.Add("write-backup"); } } private sealed class FakeProcessRunner( List events, params ProvisioningProcessResult[] results) : IProvisioningProcessRunner { private readonly Queue _results = new(results); public List<(string FileName, IReadOnlyList Arguments)> Calls { get; } = []; public Task RunAsync( string fileName, IReadOnlyList arguments, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); Calls.Add((fileName, arguments.ToArray())); events.Add($"process:{string.Join(' ', arguments)}"); return Task.FromResult(_results.Count == 0 ? throw new InvalidOperationException("Unexpected process call.") : _results.Dequeue()); } } private sealed class SynchronouslyBlockingProcessRunner : IProvisioningProcessRunner { private int _calls; public ManualResetEventSlim Started { get; } = new(false); public bool CancellationObserved { get; private set; } public bool RecoveryObserved { get; private set; } public Task RunAsync( string fileName, IReadOnlyList arguments, CancellationToken cancellationToken) { var call = Interlocked.Increment(ref _calls); if (call == 1) { return Task.FromResult(new ProvisioningProcessResult(0, "UTC\n", "")); } if (call == 2) { Started.Set(); cancellationToken.WaitHandle.WaitOne(); CancellationObserved = cancellationToken.IsCancellationRequested; cancellationToken.ThrowIfCancellationRequested(); throw new InvalidOperationException("Cancellation was not observed."); } RecoveryObserved = true; return Task.FromResult(call switch { 3 => new ProvisioningProcessResult(0, "Europe/Berlin\n", ""), 4 => new ProvisioningProcessResult(0, "", ""), 5 => new ProvisioningProcessResult(0, "UTC\n", ""), _ => throw new InvalidOperationException("Unexpected recovery process call.") }); } } }