@ -17,6 +17,7 @@ import (
"path/filepath"
"reflect"
"testing"
"time"
go4mem "go4.org/mem"
@ -31,6 +32,7 @@ import (
"tailscale.com/tailcfg"
"tailscale.com/tka"
"tailscale.com/tsd"
"tailscale.com/tstest"
"tailscale.com/types/key"
"tailscale.com/types/netmap"
"tailscale.com/types/persist"
@ -89,7 +91,7 @@ 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 }
a1 , genesisAUM , err := tka . Create ( & tka . Mem { } , tka . State {
a1 , genesisAUM , err := tka . Create ( tka . ChonkMem ( ) , tka . State {
Keys : [ ] tka . Key { key } ,
DisablementSecrets : [ ] [ ] byte { bytes . Repeat ( [ ] byte { 0xa5 } , 32 ) } ,
} , nlPriv )
@ -399,7 +401,7 @@ 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 . Mem { }
controlStorage := tka . ChonkMem ( )
controlAuthority , bootstrap , err := tka . Create ( controlStorage , tka . State {
Keys : [ ] tka . Key { key , someKey } ,
DisablementSecrets : [ ] [ ] byte { tka . DisablementKDF ( disablementSecret ) } ,
@ -548,10 +550,226 @@ func TestTKASync(t *testing.T) {
}
}
// Whenever we run a TKA sync and get new state from control, we compact the
// local state.
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 := must . Get ( newProfileManager ( new ( mem . Store ) , t . Logf , health . NewTracker ( eventbustest . NewBus ( t ) ) ) )
must . Do ( pm . SetPrefs ( ( & ipn . Prefs {
Persist : & persist . Persist {
PrivateNodeKey : nodePriv ,
NetworkLockKey : nlPriv ,
} ,
} ) . View ( ) , ipn . NetworkProfile { } ) )
// Create a clock, and roll it back by 30 days.
//
// Our compaction algorithm preserves AUMs received in the last 14 days, so
// we need to backdate the commit times to make the AUMs eligible for compaction.
clock := tstest . NewClock ( tstest . ClockOpts { } )
clock . Advance ( - 30 * 24 * time . Hour )
// Set up the TKA authority on the control plane.
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 } ,
DisablementSecrets : [ ] [ ] byte { tka . DisablementKDF ( disablementSecret ) } ,
} , nlPriv )
if err != nil {
t . Fatalf ( "tka.Create() failed: %v" , err )
}
// Fill the control plane TKA authority with a lot of AUMs, enough so that:
//
// 1. the chain of AUMs includes some checkpoints
// 2. the chain is long enough it would be trimmed if we ran the compaction
// algorithm with the defaults
for range 100 {
upd := controlAuthority . NewUpdater ( nlPriv )
if err := upd . RemoveKey ( someKey . MustID ( ) ) ; err != nil {
t . Fatalf ( "RemoveKey: %v" , err )
}
if err := upd . AddKey ( someKey ) ; err != nil {
t . Fatalf ( "AddKey: %v" , err )
}
aums , err := upd . Finalize ( controlStorage )
if err != nil {
t . Fatalf ( "Finalize: %v" , err )
}
if err := controlAuthority . Inform ( controlStorage , aums ) ; err != nil {
t . Fatalf ( "controlAuthority.Inform() failed: %v" , err )
}
}
// Set up the TKA authority on the node.
nodeStorage := tka . ChonkMem ( )
nodeStorage . SetClock ( clock )
nodeAuthority , err := tka . Bootstrap ( nodeStorage , bootstrap )
if err != nil {
t . Fatalf ( "tka.Bootstrap() failed: %v" , err )
}
// Make a mock control server.
ts , client := fakeNoiseServer ( t , http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
defer r . Body . Close ( )
switch r . URL . Path {
case "/machine/tka/sync/offer" :
body := new ( tailcfg . TKASyncOfferRequest )
if err := json . NewDecoder ( r . Body ) . Decode ( body ) ; err != nil {
t . Fatal ( err )
}
t . Logf ( "got sync offer:\n%+v" , body )
nodeOffer , err := toSyncOffer ( body . Head , body . Ancestors )
if err != nil {
t . Fatal ( err )
}
controlOffer , err := controlAuthority . SyncOffer ( controlStorage )
if err != nil {
t . Fatal ( err )
}
sendAUMs , err := controlAuthority . MissingAUMs ( controlStorage , nodeOffer )
if err != nil {
t . Fatal ( err )
}
head , ancestors , err := fromSyncOffer ( controlOffer )
if err != nil {
t . Fatal ( err )
}
resp := tailcfg . TKASyncOfferResponse {
Head : head ,
Ancestors : ancestors ,
MissingAUMs : make ( [ ] tkatype . MarshaledAUM , len ( sendAUMs ) ) ,
}
for i , a := range sendAUMs {
resp . MissingAUMs [ i ] = a . Serialize ( )
}
t . Logf ( "responding to sync offer with:\n%+v" , resp )
w . WriteHeader ( 200 )
if err := json . NewEncoder ( w ) . Encode ( resp ) ; err != nil {
t . Fatal ( err )
}
case "/machine/tka/sync/send" :
body := new ( tailcfg . TKASyncSendRequest )
if err := json . NewDecoder ( r . Body ) . Decode ( body ) ; err != nil {
t . Fatal ( err )
}
t . Logf ( "got sync send:\n%+v" , body )
var remoteHead tka . AUMHash
if err := remoteHead . UnmarshalText ( [ ] byte ( body . Head ) ) ; err != nil {
t . Fatalf ( "head unmarshal: %v" , err )
}
toApply := make ( [ ] tka . AUM , len ( body . MissingAUMs ) )
for i , a := range body . MissingAUMs {
if err := toApply [ i ] . Unserialize ( a ) ; err != nil {
t . Fatalf ( "decoding missingAUM[%d]: %v" , i , err )
}
}
if len ( toApply ) > 0 {
if err := controlAuthority . Inform ( controlStorage , toApply ) ; err != nil {
t . Fatalf ( "control.Inform(%+v) failed: %v" , toApply , err )
}
}
head , err := controlAuthority . Head ( ) . MarshalText ( )
if err != nil {
t . Fatal ( err )
}
w . WriteHeader ( 200 )
if err := json . NewEncoder ( w ) . Encode ( tailcfg . TKASyncSendResponse {
Head : string ( head ) ,
} ) ; err != nil {
t . Fatal ( err )
}
default :
t . Errorf ( "unhandled endpoint path: %v" , r . URL . Path )
w . WriteHeader ( 404 )
}
} ) )
defer ts . Close ( )
// Setup the client.
cc , _ := fakeControlClient ( t , client )
b := LocalBackend {
cc : cc ,
ccAuto : cc ,
logf : t . Logf ,
pm : pm ,
store : pm . Store ( ) ,
tka : & tkaState {
authority : nodeAuthority ,
storage : nodeStorage ,
} ,
}
// Trigger a sync.
err = b . tkaSyncIfNeeded ( & netmap . NetworkMap {
TKAEnabled : true ,
TKAHead : controlAuthority . Head ( ) ,
} , pm . CurrentPrefs ( ) )
if err != nil {
t . Errorf ( "tkaSyncIfNeeded() failed: %v" , err )
}
// Add a new AUM in control.
upd := controlAuthority . NewUpdater ( nlPriv )
if err := upd . RemoveKey ( someKey . MustID ( ) ) ; err != nil {
t . Fatalf ( "RemoveKey: %v" , err )
}
aums , err := upd . Finalize ( controlStorage )
if err != nil {
t . Fatalf ( "Finalize: %v" , err )
}
if err := controlAuthority . Inform ( controlStorage , aums ) ; err != nil {
t . Fatalf ( "controlAuthority.Inform() failed: %v" , err )
}
// Run a second sync, which should trigger a compaction.
err = b . tkaSyncIfNeeded ( & netmap . NetworkMap {
TKAEnabled : true ,
TKAHead : controlAuthority . Head ( ) ,
} , pm . CurrentPrefs ( ) )
if err != nil {
t . Errorf ( "tkaSyncIfNeeded() failed: %v" , err )
}
// Check that the node and control plane are in sync.
if nodeHead , controlHead := b . tka . authority . Head ( ) , controlAuthority . Head ( ) ; nodeHead != controlHead {
t . Errorf ( "node head = %v, want %v" , nodeHead , controlHead )
}
// Check the node has compacted away some of its AUMs; that it has purged some AUMs which
// are still kept in the control plane.
nodeAUMs , err := b . tka . storage . AllAUMs ( )
if err != nil {
t . Errorf ( "AllAUMs() for node failed: %v" , err )
}
controlAUMS , err := controlStorage . AllAUMs ( )
if err != nil {
t . Errorf ( "AllAUMs() for control failed: %v" , err )
}
if len ( nodeAUMs ) == len ( controlAUMS ) {
t . Errorf ( "node has not compacted; it has the same number of AUMs as control (node = control = %d)" , len ( nodeAUMs ) )
}
}
func TestTKAFilterNetmap ( t * testing . T ) {
nlPriv := key . NewNLPrivate ( )
nlKey := tka . Key { Kind : tka . Key25519 , Public : nlPriv . Public ( ) . Verifier ( ) , Votes : 2 }
storage := & tka . Mem { }
storage := tka . ChonkMem ( )
authority , _ , err := tka . Create ( storage , tka . State {
Keys : [ ] tka . Key { nlKey } ,
DisablementSecrets : [ ] [ ] byte { bytes . Repeat ( [ ] byte { 0xa5 } , 32 ) } ,