mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-11 13:18:53 +00:00
tka: truncate long rotation signature chains
When a rotation signature chain reaches a certain size, remove the oldest rotation signature from the chain before wrapping it in a new rotation signature. Since all previous rotation signatures are signed by the same wrapping pubkey (node's own tailnet lock key), the node can re-construct the chain, re-signing previous rotation signatures. This will satisfy the existing certificate validation logic. Updates #13185 Signed-off-by: Anton Tolchanov <anton@tailscale.com>
This commit is contained in:

committed by
Anton Tolchanov

parent
bcc47d91ca
commit
fd6686d81a
@@ -175,23 +175,24 @@ func (r *rotationTracker) addRotationDetails(np key.NodePublic, d *tka.RotationD
|
||||
// obsoleteKeys returns the set of node keys that are obsolete due to key rotation.
|
||||
func (r *rotationTracker) obsoleteKeys() set.Set[key.NodePublic] {
|
||||
for _, v := range r.byWrappingKey {
|
||||
// Do not consider signatures for keys that have been marked as obsolete
|
||||
// by another signature.
|
||||
v = slices.DeleteFunc(v, func(rd sigRotationDetails) bool {
|
||||
return r.obsolete.Contains(rd.np)
|
||||
})
|
||||
if len(v) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// If there are multiple rotation signatures with the same wrapping
|
||||
// pubkey, we need to decide which one is the "latest", and keep it.
|
||||
// The signature with the largest number of previous keys is likely to
|
||||
// be the latest, unless it has been marked as obsolete (rotated out) by
|
||||
// another signature (which might happen in the future if we start
|
||||
// compacting long rotated signature chains).
|
||||
// be the latest.
|
||||
slices.SortStableFunc(v, func(a, b sigRotationDetails) int {
|
||||
// Group all obsolete keys after non-obsolete keys.
|
||||
if ao, bo := r.obsolete.Contains(a.np), r.obsolete.Contains(b.np); ao != bo {
|
||||
if ao {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
// Sort by decreasing number of previous keys.
|
||||
return b.numPrevKeys - a.numPrevKeys
|
||||
})
|
||||
|
||||
// If there are several signatures with the same number of previous
|
||||
// keys, we cannot determine which one is the latest, so all of them are
|
||||
// rejected for safety.
|
||||
|
@@ -667,6 +667,31 @@ func TestTKAFilterNetmap(t *testing.T) {
|
||||
if diff := cmp.Diff(want, nm.Peers, nodePubComparer); diff != "" {
|
||||
t.Errorf("filtered netmap differs (-want, +got):\n%s", diff)
|
||||
}
|
||||
|
||||
// Confirm that repeated rotation works correctly.
|
||||
for range 100 {
|
||||
n5Rotated, n5RotatedSig = resign(n5nl, n5RotatedSig)
|
||||
}
|
||||
|
||||
n51, n51Sig := resign(n5nl, n5RotatedSig)
|
||||
|
||||
nm = &netmap.NetworkMap{
|
||||
Peers: nodeViews([]*tailcfg.Node{
|
||||
{ID: 1, Key: n1.Public(), KeySignature: n1GoodSig.Serialize()},
|
||||
{ID: 5, Key: n5Rotated.Public(), KeySignature: n5RotatedSig}, // rotated
|
||||
{ID: 51, Key: n51.Public(), KeySignature: n51Sig},
|
||||
}),
|
||||
}
|
||||
|
||||
b.tkaFilterNetmapLocked(nm)
|
||||
|
||||
want = nodeViews([]*tailcfg.Node{
|
||||
{ID: 1, Key: n1.Public(), KeySignature: n1GoodSig.Serialize()},
|
||||
{ID: 51, Key: n51.Public(), KeySignature: n51Sig},
|
||||
})
|
||||
if diff := cmp.Diff(want, nm.Peers, nodePubComparer); diff != "" {
|
||||
t.Errorf("filtered netmap differs (-want, +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTKADisable(t *testing.T) {
|
||||
|
Reference in New Issue
Block a user