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:
1 parent
fc2bbdcc6d
commit
9bb6841dce
11 files changed
+129
-12
No files matched your search
@@ -41,12 +41,14 @@ public sealed class AgentHarness : IAsyncDisposable
|
||||
|
||||
public byte[] TlsPublicKey => _identity.Certificate.PublicKey.ExportSubjectPublicKeyInfo();
|
||||
|
||||
public static async Task<AgentHarness> StartAsync(BackendFixture backend, Action<AgentConfig>? configure = null, string? name = null)
|
||||
/// <param name="signed">操作员签过委托的加入码(认证串、种子、创世指纹):节点按固定了创世的方式注册,名册里的 Probe 必须带操作员批准的证明。</param>
|
||||
public static async Task<AgentHarness> StartAsync(BackendFixture backend, Action<AgentConfig>? configure = null, string? name = null, (string Auth, string Seed, string Genesis)? signed = null)
|
||||
{
|
||||
string directory = Path.Combine(Path.GetTempPath(), "moshiro-agent-" + Guid.NewGuid().ToString("N"));
|
||||
string token = await backend.CreateJoinTokenAsync();
|
||||
string token = signed?.Auth ?? await backend.CreateJoinTokenAsync();
|
||||
using var http = new HttpClient();
|
||||
Result<EnrollmentResult> enrolled = await EnrollmentClient.EnrollAsync(http, backend.BaseAddress, token, name ?? backend.UniqueName("agent"), directory, AgentApp.Version, null, TimeProvider.System, CancellationToken.None);
|
||||
Result<EnrollmentResult> enrolled = await EnrollmentClient.EnrollAsync(
|
||||
http, backend.BaseAddress, token, name ?? backend.UniqueName("agent"), directory, AgentApp.Version, null, TimeProvider.System, CancellationToken.None, signed?.Seed, signed?.Genesis, "agent");
|
||||
Assert.True(enrolled.IsSuccess, enrolled.IsSuccess ? "" : enrolled.Error.ToString());
|
||||
|
||||
// 测试里的 Agent 只在回环上监听:手填 public_host(手动的优先于自动识别,Agent 上报本机真实地址也不会覆盖)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user