feat: the node itself requires an owner passkey (shared by agent and probe) and reports it; the panel derives the account and passkey it is bound to [CI]

This commit is contained in:
justme committed 2026-10-08 06:15:07 +08:00
1 parent 8c37db1577
commit 2ae63653c4
23 files changed
+328 -10

No files matched your search

@@ -0,0 +1,48 @@
using Fleet.Core;
namespace Fleet.Hosting;
/// <summary>
/// 节点自己要求有属主(passkey):Agent 和 Probe 共用这一份,启动时检查。
/// <para>
/// 属主是安装命令带来、注册时固定在 <c>trust.json</c> 里的 passkey 凭证 id(必须是操作员集合里的凭证)。节点据此知道自己绑定在哪个 passkey 上,
/// 才敢把更多的接口和操作交给它(extension 会话、加密内容都只认属主的 passkey);没有属主的节点没有人能授权它的敏感能力,所以不运行。
/// 这是节点本地的强制,不依赖面板:面板被攻破也改不了节点已经固定的属主。
/// </para>
/// </summary>
public static class OwnerGuard
{
/// <summary>设为 <c>1</c> 放行没有属主的节点(只给没有信任根的老面板)。</summary>
public const string AllowEnvironmentVariable = "MOSHIRO_ALLOW_NO_OWNER";
/// <summary>没问题返回 null;否则是要告诉运维的原因。</summary>
public static string? Problem(TrustAnchor trust, bool require)
{
if (!require || Environment.GetEnvironmentVariable(AllowEnvironmentVariable) == "1")
{
return null;
}
if (!trust.Pinned)
{
return "this node has no owner: it was not enrolled with a trust root and owner passkeys; enroll it again with the command from the panel (or set " + AllowEnvironmentVariable + "=1 for a panel without a trust root)";
}
return trust.Controllers.Count == 0
? "none of this node's owner passkeys is in the operator set any more; enroll it again with a command from the panel"
: null;
}
/// <summary>读身份目录里固定的信任根,检查属主;不行就记一条错误并返回 false(调用方应当退出)。</summary>
public static bool Check(string identityDirectory, bool require, INodeLog log)
{
TrustAnchor trust = TrustAnchor.Load(identityDirectory, log);
if (Problem(trust, require) is { } problem)
{
log.Error("startup failed", ("err", problem));
return false;
}
return true;
}
}