From cb4430f36044fe21cca190f03e64bc6ee114796f Mon Sep 17 00:00:00 2001 From: Alex Chan Date: Thu, 30 Apr 2026 12:32:22 +0100 Subject: [PATCH] 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 --- feature/tailnetlock/tailnetlock_test.go | 7 +- ipn/ipnlocal/network-lock_test.go | 267 ++++++------------------ tka/builder_test.go | 42 ++-- tka/chaintest_test.go | 6 +- tka/key_test.go | 6 +- tka/state.go | 16 ++ tka/tailchonk_test.go | 6 +- tka/tka_test.go | 49 ++--- 8 files changed, 126 insertions(+), 273 deletions(-) diff --git a/feature/tailnetlock/tailnetlock_test.go b/feature/tailnetlock/tailnetlock_test.go index 771525d9d..bad294109 100644 --- a/feature/tailnetlock/tailnetlock_test.go +++ b/feature/tailnetlock/tailnetlock_test.go @@ -22,15 +22,12 @@ func TestHandleC2NDebugTKA(t *testing.T) { return nil, nil } - disablementSecret := bytes.Repeat([]byte{0xa5}, 32) signerKey := key.NewNLPrivate() key1 := tka.Key{Kind: tka.Key25519, Public: signerKey.Public().Verifier(), Votes: 2} + state := tka.CreateStateForTest(key1) chonk := tka.ChonkMem() - authority, _, err := tka.Create(chonk, tka.State{ - Keys: []tka.Key{key1}, - DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)}, - }, signerKey) + authority, _, err := tka.Create(chonk, state, signerKey) if err != nil { t.Fatalf("tka.Create() failed: %v", err) } diff --git a/ipn/ipnlocal/network-lock_test.go b/ipn/ipnlocal/network-lock_test.go index eead2d892..1fceb748a 100644 --- a/ipn/ipnlocal/network-lock_test.go +++ b/ipn/ipnlocal/network-lock_test.go @@ -84,7 +84,29 @@ func fakeNoiseServer(t *testing.T, handler http.HandlerFunc) (*httptest.Server, 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 { + t.Helper() pm := must.Get(newProfileManager(new(mem.Store), t.Logf, health.NewTracker(eventbustest.NewBus(t)))) must.Do(pm.SetPrefs((&ipn.Prefs{ Persist: &persist.Persist{ @@ -95,6 +117,18 @@ func setupProfileManager(t *testing.T, nodePriv key.NodePrivate, nlPriv key.NLPr 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) { nodePriv := key.NewNode() @@ -102,11 +136,9 @@ func TestTKAEnablementFlow(t *testing.T) { // our mock server can communicate. nlPriv := key.NewNLPrivate() key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2} + state := tka.CreateStateForTest(key) chonk := tka.ChonkMem() - a1, genesisAUM, err := tka.Create(chonk, tka.State{ - Keys: []tka.Key{key}, - DisablementValues: [][]byte{bytes.Repeat([]byte{0xa5}, 32)}, - }, nlPriv) + a1, genesisAUM, err := tka.Create(chonk, state, nlPriv) if err != nil { t.Fatalf("tka.Create() failed: %v", err) } @@ -188,13 +220,7 @@ func TestTKADisablementFlow(t *testing.T) { pm := setupProfileManager(t, nodePriv, nlPriv) - temp := t.TempDir() - 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) - } + varRoot, chonk := setupChonkStorage(t, pm) authority, _, err := tka.Create(chonk, tka.State{ Keys: []tka.Key{key}, DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)}, @@ -239,20 +265,7 @@ func TestTKADisablementFlow(t *testing.T) { })) defer ts.Close() - cc := fakeControlClient(t, client) - 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(), - } + b := newLocalBackendForTKA(t, varRoot, client, pm, authority, chonk) // Test that the wrong disablement secret does not shut down the authority. returnWrongSecret = true @@ -289,8 +302,6 @@ func TestTKASync(t *testing.T) { someKeyPriv := key.NewNLPrivate() someKey := tka.Key{Kind: tka.Key25519, Public: someKeyPriv.Public().Verifier(), Votes: 1} - disablementSecret := bytes.Repeat([]byte{0xa5}, 32) - type tkaSyncScenario struct { name string // 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. key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2} controlStorage := tka.ChonkMem() - controlAuthority, bootstrap, err := tka.Create(controlStorage, tka.State{ - Keys: []tka.Key{key, someKey}, - DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)}, - }, nlPriv) + controlState := tka.CreateStateForTest(key, someKey) + controlAuthority, bootstrap, err := tka.Create(controlStorage, controlState, nlPriv) if err != nil { 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. - nodeStorage, err := tka.ChonkDir(tkaPath) - if err != nil { - t.Fatal(err) - } + varRoot, nodeStorage := setupChonkStorage(t, pm) nodeAuthority, err := tka.Bootstrap(nodeStorage, bootstrap) if err != nil { t.Fatalf("tka.Bootstrap() failed: %v", err) @@ -424,20 +427,7 @@ func TestTKASync(t *testing.T) { defer ts.Close() // Setup the client. - cc := fakeControlClient(t, client) - 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, - }, - } + b := newLocalBackendForTKA(t, varRoot, client, pm, nodeAuthority, nodeStorage) // Finally, let's trigger a sync. err = b.tkaSyncIfNeeded(&netmap.NetworkMap{ @@ -463,8 +453,6 @@ func TestTKASyncTriggersCompact(t *testing.T) { someKeyPriv := key.NewNLPrivate() someKey := tka.Key{Kind: tka.Key25519, Public: someKeyPriv.Public().Verifier(), Votes: 1} - disablementSecret := bytes.Repeat([]byte{0xa5}, 32) - nodePriv := key.NewNode() nlPriv := key.NewNLPrivate() 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} controlStorage := tka.ChonkMem() controlStorage.SetClock(clock) - controlAuthority, bootstrap, err := tka.Create(controlStorage, tka.State{ - Keys: []tka.Key{key, someKey}, - DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)}, - }, nlPriv) + controlState := tka.CreateStateForTest(key, someKey) + controlAuthority, bootstrap, err := tka.Create(controlStorage, controlState, nlPriv) if err != nil { t.Fatalf("tka.Create() failed: %v", err) } @@ -542,19 +528,8 @@ func TestTKASyncTriggersCompact(t *testing.T) { defer ts.Close() // Setup the client. - cc := fakeControlClient(t, client) - b := LocalBackend{ - cc: cc, - ccAuto: cc, - logf: t.Logf, - health: health.NewTracker(eventbustest.NewBus(t)), - pm: pm, - store: pm.Store(), - tka: &tkaState{ - authority: nodeAuthority, - storage: nodeStorage, - }, - } + varRoot := "" + b := newLocalBackendForTKA(t, varRoot, client, pm, nodeAuthority, nodeStorage) // Trigger a sync. err = b.tkaSyncIfNeeded(&netmap.NetworkMap{ @@ -610,11 +585,9 @@ func TestTKASyncTriggersCompact(t *testing.T) { func TestTKAFilterNetmap(t *testing.T) { nlPriv := key.NewNLPrivate() nlKey := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2} + state := tka.CreateStateForTest(nlKey) storage := tka.ChonkMem() - authority, _, err := tka.Create(storage, tka.State{ - Keys: []tka.Key{nlKey}, - DisablementValues: [][]byte{bytes.Repeat([]byte{0xa5}, 32)}, - }, nlPriv) + authority, _, err := tka.Create(storage, state, nlPriv) if err != nil { 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. disablementSecret := bytes.Repeat([]byte{0xa5}, 32) nlPriv := key.NewNLPrivate() + key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2} pm := setupProfileManager(t, nodePriv, nlPriv) - temp := t.TempDir() - 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) - } + temp, chonk := setupChonkStorage(t, pm) authority, _, err := tka.Create(chonk, tka.State{ Keys: []tka.Key{key}, DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)}, @@ -821,21 +788,7 @@ func TestTKADisable(t *testing.T) { })) defer ts.Close() - cc := fakeControlClient(t, client) - 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(), - } + b := newLocalBackendForTKA(t, temp, client, pm, authority, chonk) // 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" { @@ -854,20 +807,11 @@ func TestTKASign(t *testing.T) { pm := setupProfileManager(t, nodePriv, nlPriv) // 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} + state := tka.CreateStateForTest(key) - temp := t.TempDir() - 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{ - Keys: []tka.Key{key}, - DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)}, - }, nlPriv) + varRoot, chonk := setupChonkStorage(t, pm) + authority, _, err := tka.Create(chonk, state, nlPriv) if err != nil { t.Fatalf("tka.Create() failed: %v", err) } @@ -887,20 +831,8 @@ func TestTKASign(t *testing.T) { } })) defer ts.Close() - cc := fakeControlClient(t, client) - 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(), - } + + b := newLocalBackendForTKA(t, varRoot, client, pm, authority, chonk) if err := b.NetworkLockSign(toSign.Public(), nil); err != nil { t.Errorf("NetworkLockSign() failed: %v", err) @@ -911,23 +843,14 @@ func TestTKAForceDisable(t *testing.T) { nodePriv := key.NewNode() // Make a fake TKA authority, to seed local state. - disablementSecret := bytes.Repeat([]byte{0xa5}, 32) nlPriv := key.NewNLPrivate() key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2} + state := tka.CreateStateForTest(key) pm := setupProfileManager(t, nodePriv, nlPriv) - temp := t.TempDir() - 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, genesis, err := tka.Create(chonk, tka.State{ - Keys: []tka.Key{key}, - DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)}, - }, nlPriv) + temp, chonk := setupChonkStorage(t, pm) + authority, genesis, err := tka.Create(chonk, state, nlPriv) if err != nil { t.Fatalf("tka.Create() failed: %v", err) } @@ -1002,20 +925,11 @@ func TestTKAAffectedSigs(t *testing.T) { pm := setupProfileManager(t, nodePriv, nlPriv) // 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} + state := tka.CreateStateForTest(tkaKey) - temp := t.TempDir() - 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{ - Keys: []tka.Key{tkaKey}, - DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)}, - }, nlPriv) + varRoot, chonk := setupChonkStorage(t, pm) + authority, _, err := tka.Create(chonk, state, nlPriv) if err != nil { t.Fatalf("tka.Create() failed: %v", err) } @@ -1084,20 +998,7 @@ func TestTKAAffectedSigs(t *testing.T) { } })) defer ts.Close() - cc := fakeControlClient(t, client) - 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(), - } + b := newLocalBackendForTKA(t, varRoot, client, pm, authority, chonk) sigs, err := b.NetworkLockAffectedSigs(nlPriv.KeyID()) switch { @@ -1130,22 +1031,13 @@ func TestTKARecoverCompromisedKeyFlow(t *testing.T) { pm := setupProfileManager(t, nodePriv, nlPriv) // 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} cosignKey := tka.Key{Kind: tka.Key25519, Public: cosignPriv.Public().Verifier(), Votes: 2} compromisedKey := tka.Key{Kind: tka.Key25519, Public: compromisedPriv.Public().Verifier(), Votes: 1} + state := tka.CreateStateForTest(key, compromisedKey, cosignKey) - temp := t.TempDir() - 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{ - Keys: []tka.Key{key, compromisedKey, cosignKey}, - DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)}, - }, nlPriv) + varRoot, chonk := setupChonkStorage(t, pm) + authority, _, err := tka.Create(chonk, state, nlPriv) if err != nil { t.Fatalf("tka.Create() failed: %v", err) } @@ -1170,20 +1062,7 @@ func TestTKARecoverCompromisedKeyFlow(t *testing.T) { } })) defer ts.Close() - cc := fakeControlClient(t, client) - 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(), - } + b := newLocalBackendForTKA(t, varRoot, client, pm, authority, chonk) aum, err := b.NetworkLockGenerateRecoveryAUM([]tkatype.KeyID{compromisedPriv.KeyID()}, tka.AUMHash{}) if err != nil { @@ -1193,17 +1072,7 @@ func TestTKARecoverCompromisedKeyFlow(t *testing.T) { // Cosign using the cosigning key. { pm := setupProfileManager(t, nodePriv, cosignPriv) - b := LocalBackend{ - varRoot: temp, - logf: t.Logf, - health: health.NewTracker(eventbustest.NewBus(t)), - tka: &tkaState{ - authority: authority, - storage: chonk, - }, - pm: pm, - store: pm.Store(), - } + b := newLocalBackendForTKA(t, varRoot, client, pm, authority, chonk) if aum, err = b.NetworkLockCosignRecoveryAUM(aum); err != nil { t.Fatalf("NetworkLockCosignRecoveryAUM() failed: %v", err) } diff --git a/tka/builder_test.go b/tka/builder_test.go index 29ecaf88c..4e820e14d 100644 --- a/tka/builder_test.go +++ b/tka/builder_test.go @@ -27,12 +27,10 @@ func (s signer25519) SignAUM(sigHash tkatype.AUMSigHash) ([]tkatype.Signature, e func TestAuthorityBuilderAddKey(t *testing.T) { pub, priv := testingKey25519(t, 1) key := Key{Kind: Key25519, Public: pub, Votes: 2} + state := CreateStateForTest(key) storage := ChonkMem() - a, _, err := Create(storage, State{ - Keys: []Key{key}, - DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})}, - }, signer25519(priv)) + a, _, err := Create(storage, state, signer25519(priv)) if err != nil { t.Fatalf("Create() failed: %v", err) } @@ -61,12 +59,10 @@ func TestAuthorityBuilderAddKey(t *testing.T) { func TestAuthorityBuilderMaxKey(t *testing.T) { pub, priv := testingKey25519(t, 1) key := Key{Kind: Key25519, Public: pub, Votes: 2} + state := CreateStateForTest(key) storage := ChonkMem() - a, _, err := Create(storage, State{ - Keys: []Key{key}, - DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})}, - }, signer25519(priv)) + a, _, err := Create(storage, state, signer25519(priv)) if err != nil { t.Fatalf("Create() failed: %v", err) } @@ -108,12 +104,10 @@ func TestAuthorityBuilderRemoveKey(t *testing.T) { key := Key{Kind: Key25519, Public: pub, Votes: 2} pub2, _ := testingKey25519(t, 2) key2 := Key{Kind: Key25519, Public: pub2, Votes: 1} + state := CreateStateForTest(key, key2) storage := ChonkMem() - a, _, err := Create(storage, State{ - Keys: []Key{key, key2}, - DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})}, - }, signer25519(priv)) + a, _, err := Create(storage, state, signer25519(priv)) if err != nil { t.Fatalf("Create() failed: %v", err) } @@ -154,12 +148,10 @@ func TestAuthorityBuilderRemoveKey(t *testing.T) { func TestAuthorityBuilderSetKeyVote(t *testing.T) { pub, priv := testingKey25519(t, 1) key := Key{Kind: Key25519, Public: pub, Votes: 2} + state := CreateStateForTest(key) storage := ChonkMem() - a, _, err := Create(storage, State{ - Keys: []Key{key}, - DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})}, - }, signer25519(priv)) + a, _, err := Create(storage, state, signer25519(priv)) if err != nil { t.Fatalf("Create() failed: %v", err) } @@ -190,12 +182,10 @@ func TestAuthorityBuilderSetKeyVote(t *testing.T) { func TestAuthorityBuilderSetKeyMeta(t *testing.T) { pub, priv := testingKey25519(t, 1) key := Key{Kind: Key25519, Public: pub, Votes: 2, Meta: map[string]string{"a": "b"}} + state := CreateStateForTest(key) storage := ChonkMem() - a, _, err := Create(storage, State{ - Keys: []Key{key}, - DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})}, - }, signer25519(priv)) + a, _, err := Create(storage, state, signer25519(priv)) if err != nil { t.Fatalf("Create() failed: %v", err) } @@ -226,12 +216,10 @@ func TestAuthorityBuilderSetKeyMeta(t *testing.T) { func TestAuthorityBuilderMultiple(t *testing.T) { pub, priv := testingKey25519(t, 1) key := Key{Kind: Key25519, Public: pub, Votes: 2} + state := CreateStateForTest(key) storage := ChonkMem() - a, _, err := Create(storage, State{ - Keys: []Key{key}, - DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})}, - }, signer25519(priv)) + a, _, err := Create(storage, state, signer25519(priv)) if err != nil { t.Fatalf("Create() failed: %v", err) } @@ -274,12 +262,10 @@ func TestAuthorityBuilderMultiple(t *testing.T) { func TestAuthorityBuilderCheckpointsAfterXUpdates(t *testing.T) { pub, priv := testingKey25519(t, 1) key := Key{Kind: Key25519, Public: pub, Votes: 2} + state := CreateStateForTest(key) storage := ChonkMem() - a, _, err := Create(storage, State{ - Keys: []Key{key}, - DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})}, - }, signer25519(priv)) + a, _, err := Create(storage, state, signer25519(priv)) if err != nil { t.Fatalf("Create() failed: %v", err) } diff --git a/tka/chaintest_test.go b/tka/chaintest_test.go index 0ec612210..71210608b 100644 --- a/tka/chaintest_test.go +++ b/tka/chaintest_test.go @@ -321,10 +321,8 @@ func optTemplate(name string, template AUM) testchainOpt { } func genesisTemplate(key Key) testchainOpt { - return optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{ - Keys: []Key{key}, - DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})}, - }}) + state := CreateStateForTest(key) + return optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &state}) } func checkpointTemplate() testchainOpt { diff --git a/tka/key_test.go b/tka/key_test.go index cc6a1f580..1edeaf54f 100644 --- a/tka/key_test.go +++ b/tka/key_test.go @@ -72,10 +72,8 @@ func TestNLPrivate(t *testing.T) { // Test that key.NLPrivate implements Signer by making a new // authority. k := Key{Kind: Key25519, Public: pub.Verifier(), Votes: 1} - _, aum, err := Create(ChonkMem(), State{ - Keys: []Key{k}, - DisablementValues: [][]byte{bytes.Repeat([]byte{1}, 32)}, - }, p) + state := CreateStateForTest(k) + _, aum, err := Create(ChonkMem(), state, p) if err != nil { t.Fatalf("Create() failed: %v", err) } diff --git a/tka/state.go b/tka/state.go index 69b3dbfeb..de344767c 100644 --- a/tka/state.go +++ b/tka/state.go @@ -13,6 +13,7 @@ import ( "golang.org/x/crypto/argon2" "tailscale.com/types/tkatype" + "tailscale.com/util/testenv" ) // ErrNoSuchKey is returned if the key referenced by a KeyID does not exist. @@ -313,3 +314,18 @@ func (s *State) staticValidateCheckpoint() error { } 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)}, + } +} diff --git a/tka/tailchonk_test.go b/tka/tailchonk_test.go index 23bf45e20..125fbecc0 100644 --- a/tka/tailchonk_test.go +++ b/tka/tailchonk_test.go @@ -597,12 +597,10 @@ func TestCompactLongButYoung(t *testing.T) { ourPriv := key.NewNLPrivate() ourKey := Key{Kind: Key25519, Public: ourPriv.Public().Verifier(), Votes: 1} someOtherKey := Key{Kind: Key25519, Public: key.NewNLPrivate().Public().Verifier(), Votes: 1} + state := CreateStateForTest(ourKey, someOtherKey) storage := ChonkMem() - auth, _, err := Create(storage, State{ - Keys: []Key{ourKey, someOtherKey}, - DisablementValues: [][]byte{DisablementKDF(bytes.Repeat([]byte{0xa5}, 32))}, - }, ourPriv) + auth, _, err := Create(storage, state, ourPriv) if err != nil { t.Fatalf("tka.Create() failed: %v", err) } diff --git a/tka/tka_test.go b/tka/tka_test.go index 4bd0ac083..f0ec3ff68 100644 --- a/tka/tka_test.go +++ b/tka/tka_test.go @@ -197,6 +197,7 @@ func TestComputeStateAt(t *testing.T) { // for tests you want one AUM to be 'lower' than another, so that // that chain is taken based on fork resolution rules). func fakeAUM(t *testing.T, template any, parent *AUMHash) (AUM, AUMHash) { + t.Helper() if seed, ok := template.(int); ok { a := AUM{MessageKind: AUMNoOp, KeyID: []byte{byte(seed)}} if parent != nil { @@ -299,12 +300,17 @@ func TestAuthorityHead(t *testing.T) { func TestAuthorityValidDisablement(t *testing.T) { pub, _ := testingKey25519(t, 1) 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, ` G1 -> L1 G1.template = genesis `, - genesisTemplate(key), + optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &state}), ) a, _ := Open(c.Chonk()) @@ -317,10 +323,7 @@ func TestCreateBootstrapAuthority(t *testing.T) { pub, priv := testingKey25519(t, 1) key := Key{Kind: Key25519, Public: pub, Votes: 2} - a1, genesisAUM, err := Create(ChonkMem(), State{ - Keys: []Key{key}, - DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})}, - }, signer25519(priv)) + a1, genesisAUM, err := Create(ChonkMem(), CreateStateForTest(key), signer25519(priv)) if err != nil { t.Fatalf("Create() failed: %v", err) } @@ -349,10 +352,7 @@ func TestBootstrapChonkMustBeEmpty(t *testing.T) { pub, priv := testingKey25519(t, 1) key := Key{Kind: Key25519, Public: pub, Votes: 2} - state := State{ - Keys: []Key{key}, - DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})}, - } + state := CreateStateForTest(key) // Bootstrap our chonk for the first time, which should succeed. _, _, err := Create(chonk, state, signer25519(priv)) @@ -412,7 +412,7 @@ func TestAuthorityInformNonLinear(t *testing.T) { | -> L4 -> L5 G1.template = genesis - L1.hashSeed = 3 + L1.hashSeed = 2 L2.hashSeed = 2 L4.hashSeed = 2 `, @@ -445,6 +445,8 @@ func TestAuthorityInformNonLinear(t *testing.T) { } 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") } } @@ -495,21 +497,12 @@ func TestInteropWithNLKey(t *testing.T) { pub2 := key.NewNLPrivate().Public() pub3 := key.NewNLPrivate().Public() - a, _, err := Create(ChonkMem(), State{ - Keys: []Key{ - { - Kind: Key25519, - Votes: 1, - Public: pub1.KeyID(), - }, - { - Kind: Key25519, - Votes: 1, - Public: pub2.KeyID(), - }, - }, - DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})}, - }, priv1) + state := CreateStateForTest( + Key{Kind: Key25519, Votes: 1, Public: pub1.KeyID()}, + Key{Kind: Key25519, Votes: 1, Public: pub2.KeyID()}, + ) + + a, _, err := Create(ChonkMem(), state, priv1) if err != nil { t.Errorf("tka.Create: %v", err) return @@ -529,6 +522,7 @@ func TestInteropWithNLKey(t *testing.T) { func TestAuthorityCompact(t *testing.T) { pub, priv := testingKey25519(t, 1) key := Key{Kind: Key25519, Public: pub, Votes: 2} + state := CreateStateForTest(key) c := newTestchain(t, ` G -> A -> B -> C -> D -> E @@ -537,10 +531,7 @@ func TestAuthorityCompact(t *testing.T) { C.template = checkpoint2 `, genesisTemplate(key), - optTemplate("checkpoint2", AUM{MessageKind: AUMCheckpoint, State: &State{ - Keys: []Key{key}, - DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})}, - }}), + optTemplate("checkpoint2", AUM{MessageKind: AUMCheckpoint, State: &state}), optKey("key", key, priv), optSignAllUsing("key"))