Files
MoShiro/backend/fleet/src/Fleet.Rpc/FleetMethods.cs
T
justme 904ea2b0ae
ci/woodpecker/push/main-amd64-build Pipeline was successful
refactor: remove the runtime binary binding; every release registration path verifies the CI signature [CI]
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.
2026-10-08 18:30:05 +08:00

178 lines
6.0 KiB
C#

using System.Text.Json;
using System.Text.Json.Serialization;
namespace Fleet.Rpc;
/// <summary>框架保留的方法名。<c>fleet.*</c> 只属于框架,产品使用自己的前缀。</summary>
public static class FleetMethods
{
public const string Challenge = "fleet.challenge";
public const string Auth = "fleet.auth";
public const string Hello = "fleet.hello";
public const string Heartbeat = "fleet.heartbeat";
public const string StateSync = "fleet.state.sync";
public const string OutboxAck = "fleet.outbox.ack";
public const string Revoked = "fleet.revoked";
}
/// <summary>
/// 框架自己的方法声明,两个平面各一份。认证和握手阶段由连接代码直接处理的(<c>Inline</c>)不经过路由器。
/// 同一个方法名在两个平面里可以有不同的发起方:<c>fleet.state.sync</c> 在节点面是服务端发给节点,在对等面是 Agent 发给 Probe。
/// </summary>
public static class FleetMethodTable
{
public static readonly MethodTable Table = new(
[
// 节点面
MethodSpec.Notification(FleetMethods.Challenge, RpcSide.Server).AsInline(),
MethodSpec.Request(FleetMethods.Auth, RpcSide.Node).AsInline(),
MethodSpec.Request(FleetMethods.Hello, RpcSide.Node).AsInline(),
MethodSpec.Notification(FleetMethods.Heartbeat, RpcSide.Node).AsInline(),
MethodSpec.Request(FleetMethods.StateSync, RpcSide.Server),
MethodSpec.Notification(FleetMethods.OutboxAck, RpcSide.Server),
MethodSpec.Notification(FleetMethods.Revoked, RpcSide.Server),
// 对等面
MethodSpec.Notification(PeerMethods.Challenge, RpcSide.Listener).AsInline(),
MethodSpec.Request(PeerMethods.Auth, RpcSide.Dialer).AsInline(),
MethodSpec.Request(FleetMethods.StateSync, RpcSide.Listener),
MethodSpec.Notification(FleetMethods.OutboxAck, RpcSide.Listener),
]);
}
/// <summary>二进制通道:0–15 保留给框架,产品从 16 开始。</summary>
public static class FleetChannels
{
public const byte FirstProductChannel = 16;
}
public sealed class ChallengeParams
{
[JsonPropertyName("nonce")]
public string Nonce { get; set; } = "";
[JsonPropertyName("expires_at_ms")]
public long ExpiresAtMs { get; set; }
/// <summary>服务器这次连接的临时 ECDH 公钥(SPKI,base64);服务器有身份密钥时才有。</summary>
[JsonPropertyName("server_eph")]
public string? ServerEphemeral { get; set; }
/// <summary>服务器密钥对「随机数加临时公钥」的签名(base64):节点据此确认对面是操作员授权的服务器。</summary>
[JsonPropertyName("server_sig")]
public string? ServerSignature { get; set; }
}
public sealed class AuthParams
{
[JsonPropertyName("node_id")]
public string NodeId { get; set; } = "";
/// <summary>对 <c>fleet|v1|auth|{node_id}|{nonce}</c> 的签名(base64)。</summary>
[JsonPropertyName("signature")]
public string Signature { get; set; } = "";
/// <summary>节点的临时 ECDH 公钥(SPKI,base64):有它就是安全链路,签名覆盖双方的临时公钥,认证结果起所有帧都是加密的。</summary>
[JsonPropertyName("node_eph")]
public string? NodeEphemeral { get; set; }
}
public sealed class AuthResult
{
/// <summary>pending | active</summary>
[JsonPropertyName("status")]
public string Status { get; set; } = "";
}
public sealed class HelloParams
{
[JsonPropertyName("node_version")]
public string NodeVersion { get; set; } = "";
[JsonPropertyName("capabilities")]
public List<string> Capabilities { get; set; } = [];
[JsonPropertyName("now_utc_ms")]
public long NowUtcMs { get; set; }
/// <summary>产品自定义的元数据(例如 MoShiro 的 tls_spki、probe_ports、local_limits 摘要)。</summary>
[JsonPropertyName("meta")]
public JsonElement? Meta { get; set; }
}
public sealed class HelloResult
{
[JsonPropertyName("server_time_ms")]
public long ServerTimeMs { get; set; }
/// <summary>节点时钟 − 服务端时钟(毫秒,未扣除网络往返)。</summary>
[JsonPropertyName("clock_offset_ms")]
public long ClockOffsetMs { get; set; }
}
public sealed class HeartbeatParams
{
[JsonPropertyName("extra")]
public JsonElement? Extra { get; set; }
}
public sealed class StateSyncParams
{
[JsonPropertyName("type")]
public string Type { get; set; } = "";
[JsonPropertyName("version")]
public long Version { get; set; }
[JsonPropertyName("doc")]
public JsonElement Doc { get; set; }
}
public sealed class StateSyncResult
{
[JsonPropertyName("applied_version")]
public long AppliedVersion { get; set; }
[JsonPropertyName("rejected")]
public List<RejectedItem> Rejected { get; set; } = [];
}
public sealed class RejectedItem
{
[JsonPropertyName("id")]
public string Id { get; set; } = "";
/// <summary>unsupported | budget_exceeded | policy_denied | …</summary>
[JsonPropertyName("reason")]
public string Reason { get; set; } = "";
}
public sealed class OutboxAckParams
{
[JsonPropertyName("ids")]
public List<string> Ids { get; set; } = [];
[JsonPropertyName("duplicates")]
public List<string> Duplicates { get; set; } = [];
}
public sealed class RevokedParams
{
[JsonPropertyName("reason")]
public string Reason { get; set; } = "";
}
[JsonSourceGenerationOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(RpcMessage))]
[JsonSerializable(typeof(ChallengeParams))]
[JsonSerializable(typeof(AuthParams))]
[JsonSerializable(typeof(AuthResult))]
[JsonSerializable(typeof(HelloParams))]
[JsonSerializable(typeof(HelloResult))]
[JsonSerializable(typeof(HeartbeatParams))]
[JsonSerializable(typeof(StateSyncParams))]
[JsonSerializable(typeof(StateSyncResult))]
[JsonSerializable(typeof(OutboxAckParams))]
[JsonSerializable(typeof(RevokedParams))]
public sealed partial class FleetRpcContext : JsonSerializerContext;