feat: pinned agents and probes only accept peers carrying the operator's proof; the panel ships the signed operator chain with the rosters [CI SKIP]

This commit is contained in:
justme committed 2026-10-07 23:24:33 +08:00
1 parent fc2bbdcc6d
commit 9bb6841dce
11 files changed
+129 -12

No files matched your search

@@ -217,4 +217,53 @@ public sealed class TrustTests
using HttpResponseMessage anonymous = await _backend.Anonymous.PostAsync("/api/admin/trust", BackendFixture.Json(unsigned));
Assert.Equal(HttpStatusCode.Unauthorized, anonymous.StatusCode);
}
[Fact]
public async Task APinnedAgent_OnlyAcceptsProbesTheOperatorApproved_EvenIfThePanelListsOthers()
{
Operator op = await OperatorAsync();
(string agentSeed, string agentAuth, _) = await CreateSignedCodeAsync(op, role: "agent");
await using AgentHarness agent = await AgentHarness.StartAsync(_backend, signed: (agentAuth, agentSeed, op.Genesis));
// 面板里有一个没有操作员证明的 Probe(相当于面板自己塞进名册的),再有一个操作员批准的
using EnrolledProbe plain = await _backend.CreateProbeAsync();
(string seed, string auth, _) = await CreateSignedCodeAsync(op, role: "probe");
string directory = Path.Combine(Path.GetTempPath(), "moshiro-trust-id-" + Guid.NewGuid().ToString("N"));
Result<EnrollmentResult> enrolled = await EnrollAsync(auth, seed, op.Genesis, directory: directory);
Assert.True(enrolled.IsSuccess, enrolled.IsSuccess ? "" : enrolled.Error.ToString());
using LoadedNodeIdentity approved = LoadedNodeIdentity.Load(directory);
await BackendFixture.EventuallyAsync(
() => Task.FromResult(agent.App.IsPeerKnown(enrolled.Value.NodeId, Convert.ToBase64String(approved.PeerIdentity.PublicKeySpki))),
because: "the agent should accept the probe the operator approved");
Assert.False(agent.App.IsPeerKnown(plain.ProbeId, Convert.ToBase64String(plain.Identity.PeerIdentity.PublicKeySpki)), "a probe without the operator's proof must not be accepted");
}
[Fact]
public async Task APinnedProbe_OnlyConnectsToAgentsTheOperatorApproved()
{
Operator op = await OperatorAsync();
(string probeSeed, string probeAuth, _) = await CreateSignedCodeAsync(op, role: "probe");
string probeDirectory = Path.Combine(Path.GetTempPath(), "moshiro-trust-id-" + Guid.NewGuid().ToString("N"));
Assert.True((await EnrollAsync(probeAuth, probeSeed, op.Genesis, directory: probeDirectory)).IsSuccess);
(string agentSeed, string agentAuth, _) = await CreateSignedCodeAsync(op, role: "agent");
await using AgentHarness agent = await AgentHarness.StartAsync(_backend, signed: (agentAuth, agentSeed, op.Genesis));
MemberProofDto? proof = TrustAnchor.Load(agent.DataDirectory, NullNodeLog.Instance).OwnProof;
string key = Convert.ToBase64String(agent.TlsPublicKey);
string configDirectory = Path.Combine(Path.GetTempPath(), "moshiro-probe-cfg-" + Guid.NewGuid().ToString("N"));
var directory = new MoShiro.Probe.AgentDirectory(new ConfigStore(configDirectory), configDirectory, NullNodeLog.Instance, TrustAnchor.Load(probeDirectory, NullNodeLog.Instance));
directory.Apply(new MoShiro.Protocol.ProbeAgentList
{
Version = 1,
Agents =
[
new MoShiro.Protocol.AgentEndpoint { AgentId = new NodeId("agt_approved"), Host = "127.0.0.1", TcpPort = 1, PublicKey = key, Proof = proof },
new MoShiro.Protocol.AgentEndpoint { AgentId = new NodeId("agt_planted"), Host = "127.0.0.1", TcpPort = 2, PublicKey = key }, // 面板塞进来的:没有证明
new MoShiro.Protocol.AgentEndpoint { AgentId = new NodeId("agt_swapped"), Host = "127.0.0.1", TcpPort = 3, PublicKey = Convert.ToBase64String(RandomNumberGenerator.GetBytes(91)), Proof = proof }, // 拿着别人的证明、换了自己的公钥
],
});
Assert.Equal(["agt_approved"], directory.Agents.Select(a => a.AgentId));
}
}