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
ci/woodpecker/push/main-amd64-build Pipeline was successful
This commit is contained in:
1 parent
1ff847e45a
commit
a9f562441f
51 files changed
+505
-749
No files matched your search
@@ -23,20 +23,20 @@ public sealed class ProbeApp : IDisposable
|
||||
private readonly INodeLog _log;
|
||||
private readonly FleetLink _link;
|
||||
|
||||
public ProbeApp(ProbeFile file, string configDirectory, TimeProvider time, INodeLog log, RestartController? restart = null)
|
||||
/// <param name="identity">注册时由节点自己生成:<c>Identity</c> 用来连面板,<c>PeerIdentity</c> 是另一把独立的密钥,只用来连 Agent。</param>
|
||||
public ProbeApp(ProbeFile file, LoadedNodeIdentity identity, string configDirectory, TimeProvider time, INodeLog log, RestartController? restart = null)
|
||||
{
|
||||
_log = log;
|
||||
var directory = new AgentDirectory(new ConfigStore(configDirectory), configDirectory, log);
|
||||
NodeIdentity identity = NodeIdentity.FromPkcs8(Convert.FromBase64String(file.PrivateKey));
|
||||
_env = new ConnectionEnv(identity, file.ProbeId, file.Limits, directory, new MonoClock(time), time, log);
|
||||
_env = new ConnectionEnv(identity.PeerIdentity, identity.NodeId.Value, file.Limits, directory, new MonoClock(time), time, log);
|
||||
directory.Changed += () => _changed.Release();
|
||||
|
||||
var update = new UpdateBridge(configDirectory, new ConfigStore(configDirectory).Write, log);
|
||||
var options = new FleetLinkOptions
|
||||
{
|
||||
Server = new Uri(file.Server),
|
||||
Identity = identity,
|
||||
NodeId = new NodeId(file.ProbeId),
|
||||
Server = new Uri(file.Fleet.Server),
|
||||
Identity = identity.Identity,
|
||||
NodeId = identity.NodeId,
|
||||
NodeVersion = Version,
|
||||
Capabilities = [MoShiroCapabilities.Base],
|
||||
HeartbeatExtra = () => JsonSerializer.SerializeToElement(new ProbeHeartbeatExtra { Update = update.ReadStatus(), Ipv6 = Ipv6Diagnostics.Sample(null, Environment.GetEnvironmentVariable("MOSHIRO_NETWORK_SCOPE"), Environment.GetEnvironmentVariable("HOST_PROC")) }, MoShiroJsonContext.Default.ProbeHeartbeatExtra),
|
||||
|
||||
@@ -4,21 +4,13 @@ using MoShiro.Protocol;
|
||||
namespace MoShiro.Probe;
|
||||
|
||||
/// <summary>
|
||||
/// <c>probe.json</c>:面板 <c>POST /api/admin/probe/identity</c> 生成的文件(只能下载一次)。
|
||||
/// <c>probe.json</c>:本地配置,和 Agent 一样由 <c>fleet-enroll</c> 注册之后写出身份目录(节点自己生成的两把密钥),这里只放面板地址和本地硬上限。
|
||||
/// <c>limits</c> 是本地硬上限,面板无法修改;在路由器上建议调低,避免 Probe 自己的 CPU 成为瓶颈、反过来影响测量。
|
||||
/// </summary>
|
||||
public sealed class ProbeFile
|
||||
{
|
||||
[JsonPropertyName("probe_id")]
|
||||
public string ProbeId { get; set; } = "";
|
||||
|
||||
/// <summary>PKCS#8,base64。</summary>
|
||||
[JsonPropertyName("private_key")]
|
||||
public string PrivateKey { get; set; } = "";
|
||||
|
||||
/// <summary>面板的地址(http 或 https)。Probe 直接连它:身份、Agent 列表、更新都从这里来。</summary>
|
||||
[JsonPropertyName("server")]
|
||||
public string Server { get; set; } = "";
|
||||
[JsonPropertyName("fleet")]
|
||||
public ProbeFleetSection Fleet { get; set; } = new();
|
||||
|
||||
/// <summary>每天定时重启一次,例如 <c>04:00+08:00</c>(不写偏移按 UTC);不写就不重启。环境变量 <c>MOSHIRO_RESTART_AT</c> 优先;这里是本地配置,从面板重新下载 probe.json 会丢掉它,所以容器里推荐用环境变量。</summary>
|
||||
[JsonPropertyName("restart_at")]
|
||||
@@ -28,6 +20,17 @@ public sealed class ProbeFile
|
||||
public ProbeLimits Limits { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class ProbeFleetSection
|
||||
{
|
||||
/// <summary>面板的地址(http 或 https)。Probe 直接连它:身份、Agent 列表、更新都从这里来。</summary>
|
||||
[JsonPropertyName("server")]
|
||||
public string Server { get; set; } = "";
|
||||
|
||||
/// <summary><c>fleet-enroll</c> 写出的身份目录:<c>node.json</c>、<c>node.key</c>(连面板的密钥)、<c>tls.pfx</c>(对等面的密钥)。</summary>
|
||||
[JsonPropertyName("identity_dir")]
|
||||
public string IdentityDir { get; set; } = "/etc/moshiro/probe-id";
|
||||
}
|
||||
|
||||
public sealed class KnownAgent
|
||||
{
|
||||
[JsonPropertyName("ip_version")]
|
||||
|
||||
@@ -21,17 +21,25 @@ for (int i = 0; i < args.Length - 1; i++)
|
||||
}
|
||||
|
||||
ProbeFile file;
|
||||
LoadedNodeIdentity identity;
|
||||
RestartController restart;
|
||||
try
|
||||
{
|
||||
file = NodeConfig.Load(configPath, ProbeConfigContext.Default.ProbeFile);
|
||||
restart = new RestartController(RestartSchedule.Resolve(file.RestartAt));
|
||||
if (file.ProbeId.Length == 0 || file.PrivateKey.Length == 0 || !Uri.TryCreate(file.Server, UriKind.Absolute, out _))
|
||||
if (!Uri.TryCreate(file.Fleet.Server, UriKind.Absolute, out _))
|
||||
{
|
||||
throw new InvalidOperationException("probe.json needs probe_id, private_key and server (download it from the panel: Probes → Create)");
|
||||
throw new InvalidOperationException("probe.json needs fleet.server (the panel address)");
|
||||
}
|
||||
|
||||
identity = LoadedNodeIdentity.Load(file.Fleet.IdentityDir);
|
||||
if (!identity.NodeId.Value.StartsWith(MoShiro.Protocol.ProbeId.Prefix, StringComparison.Ordinal))
|
||||
{
|
||||
identity.Dispose();
|
||||
throw new InvalidOperationException($"the identity in {file.Fleet.IdentityDir} is not a probe's: enroll with a probe join token");
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or InvalidDataException or InvalidOperationException or System.Text.Json.JsonException)
|
||||
catch (Exception ex) when (ex is IOException or InvalidDataException or InvalidOperationException or System.Text.Json.JsonException or System.Security.Cryptography.CryptographicException)
|
||||
{
|
||||
log.Error("startup failed", ("err", ex.Message));
|
||||
return 1;
|
||||
@@ -39,15 +47,10 @@ catch (Exception ex) when (ex is IOException or InvalidDataException or InvalidO
|
||||
|
||||
using var restartScope = restart;
|
||||
string directory = Path.GetDirectoryName(Path.GetFullPath(configPath))!;
|
||||
try
|
||||
using (identity)
|
||||
{
|
||||
using var app = new ProbeApp(file, directory, time, log, restart);
|
||||
using var app = new ProbeApp(file, identity, directory, time, log, restart);
|
||||
await ServiceShell.RunAsync(app.RunAsync, log, restart, time);
|
||||
}
|
||||
catch (Exception ex) when (ex is FormatException or System.Security.Cryptography.CryptographicException)
|
||||
{
|
||||
log.Error("startup failed", ("err", "private_key is not a valid PKCS#8 key: " + ex.Message));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
Reference in new issue
Block a user