tka,ipn: reduce boilerplate in Tailnet Lock tests

The `CreateStateForTest` helper reduces boilerplate in cases where the test
only cares about the trusted keys and not the disablement values (and makes
it more obvious where the disablement values are meaningful).

The `setupChonkStorage` helper reduces the boilerplate when creating on-disk
TKA storage in tests.

The `fakeLocalBackend` helper reduces the boilerplate when setting up a
`LocalBackend` instance in the IPN tests.

Updates #cleanup

Change-Id: Iacfba1be5f7fab208eec11e4369d63c7d7519da5
Signed-off-by: Alex Chan <alexc@tailscale.com>
This commit is contained in:
Alex Chan
2026-04-30 12:32:22 +01:00
committed by Alex Chan
parent 495d3acc7b
commit d6ffc0d986
8 changed files with 126 additions and 273 deletions
+2 -5
View File
@@ -22,15 +22,12 @@ func TestHandleC2NDebugTKA(t *testing.T) {
return nil, nil return nil, nil
} }
disablementSecret := bytes.Repeat([]byte{0xa5}, 32)
signerKey := key.NewNLPrivate() signerKey := key.NewNLPrivate()
key1 := tka.Key{Kind: tka.Key25519, Public: signerKey.Public().Verifier(), Votes: 2} key1 := tka.Key{Kind: tka.Key25519, Public: signerKey.Public().Verifier(), Votes: 2}
state := tka.CreateStateForTest(key1)
chonk := tka.ChonkMem() chonk := tka.ChonkMem()
authority, _, err := tka.Create(chonk, tka.State{ authority, _, err := tka.Create(chonk, state, signerKey)
Keys: []tka.Key{key1},
DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
}, signerKey)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
} }
+68 -199
View File
@@ -84,7 +84,29 @@ func fakeNoiseServer(t *testing.T, handler http.HandlerFunc) (*httptest.Server,
return ts, client return ts, client
} }
// newLocalBackendForTKA creates a new instance of [LocalBackend] for testing
// Tailnet Lock, in particular setting the tka field.
func newLocalBackendForTKA(t *testing.T, varRoot string, client *http.Client, pm *profileManager, authority *tka.Authority, chonk tka.CompactableChonk) LocalBackend {
t.Helper()
cc := fakeControlClient(t, client)
return LocalBackend{
varRoot: varRoot,
cc: cc,
ccAuto: cc,
logf: t.Logf,
health: health.NewTracker(eventbustest.NewBus(t)),
tka: &tkaState{
profile: pm.CurrentProfile().ID(),
authority: authority,
storage: chonk,
},
pm: pm,
store: pm.Store(),
}
}
func setupProfileManager(t *testing.T, nodePriv key.NodePrivate, nlPriv key.NLPrivate) *profileManager { func setupProfileManager(t *testing.T, nodePriv key.NodePrivate, nlPriv key.NLPrivate) *profileManager {
t.Helper()
pm := must.Get(newProfileManager(new(mem.Store), t.Logf, health.NewTracker(eventbustest.NewBus(t)))) pm := must.Get(newProfileManager(new(mem.Store), t.Logf, health.NewTracker(eventbustest.NewBus(t))))
must.Do(pm.SetPrefs((&ipn.Prefs{ must.Do(pm.SetPrefs((&ipn.Prefs{
Persist: &persist.Persist{ Persist: &persist.Persist{
@@ -95,6 +117,18 @@ func setupProfileManager(t *testing.T, nodePriv key.NodePrivate, nlPriv key.NLPr
return pm return pm
} }
// setupChonkStorage creates a new [tka.FS] in a temporary folder.
func setupChonkStorage(t *testing.T, pm *profileManager) (varRoot string, chonk *tka.FS) {
varRoot = t.TempDir()
tkaPath := filepath.Join(varRoot, "tka-profile", string(pm.CurrentProfile().ID()))
os.Mkdir(tkaPath, 0755)
chonk, err := tka.ChonkDir(tkaPath)
if err != nil {
t.Fatal(err)
}
return varRoot, chonk
}
func TestTKAEnablementFlow(t *testing.T) { func TestTKAEnablementFlow(t *testing.T) {
nodePriv := key.NewNode() nodePriv := key.NewNode()
@@ -102,11 +136,9 @@ func TestTKAEnablementFlow(t *testing.T) {
// our mock server can communicate. // our mock server can communicate.
nlPriv := key.NewNLPrivate() nlPriv := key.NewNLPrivate()
key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2} key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2}
state := tka.CreateStateForTest(key)
chonk := tka.ChonkMem() chonk := tka.ChonkMem()
a1, genesisAUM, err := tka.Create(chonk, tka.State{ a1, genesisAUM, err := tka.Create(chonk, state, nlPriv)
Keys: []tka.Key{key},
DisablementValues: [][]byte{bytes.Repeat([]byte{0xa5}, 32)},
}, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
} }
@@ -188,13 +220,7 @@ func TestTKADisablementFlow(t *testing.T) {
pm := setupProfileManager(t, nodePriv, nlPriv) pm := setupProfileManager(t, nodePriv, nlPriv)
temp := t.TempDir() varRoot, chonk := setupChonkStorage(t, pm)
tkaPath := filepath.Join(temp, "tka-profile", string(pm.CurrentProfile().ID()))
os.Mkdir(tkaPath, 0755)
chonk, err := tka.ChonkDir(tkaPath)
if err != nil {
t.Fatal(err)
}
authority, _, err := tka.Create(chonk, tka.State{ authority, _, err := tka.Create(chonk, tka.State{
Keys: []tka.Key{key}, Keys: []tka.Key{key},
DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)}, DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
@@ -239,20 +265,7 @@ func TestTKADisablementFlow(t *testing.T) {
})) }))
defer ts.Close() defer ts.Close()
cc := fakeControlClient(t, client) b := newLocalBackendForTKA(t, varRoot, client, pm, authority, chonk)
b := LocalBackend{
varRoot: temp,
cc: cc,
ccAuto: cc,
logf: t.Logf,
health: health.NewTracker(eventbustest.NewBus(t)),
tka: &tkaState{
authority: authority,
storage: chonk,
},
pm: pm,
store: pm.Store(),
}
// Test that the wrong disablement secret does not shut down the authority. // Test that the wrong disablement secret does not shut down the authority.
returnWrongSecret = true returnWrongSecret = true
@@ -289,8 +302,6 @@ func TestTKASync(t *testing.T) {
someKeyPriv := key.NewNLPrivate() someKeyPriv := key.NewNLPrivate()
someKey := tka.Key{Kind: tka.Key25519, Public: someKeyPriv.Public().Verifier(), Votes: 1} someKey := tka.Key{Kind: tka.Key25519, Public: someKeyPriv.Public().Verifier(), Votes: 1}
disablementSecret := bytes.Repeat([]byte{0xa5}, 32)
type tkaSyncScenario struct { type tkaSyncScenario struct {
name string name string
// controlAUMs is called (if non-nil) to get any AUMs which the tka state // controlAUMs is called (if non-nil) to get any AUMs which the tka state
@@ -369,10 +380,8 @@ func TestTKASync(t *testing.T) {
// Setup the tka authority on the control plane. // Setup the tka authority on the control plane.
key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2} key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2}
controlStorage := tka.ChonkMem() controlStorage := tka.ChonkMem()
controlAuthority, bootstrap, err := tka.Create(controlStorage, tka.State{ controlState := tka.CreateStateForTest(key, someKey)
Keys: []tka.Key{key, someKey}, controlAuthority, bootstrap, err := tka.Create(controlStorage, controlState, nlPriv)
DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
}, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
} }
@@ -382,14 +391,8 @@ func TestTKASync(t *testing.T) {
} }
} }
temp := t.TempDir()
tkaPath := filepath.Join(temp, "tka-profile", string(pm.CurrentProfile().ID()))
os.Mkdir(tkaPath, 0755)
// Setup the TKA authority on the node. // Setup the TKA authority on the node.
nodeStorage, err := tka.ChonkDir(tkaPath) varRoot, nodeStorage := setupChonkStorage(t, pm)
if err != nil {
t.Fatal(err)
}
nodeAuthority, err := tka.Bootstrap(nodeStorage, bootstrap) nodeAuthority, err := tka.Bootstrap(nodeStorage, bootstrap)
if err != nil { if err != nil {
t.Fatalf("tka.Bootstrap() failed: %v", err) t.Fatalf("tka.Bootstrap() failed: %v", err)
@@ -424,20 +427,7 @@ func TestTKASync(t *testing.T) {
defer ts.Close() defer ts.Close()
// Setup the client. // Setup the client.
cc := fakeControlClient(t, client) b := newLocalBackendForTKA(t, varRoot, client, pm, nodeAuthority, nodeStorage)
b := LocalBackend{
varRoot: temp,
cc: cc,
ccAuto: cc,
logf: t.Logf,
health: health.NewTracker(eventbustest.NewBus(t)),
pm: pm,
store: pm.Store(),
tka: &tkaState{
authority: nodeAuthority,
storage: nodeStorage,
},
}
// Finally, let's trigger a sync. // Finally, let's trigger a sync.
err = b.tkaSyncIfNeeded(&netmap.NetworkMap{ err = b.tkaSyncIfNeeded(&netmap.NetworkMap{
@@ -463,8 +453,6 @@ func TestTKASyncTriggersCompact(t *testing.T) {
someKeyPriv := key.NewNLPrivate() someKeyPriv := key.NewNLPrivate()
someKey := tka.Key{Kind: tka.Key25519, Public: someKeyPriv.Public().Verifier(), Votes: 1} someKey := tka.Key{Kind: tka.Key25519, Public: someKeyPriv.Public().Verifier(), Votes: 1}
disablementSecret := bytes.Repeat([]byte{0xa5}, 32)
nodePriv := key.NewNode() nodePriv := key.NewNode()
nlPriv := key.NewNLPrivate() nlPriv := key.NewNLPrivate()
pm := setupProfileManager(t, nodePriv, nlPriv) pm := setupProfileManager(t, nodePriv, nlPriv)
@@ -480,10 +468,8 @@ func TestTKASyncTriggersCompact(t *testing.T) {
key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2} key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2}
controlStorage := tka.ChonkMem() controlStorage := tka.ChonkMem()
controlStorage.SetClock(clock) controlStorage.SetClock(clock)
controlAuthority, bootstrap, err := tka.Create(controlStorage, tka.State{ controlState := tka.CreateStateForTest(key, someKey)
Keys: []tka.Key{key, someKey}, controlAuthority, bootstrap, err := tka.Create(controlStorage, controlState, nlPriv)
DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
}, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
} }
@@ -542,19 +528,8 @@ func TestTKASyncTriggersCompact(t *testing.T) {
defer ts.Close() defer ts.Close()
// Setup the client. // Setup the client.
cc := fakeControlClient(t, client) varRoot := ""
b := LocalBackend{ b := newLocalBackendForTKA(t, varRoot, client, pm, nodeAuthority, nodeStorage)
cc: cc,
ccAuto: cc,
logf: t.Logf,
health: health.NewTracker(eventbustest.NewBus(t)),
pm: pm,
store: pm.Store(),
tka: &tkaState{
authority: nodeAuthority,
storage: nodeStorage,
},
}
// Trigger a sync. // Trigger a sync.
err = b.tkaSyncIfNeeded(&netmap.NetworkMap{ err = b.tkaSyncIfNeeded(&netmap.NetworkMap{
@@ -610,11 +585,9 @@ func TestTKASyncTriggersCompact(t *testing.T) {
func TestTKAFilterNetmap(t *testing.T) { func TestTKAFilterNetmap(t *testing.T) {
nlPriv := key.NewNLPrivate() nlPriv := key.NewNLPrivate()
nlKey := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2} nlKey := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2}
state := tka.CreateStateForTest(nlKey)
storage := tka.ChonkMem() storage := tka.ChonkMem()
authority, _, err := tka.Create(storage, tka.State{ authority, _, err := tka.Create(storage, state, nlPriv)
Keys: []tka.Key{nlKey},
DisablementValues: [][]byte{bytes.Repeat([]byte{0xa5}, 32)},
}, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
} }
@@ -764,17 +737,11 @@ func TestTKADisable(t *testing.T) {
// Make a fake TKA authority, to seed local state. // Make a fake TKA authority, to seed local state.
disablementSecret := bytes.Repeat([]byte{0xa5}, 32) disablementSecret := bytes.Repeat([]byte{0xa5}, 32)
nlPriv := key.NewNLPrivate() nlPriv := key.NewNLPrivate()
key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2}
pm := setupProfileManager(t, nodePriv, nlPriv) pm := setupProfileManager(t, nodePriv, nlPriv)
temp := t.TempDir() temp, chonk := setupChonkStorage(t, pm)
tkaPath := filepath.Join(temp, "tka-profile", string(pm.CurrentProfile().ID()))
os.Mkdir(tkaPath, 0755)
key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2}
chonk, err := tka.ChonkDir(tkaPath)
if err != nil {
t.Fatal(err)
}
authority, _, err := tka.Create(chonk, tka.State{ authority, _, err := tka.Create(chonk, tka.State{
Keys: []tka.Key{key}, Keys: []tka.Key{key},
DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)}, DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
@@ -821,21 +788,7 @@ func TestTKADisable(t *testing.T) {
})) }))
defer ts.Close() defer ts.Close()
cc := fakeControlClient(t, client) b := newLocalBackendForTKA(t, temp, client, pm, authority, chonk)
b := LocalBackend{
varRoot: temp,
cc: cc,
ccAuto: cc,
logf: t.Logf,
health: health.NewTracker(eventbustest.NewBus(t)),
tka: &tkaState{
profile: pm.CurrentProfile().ID(),
authority: authority,
storage: chonk,
},
pm: pm,
store: pm.Store(),
}
// Test that we get an error for an incorrect disablement secret. // Test that we get an error for an incorrect disablement secret.
if err := b.NetworkLockDisable([]byte{1, 2, 3, 4}); err == nil || err.Error() != "incorrect disablement secret" { if err := b.NetworkLockDisable([]byte{1, 2, 3, 4}); err == nil || err.Error() != "incorrect disablement secret" {
@@ -854,20 +807,11 @@ func TestTKASign(t *testing.T) {
pm := setupProfileManager(t, nodePriv, nlPriv) pm := setupProfileManager(t, nodePriv, nlPriv)
// Make a fake TKA authority, to seed local state. // Make a fake TKA authority, to seed local state.
disablementSecret := bytes.Repeat([]byte{0xa5}, 32)
key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2} key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2}
state := tka.CreateStateForTest(key)
temp := t.TempDir() varRoot, chonk := setupChonkStorage(t, pm)
tkaPath := filepath.Join(temp, "tka-profile", string(pm.CurrentProfile().ID())) authority, _, err := tka.Create(chonk, state, nlPriv)
os.Mkdir(tkaPath, 0755)
chonk, err := tka.ChonkDir(tkaPath)
if err != nil {
t.Fatal(err)
}
authority, _, err := tka.Create(chonk, tka.State{
Keys: []tka.Key{key},
DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
}, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
} }
@@ -887,20 +831,8 @@ func TestTKASign(t *testing.T) {
} }
})) }))
defer ts.Close() defer ts.Close()
cc := fakeControlClient(t, client)
b := LocalBackend{ b := newLocalBackendForTKA(t, varRoot, client, pm, authority, chonk)
varRoot: temp,
cc: cc,
ccAuto: cc,
logf: t.Logf,
health: health.NewTracker(eventbustest.NewBus(t)),
tka: &tkaState{
authority: authority,
storage: chonk,
},
pm: pm,
store: pm.Store(),
}
if err := b.NetworkLockSign(toSign.Public(), nil); err != nil { if err := b.NetworkLockSign(toSign.Public(), nil); err != nil {
t.Errorf("NetworkLockSign() failed: %v", err) t.Errorf("NetworkLockSign() failed: %v", err)
@@ -911,23 +843,14 @@ func TestTKAForceDisable(t *testing.T) {
nodePriv := key.NewNode() nodePriv := key.NewNode()
// Make a fake TKA authority, to seed local state. // Make a fake TKA authority, to seed local state.
disablementSecret := bytes.Repeat([]byte{0xa5}, 32)
nlPriv := key.NewNLPrivate() nlPriv := key.NewNLPrivate()
key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2} key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2}
state := tka.CreateStateForTest(key)
pm := setupProfileManager(t, nodePriv, nlPriv) pm := setupProfileManager(t, nodePriv, nlPriv)
temp := t.TempDir() temp, chonk := setupChonkStorage(t, pm)
tkaPath := filepath.Join(temp, "tka-profile", string(pm.CurrentProfile().ID())) authority, genesis, err := tka.Create(chonk, state, nlPriv)
os.Mkdir(tkaPath, 0755)
chonk, err := tka.ChonkDir(tkaPath)
if err != nil {
t.Fatal(err)
}
authority, genesis, err := tka.Create(chonk, tka.State{
Keys: []tka.Key{key},
DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
}, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
} }
@@ -1002,20 +925,11 @@ func TestTKAAffectedSigs(t *testing.T) {
pm := setupProfileManager(t, nodePriv, nlPriv) pm := setupProfileManager(t, nodePriv, nlPriv)
// Make a fake TKA authority, to seed local state. // Make a fake TKA authority, to seed local state.
disablementSecret := bytes.Repeat([]byte{0xa5}, 32)
tkaKey := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2} tkaKey := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2}
state := tka.CreateStateForTest(tkaKey)
temp := t.TempDir() varRoot, chonk := setupChonkStorage(t, pm)
tkaPath := filepath.Join(temp, "tka-profile", string(pm.CurrentProfile().ID())) authority, _, err := tka.Create(chonk, state, nlPriv)
os.Mkdir(tkaPath, 0755)
chonk, err := tka.ChonkDir(tkaPath)
if err != nil {
t.Fatal(err)
}
authority, _, err := tka.Create(chonk, tka.State{
Keys: []tka.Key{tkaKey},
DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
}, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
} }
@@ -1084,20 +998,7 @@ func TestTKAAffectedSigs(t *testing.T) {
} }
})) }))
defer ts.Close() defer ts.Close()
cc := fakeControlClient(t, client) b := newLocalBackendForTKA(t, varRoot, client, pm, authority, chonk)
b := LocalBackend{
varRoot: temp,
cc: cc,
ccAuto: cc,
logf: t.Logf,
health: health.NewTracker(eventbustest.NewBus(t)),
tka: &tkaState{
authority: authority,
storage: chonk,
},
pm: pm,
store: pm.Store(),
}
sigs, err := b.NetworkLockAffectedSigs(nlPriv.KeyID()) sigs, err := b.NetworkLockAffectedSigs(nlPriv.KeyID())
switch { switch {
@@ -1130,22 +1031,13 @@ func TestTKARecoverCompromisedKeyFlow(t *testing.T) {
pm := setupProfileManager(t, nodePriv, nlPriv) pm := setupProfileManager(t, nodePriv, nlPriv)
// Make a fake TKA authority, to seed local state. // Make a fake TKA authority, to seed local state.
disablementSecret := bytes.Repeat([]byte{0xa5}, 32)
key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2} key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2}
cosignKey := tka.Key{Kind: tka.Key25519, Public: cosignPriv.Public().Verifier(), Votes: 2} cosignKey := tka.Key{Kind: tka.Key25519, Public: cosignPriv.Public().Verifier(), Votes: 2}
compromisedKey := tka.Key{Kind: tka.Key25519, Public: compromisedPriv.Public().Verifier(), Votes: 1} compromisedKey := tka.Key{Kind: tka.Key25519, Public: compromisedPriv.Public().Verifier(), Votes: 1}
state := tka.CreateStateForTest(key, compromisedKey, cosignKey)
temp := t.TempDir() varRoot, chonk := setupChonkStorage(t, pm)
tkaPath := filepath.Join(temp, "tka-profile", string(pm.CurrentProfile().ID())) authority, _, err := tka.Create(chonk, state, nlPriv)
os.Mkdir(tkaPath, 0755)
chonk, err := tka.ChonkDir(tkaPath)
if err != nil {
t.Fatal(err)
}
authority, _, err := tka.Create(chonk, tka.State{
Keys: []tka.Key{key, compromisedKey, cosignKey},
DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
}, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
} }
@@ -1170,20 +1062,7 @@ func TestTKARecoverCompromisedKeyFlow(t *testing.T) {
} }
})) }))
defer ts.Close() defer ts.Close()
cc := fakeControlClient(t, client) b := newLocalBackendForTKA(t, varRoot, client, pm, authority, chonk)
b := LocalBackend{
varRoot: temp,
cc: cc,
ccAuto: cc,
logf: t.Logf,
health: health.NewTracker(eventbustest.NewBus(t)),
tka: &tkaState{
authority: authority,
storage: chonk,
},
pm: pm,
store: pm.Store(),
}
aum, err := b.NetworkLockGenerateRecoveryAUM([]tkatype.KeyID{compromisedPriv.KeyID()}, tka.AUMHash{}) aum, err := b.NetworkLockGenerateRecoveryAUM([]tkatype.KeyID{compromisedPriv.KeyID()}, tka.AUMHash{})
if err != nil { if err != nil {
@@ -1193,17 +1072,7 @@ func TestTKARecoverCompromisedKeyFlow(t *testing.T) {
// Cosign using the cosigning key. // Cosign using the cosigning key.
{ {
pm := setupProfileManager(t, nodePriv, cosignPriv) pm := setupProfileManager(t, nodePriv, cosignPriv)
b := LocalBackend{ b := newLocalBackendForTKA(t, varRoot, client, pm, authority, chonk)
varRoot: temp,
logf: t.Logf,
health: health.NewTracker(eventbustest.NewBus(t)),
tka: &tkaState{
authority: authority,
storage: chonk,
},
pm: pm,
store: pm.Store(),
}
if aum, err = b.NetworkLockCosignRecoveryAUM(aum); err != nil { if aum, err = b.NetworkLockCosignRecoveryAUM(aum); err != nil {
t.Fatalf("NetworkLockCosignRecoveryAUM() failed: %v", err) t.Fatalf("NetworkLockCosignRecoveryAUM() failed: %v", err)
} }
+14 -28
View File
@@ -27,12 +27,10 @@ func (s signer25519) SignAUM(sigHash tkatype.AUMSigHash) ([]tkatype.Signature, e
func TestAuthorityBuilderAddKey(t *testing.T) { func TestAuthorityBuilderAddKey(t *testing.T) {
pub, priv := testingKey25519(t, 1) pub, priv := testingKey25519(t, 1)
key := Key{Kind: Key25519, Public: pub, Votes: 2} key := Key{Kind: Key25519, Public: pub, Votes: 2}
state := CreateStateForTest(key)
storage := ChonkMem() storage := ChonkMem()
a, _, err := Create(storage, State{ a, _, err := Create(storage, state, signer25519(priv))
Keys: []Key{key},
DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
} }
@@ -61,12 +59,10 @@ func TestAuthorityBuilderAddKey(t *testing.T) {
func TestAuthorityBuilderMaxKey(t *testing.T) { func TestAuthorityBuilderMaxKey(t *testing.T) {
pub, priv := testingKey25519(t, 1) pub, priv := testingKey25519(t, 1)
key := Key{Kind: Key25519, Public: pub, Votes: 2} key := Key{Kind: Key25519, Public: pub, Votes: 2}
state := CreateStateForTest(key)
storage := ChonkMem() storage := ChonkMem()
a, _, err := Create(storage, State{ a, _, err := Create(storage, state, signer25519(priv))
Keys: []Key{key},
DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
} }
@@ -108,12 +104,10 @@ func TestAuthorityBuilderRemoveKey(t *testing.T) {
key := Key{Kind: Key25519, Public: pub, Votes: 2} key := Key{Kind: Key25519, Public: pub, Votes: 2}
pub2, _ := testingKey25519(t, 2) pub2, _ := testingKey25519(t, 2)
key2 := Key{Kind: Key25519, Public: pub2, Votes: 1} key2 := Key{Kind: Key25519, Public: pub2, Votes: 1}
state := CreateStateForTest(key, key2)
storage := ChonkMem() storage := ChonkMem()
a, _, err := Create(storage, State{ a, _, err := Create(storage, state, signer25519(priv))
Keys: []Key{key, key2},
DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
} }
@@ -154,12 +148,10 @@ func TestAuthorityBuilderRemoveKey(t *testing.T) {
func TestAuthorityBuilderSetKeyVote(t *testing.T) { func TestAuthorityBuilderSetKeyVote(t *testing.T) {
pub, priv := testingKey25519(t, 1) pub, priv := testingKey25519(t, 1)
key := Key{Kind: Key25519, Public: pub, Votes: 2} key := Key{Kind: Key25519, Public: pub, Votes: 2}
state := CreateStateForTest(key)
storage := ChonkMem() storage := ChonkMem()
a, _, err := Create(storage, State{ a, _, err := Create(storage, state, signer25519(priv))
Keys: []Key{key},
DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
} }
@@ -190,12 +182,10 @@ func TestAuthorityBuilderSetKeyVote(t *testing.T) {
func TestAuthorityBuilderSetKeyMeta(t *testing.T) { func TestAuthorityBuilderSetKeyMeta(t *testing.T) {
pub, priv := testingKey25519(t, 1) pub, priv := testingKey25519(t, 1)
key := Key{Kind: Key25519, Public: pub, Votes: 2, Meta: map[string]string{"a": "b"}} key := Key{Kind: Key25519, Public: pub, Votes: 2, Meta: map[string]string{"a": "b"}}
state := CreateStateForTest(key)
storage := ChonkMem() storage := ChonkMem()
a, _, err := Create(storage, State{ a, _, err := Create(storage, state, signer25519(priv))
Keys: []Key{key},
DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
} }
@@ -226,12 +216,10 @@ func TestAuthorityBuilderSetKeyMeta(t *testing.T) {
func TestAuthorityBuilderMultiple(t *testing.T) { func TestAuthorityBuilderMultiple(t *testing.T) {
pub, priv := testingKey25519(t, 1) pub, priv := testingKey25519(t, 1)
key := Key{Kind: Key25519, Public: pub, Votes: 2} key := Key{Kind: Key25519, Public: pub, Votes: 2}
state := CreateStateForTest(key)
storage := ChonkMem() storage := ChonkMem()
a, _, err := Create(storage, State{ a, _, err := Create(storage, state, signer25519(priv))
Keys: []Key{key},
DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
} }
@@ -274,12 +262,10 @@ func TestAuthorityBuilderMultiple(t *testing.T) {
func TestAuthorityBuilderCheckpointsAfterXUpdates(t *testing.T) { func TestAuthorityBuilderCheckpointsAfterXUpdates(t *testing.T) {
pub, priv := testingKey25519(t, 1) pub, priv := testingKey25519(t, 1)
key := Key{Kind: Key25519, Public: pub, Votes: 2} key := Key{Kind: Key25519, Public: pub, Votes: 2}
state := CreateStateForTest(key)
storage := ChonkMem() storage := ChonkMem()
a, _, err := Create(storage, State{ a, _, err := Create(storage, state, signer25519(priv))
Keys: []Key{key},
DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
} }
+2 -4
View File
@@ -321,10 +321,8 @@ func optTemplate(name string, template AUM) testchainOpt {
} }
func genesisTemplate(key Key) testchainOpt { func genesisTemplate(key Key) testchainOpt {
return optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{ state := CreateStateForTest(key)
Keys: []Key{key}, return optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &state})
DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}})
} }
func checkpointTemplate() testchainOpt { func checkpointTemplate() testchainOpt {
+2 -4
View File
@@ -72,10 +72,8 @@ func TestNLPrivate(t *testing.T) {
// Test that key.NLPrivate implements Signer by making a new // Test that key.NLPrivate implements Signer by making a new
// authority. // authority.
k := Key{Kind: Key25519, Public: pub.Verifier(), Votes: 1} k := Key{Kind: Key25519, Public: pub.Verifier(), Votes: 1}
_, aum, err := Create(ChonkMem(), State{ state := CreateStateForTest(k)
Keys: []Key{k}, _, aum, err := Create(ChonkMem(), state, p)
DisablementValues: [][]byte{bytes.Repeat([]byte{1}, 32)},
}, p)
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
} }
+16
View File
@@ -13,6 +13,7 @@ import (
"golang.org/x/crypto/argon2" "golang.org/x/crypto/argon2"
"tailscale.com/types/tkatype" "tailscale.com/types/tkatype"
"tailscale.com/util/testenv"
) )
// ErrNoSuchKey is returned if the key referenced by a KeyID does not exist. // ErrNoSuchKey is returned if the key referenced by a KeyID does not exist.
@@ -313,3 +314,18 @@ func (s *State) staticValidateCheckpoint() error {
} }
return nil return nil
} }
// CreateStateForTest creates a [State] that marks the given keys as trusted
// with an arbitrary disablement value.
//
// This is only for use in tests, and will panic if called outside a test.
func CreateStateForTest(keys ...Key) State {
testenv.AssertInTest()
disablementSecret := bytes.Repeat([]byte{0xa5}, 32)
return State{
Keys: keys,
DisablementValues: [][]byte{DisablementKDF(disablementSecret)},
}
}
+2 -4
View File
@@ -597,12 +597,10 @@ func TestCompactLongButYoung(t *testing.T) {
ourPriv := key.NewNLPrivate() ourPriv := key.NewNLPrivate()
ourKey := Key{Kind: Key25519, Public: ourPriv.Public().Verifier(), Votes: 1} ourKey := Key{Kind: Key25519, Public: ourPriv.Public().Verifier(), Votes: 1}
someOtherKey := Key{Kind: Key25519, Public: key.NewNLPrivate().Public().Verifier(), Votes: 1} someOtherKey := Key{Kind: Key25519, Public: key.NewNLPrivate().Public().Verifier(), Votes: 1}
state := CreateStateForTest(ourKey, someOtherKey)
storage := ChonkMem() storage := ChonkMem()
auth, _, err := Create(storage, State{ auth, _, err := Create(storage, state, ourPriv)
Keys: []Key{ourKey, someOtherKey},
DisablementValues: [][]byte{DisablementKDF(bytes.Repeat([]byte{0xa5}, 32))},
}, ourPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
} }
+20 -29
View File
@@ -197,6 +197,7 @@ func TestComputeStateAt(t *testing.T) {
// for tests you want one AUM to be 'lower' than another, so that // for tests you want one AUM to be 'lower' than another, so that
// that chain is taken based on fork resolution rules). // that chain is taken based on fork resolution rules).
func fakeAUM(t *testing.T, template any, parent *AUMHash) (AUM, AUMHash) { func fakeAUM(t *testing.T, template any, parent *AUMHash) (AUM, AUMHash) {
t.Helper()
if seed, ok := template.(int); ok { if seed, ok := template.(int); ok {
a := AUM{MessageKind: AUMNoOp, KeyID: []byte{byte(seed)}} a := AUM{MessageKind: AUMNoOp, KeyID: []byte{byte(seed)}}
if parent != nil { if parent != nil {
@@ -299,12 +300,17 @@ func TestAuthorityHead(t *testing.T) {
func TestAuthorityValidDisablement(t *testing.T) { func TestAuthorityValidDisablement(t *testing.T) {
pub, _ := testingKey25519(t, 1) pub, _ := testingKey25519(t, 1)
key := Key{Kind: Key25519, Public: pub, Votes: 2} key := Key{Kind: Key25519, Public: pub, Votes: 2}
disablementSecret := []byte{1, 2, 3}
state := State{
Keys: []Key{key},
DisablementValues: [][]byte{DisablementKDF(disablementSecret)},
}
c := newTestchain(t, ` c := newTestchain(t, `
G1 -> L1 G1 -> L1
G1.template = genesis G1.template = genesis
`, `,
genesisTemplate(key), optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &state}),
) )
a, _ := Open(c.Chonk()) a, _ := Open(c.Chonk())
@@ -317,10 +323,7 @@ func TestCreateBootstrapAuthority(t *testing.T) {
pub, priv := testingKey25519(t, 1) pub, priv := testingKey25519(t, 1)
key := Key{Kind: Key25519, Public: pub, Votes: 2} key := Key{Kind: Key25519, Public: pub, Votes: 2}
a1, genesisAUM, err := Create(ChonkMem(), State{ a1, genesisAUM, err := Create(ChonkMem(), CreateStateForTest(key), signer25519(priv))
Keys: []Key{key},
DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
} }
@@ -349,10 +352,7 @@ func TestBootstrapChonkMustBeEmpty(t *testing.T) {
pub, priv := testingKey25519(t, 1) pub, priv := testingKey25519(t, 1)
key := Key{Kind: Key25519, Public: pub, Votes: 2} key := Key{Kind: Key25519, Public: pub, Votes: 2}
state := State{ state := CreateStateForTest(key)
Keys: []Key{key},
DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}
// Bootstrap our chonk for the first time, which should succeed. // Bootstrap our chonk for the first time, which should succeed.
_, _, err := Create(chonk, state, signer25519(priv)) _, _, err := Create(chonk, state, signer25519(priv))
@@ -412,7 +412,7 @@ func TestAuthorityInformNonLinear(t *testing.T) {
| -> L4 -> L5 | -> L4 -> L5
G1.template = genesis G1.template = genesis
L1.hashSeed = 3 L1.hashSeed = 2
L2.hashSeed = 2 L2.hashSeed = 2
L4.hashSeed = 2 L4.hashSeed = 2
`, `,
@@ -445,6 +445,8 @@ func TestAuthorityInformNonLinear(t *testing.T) {
} }
if a.Head() != c.AUMHashes["L3"] { if a.Head() != c.AUMHashes["L3"] {
t.Logf("a.Head() = %s", a.Head())
t.Logf("auMHashes = %v", c.AUMHashes)
t.Fatal("authority did not converge to correct AUM") t.Fatal("authority did not converge to correct AUM")
} }
} }
@@ -495,21 +497,12 @@ func TestInteropWithNLKey(t *testing.T) {
pub2 := key.NewNLPrivate().Public() pub2 := key.NewNLPrivate().Public()
pub3 := key.NewNLPrivate().Public() pub3 := key.NewNLPrivate().Public()
a, _, err := Create(ChonkMem(), State{ state := CreateStateForTest(
Keys: []Key{ Key{Kind: Key25519, Votes: 1, Public: pub1.KeyID()},
{ Key{Kind: Key25519, Votes: 1, Public: pub2.KeyID()},
Kind: Key25519, )
Votes: 1,
Public: pub1.KeyID(), a, _, err := Create(ChonkMem(), state, priv1)
},
{
Kind: Key25519,
Votes: 1,
Public: pub2.KeyID(),
},
},
DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, priv1)
if err != nil { if err != nil {
t.Errorf("tka.Create: %v", err) t.Errorf("tka.Create: %v", err)
return return
@@ -529,6 +522,7 @@ func TestInteropWithNLKey(t *testing.T) {
func TestAuthorityCompact(t *testing.T) { func TestAuthorityCompact(t *testing.T) {
pub, priv := testingKey25519(t, 1) pub, priv := testingKey25519(t, 1)
key := Key{Kind: Key25519, Public: pub, Votes: 2} key := Key{Kind: Key25519, Public: pub, Votes: 2}
state := CreateStateForTest(key)
c := newTestchain(t, ` c := newTestchain(t, `
G -> A -> B -> C -> D -> E G -> A -> B -> C -> D -> E
@@ -537,10 +531,7 @@ func TestAuthorityCompact(t *testing.T) {
C.template = checkpoint2 C.template = checkpoint2
`, `,
genesisTemplate(key), genesisTemplate(key),
optTemplate("checkpoint2", AUM{MessageKind: AUMCheckpoint, State: &State{ optTemplate("checkpoint2", AUM{MessageKind: AUMCheckpoint, State: &state}),
Keys: []Key{key},
DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}}),
optKey("key", key, priv), optKey("key", key, priv),
optSignAllUsing("key")) optSignAllUsing("key"))