fix: installer checks containers only; the panel reads signed manifests from the registry when it has none; unsigned-updater nodes are labeled from the panel's manifests; sign node release images; release 0.3.0 [CI]
This commit is contained in:
1 parent
4a1e48db20
commit
879d1c8593
15 files changed
+372
-18
No files matched your search
@@ -22,6 +22,7 @@ public sealed class BinaryBindingTests : IDisposable
|
||||
private readonly ECDsa _ci = ECDsa.Create(ECCurve.NamedCurves.nistP256);
|
||||
private readonly ECDsa _other = ECDsa.Create(ECCurve.NamedCurves.nistP256);
|
||||
private readonly MemoryStore _store = new();
|
||||
private readonly MemorySettings _settings = new();
|
||||
private readonly NodeRecord _agent = Node(AgentIds.Prefix + "_test1");
|
||||
|
||||
public void Dispose()
|
||||
@@ -35,7 +36,7 @@ public sealed class BinaryBindingTests : IDisposable
|
||||
private static NodeRecord Node(string id) => new(new NodeId(id), "n", NodeStatus.Active, [1], [2], [], null, null, 0, null);
|
||||
|
||||
private BinaryBindingService Service(string policy = "enforce") =>
|
||||
new(_store, new TestKey(_ci), new BackendOptions { BinaryPolicy = policy }, TimeProvider.System);
|
||||
new(_store, _settings, new TestKey(_ci), new BackendOptions { BinaryPolicy = policy }, TimeProvider.System);
|
||||
|
||||
private sealed class TestKey(ECDsa key) : IReleaseKey
|
||||
{
|
||||
@@ -208,6 +209,86 @@ public sealed class BinaryBindingTests : IDisposable
|
||||
Assert.True((await service.CheckAsync(second, Evidence(H('b')), default)).Accept);
|
||||
}
|
||||
|
||||
/// <summary>面板手里的、CI 签名的清单(登记时存下的):没有更新器的节点握手时没有自己的清单,就拿它来认官方二进制。</summary>
|
||||
private void PanelHolds(string component, long seq, string[] binaries, ECDsa? signer = null)
|
||||
{
|
||||
string manifest = $"{{\"schema\":1,\"component\":\"{component}\",\"seq\":{seq},\"binaries\":\"{string.Join(',', binaries)}\"}}";
|
||||
_settings.Values["release." + component + ".manifest"] = manifest;
|
||||
_settings.Values["release." + component + ".signature"] = Sign(signer ?? _ci, manifest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ANodeWithoutAnUpdater_IsMarkedSigned_WhenThePanelHoldsAManifestThatListsItsBinary()
|
||||
{
|
||||
// 一键容器安装的节点没有宿主机更新器,握手时带不来清单:面板用自己手里(登记时验过 CI 签名)的那份
|
||||
PanelHolds("agent", seq: 100, [H('a')]);
|
||||
BinaryBindingService service = Service();
|
||||
|
||||
Assert.True((await service.CheckAsync(_agent, Evidence(H('a')), default)).Accept);
|
||||
|
||||
NodeBinding bound = (await _store.GetAsync(_agent.Id.Value, default))!;
|
||||
Assert.True(bound.Signed);
|
||||
Assert.Equal(100, bound.Seq);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TheManifestThePanelHolds_NeverRefusesANodeAtFirstBinding_OnlyLabelsIt()
|
||||
{
|
||||
// 老一点的官方版本、自己构建的版本:不在面板手里最新的清单里,但没有证据说它不是官方的,所以不拒绝,只是不标成已签名
|
||||
PanelHolds("agent", seq: 100, [H('b')]);
|
||||
BinaryBindingService service = Service();
|
||||
|
||||
Assert.True((await service.CheckAsync(_agent, Evidence(H('a')), default)).Accept);
|
||||
|
||||
NodeBinding bound = (await _store.GetAsync(_agent.Id.Value, default))!;
|
||||
Assert.False(bound.Signed);
|
||||
Assert.Equal(0, bound.Seq);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AManifestThePanelHoldsThatIsSignedByAnotherKey_ProvesNothing()
|
||||
{
|
||||
PanelHolds("agent", seq: 100, [H('a')], signer: _other);
|
||||
BinaryBindingService service = Service();
|
||||
|
||||
Assert.True((await service.CheckAsync(_agent, Evidence(H('a')), default)).Accept);
|
||||
|
||||
Assert.False((await _store.GetAsync(_agent.Id.Value, default))!.Signed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnUpgradeOfANodeWithoutAnUpdater_IsFollowedFromTheManifestThePanelHolds()
|
||||
{
|
||||
PanelHolds("agent", seq: 100, [H('a')]);
|
||||
BinaryBindingService service = Service();
|
||||
await service.CheckAsync(_agent, Evidence(H('a')), default);
|
||||
|
||||
// 之后发布了新版,面板登记了新的清单;节点换成新二进制重连
|
||||
PanelHolds("agent", seq: 200, [H('b')]);
|
||||
Assert.True((await service.CheckAsync(_agent, Evidence(H('b')), default)).Accept);
|
||||
Assert.Equal(200, (await _store.GetAsync(_agent.Id.Value, default))!.Seq);
|
||||
|
||||
// 没人签过的二进制仍然被拒绝
|
||||
Assert.False((await service.CheckAsync(_agent, Evidence(H('c')), default)).Accept);
|
||||
}
|
||||
|
||||
private sealed class MemorySettings : ISettingsStore
|
||||
{
|
||||
public Dictionary<string, string> Values { get; } = [];
|
||||
|
||||
public ValueTask<string?> GetAsync(string key, CancellationToken ct) => new(Values.GetValueOrDefault(key));
|
||||
|
||||
public ValueTask SetAsync(string key, string value, CancellationToken ct)
|
||||
{
|
||||
Values[key] = value;
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
public ValueTask<IReadOnlyDictionary<string, string>> GetAllAsync(CancellationToken ct) => new(Values);
|
||||
|
||||
public ValueTask<long> IncrementAsync(string key, CancellationToken ct) => new(0);
|
||||
}
|
||||
|
||||
private sealed class MemoryStore : INodeBindingStore
|
||||
{
|
||||
private readonly Dictionary<string, NodeBinding> _rows = [];
|
||||
|
||||
Reference in new issue
Block a user