feat!: probes generate their own keys and enroll with a join token; node and peer keys are separate [CI]
ci/woodpecker/push/main-amd64-build Pipeline was successful

This commit is contained in:
justme committed 2026-10-07 16:57:34 +08:00
1 parent 1ff847e45a
commit a9f562441f
51 files changed
+505 -749

No files matched your search

@@ -53,13 +53,26 @@ public class BackendFixture : IAsyncLifetime
public string Line(string agentId) => $"{agentId}~{DefaultProbeId}";
/// <summary>在后端创建一个新的 Probe 身份,返回响应里的 JSON(含私钥,只此一次)。</summary>
public async Task<JsonNode> CreateProbeAsync(string? name = null)
/// <summary>
/// 注册一个新的 Probe:和真实部署一样,先在管理端创建 <c>probe</c> 角色的加入令牌,再由节点自己生成密钥、用 <c>fleet-enroll</c> 同样的流程注册。
/// 返回的身份目录里有两把密钥:连面板的 <c>Identity</c> 和对等面的 <c>PeerIdentity</c>。调用方负责释放。
/// </summary>
public async Task<EnrolledProbe> CreateProbeAsync(string? name = null)
{
using HttpResponseMessage response = await Admin.PostAsync("/api/admin/probes", Json(new JsonObject { ["name"] = name ?? UniqueName("probe") }));
string body = await response.Content.ReadAsStringAsync();
Assert.True(response.IsSuccessStatusCode, body);
return JsonNode.Parse(body)!;
string token = await CreateJoinTokenAsync(role: "probe");
string directory = Path.Combine(Path.GetTempPath(), "moshiro-probe-id-" + Guid.NewGuid().ToString("N"));
using var http = new HttpClient();
Fleet.Core.Result<Fleet.Enrollment.Client.EnrollmentResult> enrolled = await Fleet.Enrollment.Client.EnrollmentClient.EnrollAsync(
http, BaseAddress, token, name ?? UniqueName("probe"), directory, "0.0.0-test", null, TimeProvider.System, default);
Assert.True(enrolled.IsSuccess, enrolled.IsSuccess ? "" : enrolled.Error.ToString());
return new EnrolledProbe(enrolled.Value.NodeId, directory, Fleet.Hosting.LoadedNodeIdentity.Load(directory));
}
/// <summary>注册一个 Probe,只要它的 id(密钥用完即释放)。</summary>
public async Task<string> CreateProbeIdAsync(string? name = null)
{
using EnrolledProbe probe = await CreateProbeAsync(name);
return probe.ProbeId;
}
public async ValueTask InitializeAsync()
@@ -68,7 +81,10 @@ public class BackendFixture : IAsyncLifetime
await StartAsync();
_adminToken = await CreateAdminTokenAsync();
CreateClients();
DefaultProbeId = (await CreateProbeAsync("default"))["probe_id"]!.GetValue<string>();
using (EnrolledProbe probe = await CreateProbeAsync("default"))
{
DefaultProbeId = probe.ProbeId;
}
}
public async ValueTask DisposeAsync()
@@ -123,9 +139,9 @@ public class BackendFixture : IAsyncLifetime
public string UniqueName(string prefix) => $"{prefix}-{Interlocked.Increment(ref _counter)}";
public async Task<string> CreateJoinTokenAsync(bool autoApprove = true, int maxUses = 1, int expiresInHours = 1)
public async Task<string> CreateJoinTokenAsync(bool autoApprove = true, int maxUses = 1, int expiresInHours = 1, string role = "agent")
{
using HttpResponseMessage response = await Admin.PostAsync("/api/admin/join-tokens", Json(new JsonObject { ["auto_approve"] = autoApprove, ["max_uses"] = maxUses, ["expires_in_hours"] = expiresInHours }));
using HttpResponseMessage response = await Admin.PostAsync("/api/admin/join-tokens", Json(new JsonObject { ["role"] = role, ["auto_approve"] = autoApprove, ["max_uses"] = maxUses, ["expires_in_hours"] = expiresInHours }));
response.EnsureSuccessStatusCode();
return JsonNode.Parse(await response.Content.ReadAsStringAsync())!["plaintext"]!.GetValue<string>();
}
@@ -235,3 +251,9 @@ public sealed class StrictLoginFixture : BackendFixture
{
protected override int LoginAttemptsPerMinute => 10;
}
/// <summary>注册好的 Probe:身份目录(节点自己生成的两把密钥)和加载出来的身份。</summary>
public sealed record EnrolledProbe(string ProbeId, string Directory, Fleet.Hosting.LoadedNodeIdentity Identity) : IDisposable
{
public void Dispose() => Identity.Dispose();
}