ci/woodpecker/push/main-amd64-build Pipeline was successful
The hash a node reports about itself cannot be trusted (a swapped binary can copy the old hash), so the runtime binding only caught files that were replaced by something that did not lie. Provenance stays where it can be verified: the signed release manifest (digest) and the signed SHA256SUMS. - Remove BinaryBindingService, the node_bindings table (schema v15 drops it), BinaryPolicy, the handshake hash/manifest fields and the admin binary column. - Drop the per-platform "binaries" list from the release manifest, the hash scripts and the updater's installed.manifest copy. - ReleaseService.PublishAsync now verifies the release signature, so the admin POST /releases route is no longer an unsigned path; tests sign with a test key. - Docs: section 16 explains why the runtime check is gone.
84 lines
2.7 KiB
C#
84 lines
2.7 KiB
C#
using System.Text.Json;
|
|
using Fleet.Core;
|
|
using Fleet.Rpc;
|
|
|
|
namespace Fleet.Server;
|
|
|
|
public sealed record EnrollmentContext(string? RemoteIp, JsonElement? ProductMeta);
|
|
|
|
/// <summary>
|
|
/// 服务器的身份密钥:操作员用 passkey 把它的公钥签进操作员集合。有它的时候,认证挑战会带上服务器的签名,节点据此确认对面是真的服务器,
|
|
/// 并在这条链路上用应用层加密。没有注册就是旧的行为(没有服务器身份)。
|
|
/// </summary>
|
|
public interface IServerIdentity
|
|
{
|
|
byte[] PublicKeySpki { get; }
|
|
|
|
/// <summary>对一段内容签名(<c>Fleet.Identity.ServerHello.Payload</c> 给出的)。</summary>
|
|
byte[] Sign(ReadOnlySpan<byte> payload);
|
|
}
|
|
|
|
/// <summary>操作员集合的链(passkey 签的,创世在前)。没有初始化信任根时是空的。节点注册时用它确认「这个面板确实是我的操作员的」。</summary>
|
|
public interface IOperatorTrustSource
|
|
{
|
|
ValueTask<IReadOnlyList<SignedStatementDto>> ChainAsync(CancellationToken ct);
|
|
}
|
|
|
|
/// <summary>产品挂钩:节点注册成功之后调用。</summary>
|
|
public interface IEnrollmentHook
|
|
{
|
|
ValueTask OnEnrolledAsync(NodeRecord node, EnrollmentContext context, CancellationToken ct);
|
|
}
|
|
|
|
public enum NodeEventKind
|
|
{
|
|
Online,
|
|
Offline,
|
|
Hello,
|
|
Heartbeat,
|
|
}
|
|
|
|
public sealed record NodeEvent(NodeEventKind Kind, NodeRecord Node, long AtMs, HelloParams? Hello = null, JsonElement? Extra = null);
|
|
|
|
public interface INodeEventSink
|
|
{
|
|
ValueTask OnEventAsync(NodeEvent nodeEvent, CancellationToken ct);
|
|
}
|
|
|
|
/// <summary>期望状态提供者:为每个节点计算期望状态的完整快照(天然幂等)。</summary>
|
|
public interface IStateProvider
|
|
{
|
|
/// <summary>状态文档类型,例如 <c>task_set</c>。</summary>
|
|
string Type { get; }
|
|
|
|
ValueTask<StateDocument?> GetAsync(NodeRecord node, CancellationToken ct);
|
|
|
|
/// <summary>节点应用之后回调,<c>rejected[]</c> 带结构化原因。</summary>
|
|
ValueTask OnAppliedAsync(NodeRecord node, long version, StateSyncResult result, CancellationToken ct);
|
|
}
|
|
|
|
public sealed record StateDocument(long Version, JsonElement Doc);
|
|
|
|
public enum InboxOutcome
|
|
{
|
|
Stored,
|
|
Duplicate,
|
|
Rejected,
|
|
}
|
|
|
|
/// <summary>收件箱:持久化之后才返回 Stored,框架随后才回复 ACK。</summary>
|
|
public interface IInbox
|
|
{
|
|
byte Channel { get; }
|
|
|
|
ValueTask<InboxOutcome> HandleAsync(NodeRecord node, Guid msgId, ReadOnlyMemory<byte> payload, CancellationToken ct);
|
|
}
|
|
|
|
/// <summary>通知 Fleet:某个节点的期望状态变了,需要重新计算并推送。</summary>
|
|
public interface IStateNotifier
|
|
{
|
|
void NotifyChanged(NodeId node);
|
|
|
|
void NotifyAllChanged();
|
|
}
|