feat: declare every protocol method's direction and enforce it in the router; add the zero-trust design [CI]
ci/woodpecker/push/main-amd64-build Pipeline was successful

Methods, roles and binary channels are declared in one table per plane (node and peer). RpcRouter checks direction, kind and role when handlers are registered, when messages arrive (fleet_protocol_violation, disconnect after repeated violations) and before sending. NodeConnection is split by responsibility.
This commit is contained in:
justme committed 2026-10-07 16:17:02 +08:00
1 parent ee22309e65
commit 1ff847e45a
36 files changed
+1799 -639

No files matched your search

@@ -168,15 +168,45 @@ public sealed class OperationsAndSecurityTests
await agent.SendBinaryAsync([200, 1, 2, 3]); // 未知的二进制通道,长度不够一个完整的头部
await agent.SendBinaryAsync(OutboxFrame.Build(99, Guid.NewGuid(), [1])); // 没有注册的通道
// 节点不能调用任何“执行”类的方法:Fleet 根本没有这样的路由
// 节点不能调用任何“执行”类的方法:这些方法没有声明,是协议违规,直接拒绝
foreach (string method in new[] { "fleet.exec", "fleet.shell", "moshiro.exec", "moshiro.write_file", "fleet.update" })
{
RpcMessage reply = await agent.RequestRawAsync(method);
Assert.Equal("fleet_unsupported", reply.Error!.Data!.Code);
Assert.Equal("fleet_protocol_violation", reply.Error!.Data!.Code);
}
// 连接仍然可用:路由器照常回复
Assert.Equal("fleet_unsupported", (await agent.RequestRawAsync("fleet.exec")).Error!.Data!.Code);
// 连接仍然可用:违规次数还没到上限,路由器照常回复
Assert.Equal("fleet_protocol_violation", (await agent.RequestRawAsync("fleet.exec")).Error!.Data!.Code);
}
[Fact]
public async Task ARepeatOffender_IsDisconnected_AndANodeCannotInitiateWhatOnlyTheBackendMay()
{
await using AgentSimulator agent = await _backend.ConnectAgentAsync();
await agent.WaitForTaskSetAsync(s => s.Tasks.Count == Defaults.TaskCount);
// 节点冒充 Backend 发起只有 Backend 才能发的方法:违规,不是「不支持」
Assert.Equal("fleet_protocol_violation", (await agent.RequestRawAsync(MoShiroMethods.TestRun)).Error!.Data!.Code);
Assert.Equal("fleet_protocol_violation", (await agent.RequestRawAsync("fleet.state.sync")).Error!.Data!.Code);
// 一个 Probe 都不是的 Agent 发 Probe 才有的通道:节点面的列块通道(17)没有声明,不会进收件箱
await agent.SendBinaryAsync(OutboxFrame.Build(MoShiroChannels.ColumnBlock, Guid.NewGuid(), [1]));
// 反复违规:超过上限(默认 20 次)之后连接被服务端关闭
for (int i = 0; i < 40 && agent.CloseReason is null; i++)
{
try
{
await agent.RequestRawAsync("fleet.exec");
}
catch (Exception ex) when (ex is TimeoutException or OperationCanceledException or System.Net.WebSockets.WebSocketException or InvalidOperationException)
{
break;
}
}
await BackendFixture.EventuallyAsync(() => Task.FromResult(agent.CloseReason is not null), because: "a node that keeps violating the protocol is disconnected");
Assert.Equal("protocol violations", agent.CloseReason);
}
[Fact]