@ -95,6 +95,8 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap) error {
return nil
}
b . logf ( "tkaSyncIfNeeded: enabled=%v, head=%v" , nm . TKAEnabled , nm . TKAHead )
b . tkaSyncLock . Lock ( ) // take tkaSyncLock to make this function an exclusive section.
defer b . tkaSyncLock . Unlock ( )
b . mu . Lock ( ) // take mu to protect access to synchronized fields.
@ -125,15 +127,13 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap) error {
}
isEnabled = true
} else if ! wantEnabled && isEnabled {
if b . tka . authority . ValidDisablement ( bs . DisablementSecret ) {
b . tka = nil
isEnabled = false
if err := os . RemoveAll ( b . chonkPath ( ) ) ; err != nil {
return fmt . Errorf ( "os.RemoveAll: %v" , err )
}
if err := b . tkaApplyDisablementLocked ( bs . DisablementSecret ) ; err != nil {
// We log here instead of returning an error (which itself would be
// logged), so that sync will continue even if control gives us an
// incorrect disablement secret.
b . logf ( "Disablement failed, leaving TKA enabled. Error: %v" , err )
} else {
b. logf ( "Disablement secret did not verify, leaving TKA enabled." )
isEnabled = false
}
} else {
return fmt . Errorf ( "[bug] unreachable invariant of wantEnabled /w isEnabled" )
@ -216,12 +216,11 @@ func (b *LocalBackend) tkaSyncLocked(ourNodeKey key.NodePublic) error {
}
}
// NOTE(tom): We could short-circuit here if our HEAD equals the
// control-plane's head, but we don't just so control always has a
// copy of all forks that clients had.
// NOTE(tom): We always send this RPC so control knows what TKA
// head we landed at.
head := b . tka . authority . Head ( )
b . mu . Unlock ( )
sendResp , err := b . tkaDoSyncSend ( ourNodeKey , toSendAUMs, false )
sendResp , err := b . tkaDoSyncSend ( ourNodeKey , head, toSendAUMs, false )
b . mu . Lock ( )
if err != nil {
return fmt . Errorf ( "send RPC: %v" , err )
@ -238,6 +237,21 @@ func (b *LocalBackend) tkaSyncLocked(ourNodeKey key.NodePublic) error {
return nil
}
// tkaApplyDisablementLocked checks a disablement secret and locally disables
// TKA (if correct). An error is returned if disablement failed.
//
// b.mu must be held & TKA must be initialized.
func ( b * LocalBackend ) tkaApplyDisablementLocked ( secret [ ] byte ) error {
if b . tka . authority . ValidDisablement ( secret ) {
if err := os . RemoveAll ( b . chonkPath ( ) ) ; err != nil {
return err
}
b . tka = nil
return nil
}
return errors . New ( "incorrect disablement secret" )
}
// chonkPath returns the absolute path to the directory in which TKA
// state (the 'tailchonk') is stored.
func ( b * LocalBackend ) chonkPath ( ) string {
@ -334,7 +348,7 @@ func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus {
// needing signatures is returned as a response.
// The Finish RPC submits signatures for all these nodes, at which point
// Control has everything it needs to atomically enable network lock.
func ( b * LocalBackend ) NetworkLockInit ( keys [ ] tka . Key ) error {
func ( b * LocalBackend ) NetworkLockInit ( keys [ ] tka . Key , disablementValues [ ] [ ] byte ) error {
if err := b . CanSupportNetworkLock ( ) ; err != nil {
return err
}
@ -355,8 +369,11 @@ func (b *LocalBackend) NetworkLockInit(keys []tka.Key) error {
// just in case something goes wrong.
_ , genesisAUM , err := tka . Create ( & tka . Mem { } , tka . State {
Keys : keys ,
// TODO(tom): Actually plumb a real disablement value.
DisablementSecrets : [ ] [ ] byte { bytes . Repeat ( [ ] byte { 1 } , 32 ) } ,
// TODO(tom): s/tka.State.DisablementSecrets/tka.State.DisablementValues
// This will center on consistent nomenclature:
// - DisablementSecret: value needed to disable.
// - DisablementValue: the KDF of the disablement secret, a public value.
DisablementSecrets : disablementValues ,
} , b . nlPrivKey )
if err != nil {
return fmt . Errorf ( "tka.Create: %v" , err )
@ -454,8 +471,9 @@ func (b *LocalBackend) NetworkLockModify(addKeys, removeKeys []tka.Key) (err err
}
ourNodeKey := b . prefs . Persist ( ) . PublicNodeKey ( )
head := b . tka . authority . Head ( )
b . mu . Unlock ( )
resp , err := b . tkaDoSyncSend ( ourNodeKey , aums, true )
resp , err := b . tkaDoSyncSend ( ourNodeKey , head, aums, true )
b . mu . Lock ( )
if err != nil {
return err
@ -474,6 +492,42 @@ func (b *LocalBackend) NetworkLockModify(addKeys, removeKeys []tka.Key) (err err
return nil
}
// NetworkLockDisable disables network-lock using the provided disablement secret.
func ( b * LocalBackend ) NetworkLockDisable ( secret [ ] byte ) error {
if err := b . CanSupportNetworkLock ( ) ; err != nil {
return err
}
var (
ourNodeKey key . NodePublic
head tka . AUMHash
err error
)
b . mu . Lock ( )
if b . prefs . Valid ( ) {
ourNodeKey = b . prefs . Persist ( ) . PublicNodeKey ( )
}
if b . tka == nil {
err = errNetworkLockNotActive
} else {
head = b . tka . authority . Head ( )
if ! b . tka . authority . ValidDisablement ( secret ) {
err = errors . New ( "incorrect disablement secret" )
}
}
b . mu . Unlock ( )
if err != nil {
return err
}
if ourNodeKey . IsZero ( ) {
return errors . New ( "no node-key: is tailscale logged in?" )
}
_ , err = b . tkaDoDisablement ( ourNodeKey , head , secret )
return err
}
func signNodeKey ( nodeInfo tailcfg . TKASignInfo , signer key . NLPrivate ) ( * tka . NodeKeySignature , error ) {
p , err := nodeInfo . NodePublic . MarshalBinary ( )
if err != nil {
@ -519,7 +573,7 @@ func (b *LocalBackend) tkaInitBegin(ourNodeKey key.NodePublic, aum tka.AUM) (*ta
return nil , fmt . Errorf ( "request returned (%d): %s" , res . StatusCode , string ( body ) )
}
a := new ( tailcfg . TKAInitBeginResponse )
err = json . NewDecoder ( res . Body ) . Decode ( a )
err = json . NewDecoder ( & io . LimitedReader { R : res . Body , N : 10 * 1024 * 1024 } ) . Decode ( a )
res . Body . Close ( )
if err != nil {
return nil , fmt . Errorf ( "decoding JSON: %w" , err )
@ -555,7 +609,7 @@ func (b *LocalBackend) tkaInitFinish(ourNodeKey key.NodePublic, nks map[tailcfg.
return nil , fmt . Errorf ( "request returned (%d): %s" , res . StatusCode , string ( body ) )
}
a := new ( tailcfg . TKAInitFinishResponse )
err = json . NewDecoder ( res . Body ) . Decode ( a )
err = json . NewDecoder ( & io . LimitedReader { R : res . Body , N : 1024 * 1024 } ) . Decode ( a )
res . Body . Close ( )
if err != nil {
return nil , fmt . Errorf ( "decoding JSON: %w" , err )
@ -603,7 +657,7 @@ func (b *LocalBackend) tkaFetchBootstrap(ourNodeKey key.NodePublic, head tka.AUM
return nil , fmt . Errorf ( "request returned (%d): %s" , res . StatusCode , string ( body ) )
}
a := new ( tailcfg . TKABootstrapResponse )
err = json . NewDecoder ( res . Body ) . Decode ( a )
err = json . NewDecoder ( & io . LimitedReader { R : res . Body , N : 1024 * 1024 } ) . Decode ( a )
res . Body . Close ( )
if err != nil {
return nil , fmt . Errorf ( "decoding JSON: %w" , err )
@ -664,7 +718,7 @@ func (b *LocalBackend) tkaDoSyncOffer(ourNodeKey key.NodePublic, offer tka.SyncO
return nil , fmt . Errorf ( "request returned (%d): %s" , res . StatusCode , string ( body ) )
}
a := new ( tailcfg . TKASyncOfferResponse )
err = json . NewDecoder ( res . Body ) . Decode ( a )
err = json . NewDecoder ( & io . LimitedReader { R : res . Body , N : 10 * 1024 * 1024 } ) . Decode ( a )
res . Body . Close ( )
if err != nil {
return nil , fmt . Errorf ( "decoding JSON: %w" , err )
@ -675,10 +729,16 @@ func (b *LocalBackend) tkaDoSyncOffer(ourNodeKey key.NodePublic, offer tka.SyncO
// tkaDoSyncSend sends a /machine/tka/sync/send RPC to the control plane
// over noise. This is the second of two RPCs implementing tka synchronization.
func ( b * LocalBackend ) tkaDoSyncSend ( ourNodeKey key . NodePublic , aums [ ] tka . AUM , interactive bool ) ( * tailcfg . TKASyncSendResponse , error ) {
func ( b * LocalBackend ) tkaDoSyncSend ( ourNodeKey key . NodePublic , head tka . AUMHash , aums [ ] tka . AUM , interactive bool ) ( * tailcfg . TKASyncSendResponse , error ) {
headBytes , err := head . MarshalText ( )
if err != nil {
return nil , fmt . Errorf ( "head.MarshalText: %w" , err )
}
sendReq := tailcfg . TKASyncSendRequest {
Version : tailcfg . CurrentCapabilityVersion ,
NodeKey : ourNodeKey ,
Head : string ( headBytes ) ,
MissingAUMs : make ( [ ] tkatype . MarshaledAUM , len ( aums ) ) ,
Interactive : interactive ,
}
@ -707,7 +767,49 @@ func (b *LocalBackend) tkaDoSyncSend(ourNodeKey key.NodePublic, aums []tka.AUM,
return nil , fmt . Errorf ( "request returned (%d): %s" , res . StatusCode , string ( body ) )
}
a := new ( tailcfg . TKASyncSendResponse )
err = json . NewDecoder ( res . Body ) . Decode ( a )
err = json . NewDecoder ( & io . LimitedReader { R : res . Body , N : 10 * 1024 * 1024 } ) . Decode ( a )
res . Body . Close ( )
if err != nil {
return nil , fmt . Errorf ( "decoding JSON: %w" , err )
}
return a , nil
}
func ( b * LocalBackend ) tkaDoDisablement ( ourNodeKey key . NodePublic , head tka . AUMHash , secret [ ] byte ) ( * tailcfg . TKADisableResponse , error ) {
headBytes , err := head . MarshalText ( )
if err != nil {
return nil , fmt . Errorf ( "head.MarshalText: %w" , err )
}
var req bytes . Buffer
if err := json . NewEncoder ( & req ) . Encode ( tailcfg . TKADisableRequest {
Version : tailcfg . CurrentCapabilityVersion ,
NodeKey : ourNodeKey ,
Head : string ( headBytes ) ,
DisablementSecret : secret ,
} ) ; err != nil {
return nil , fmt . Errorf ( "encoding request: %v" , err )
}
ctx , cancel := context . WithTimeout ( context . Background ( ) , time . Minute )
defer cancel ( )
req2 , err := http . NewRequestWithContext ( ctx , "GET" , "https://unused/machine/tka/disable" , & req )
if err != nil {
return nil , fmt . Errorf ( "req: %w" , err )
}
res , err := b . DoNoiseRequest ( req2 )
if err != nil {
return nil , fmt . Errorf ( "resp: %w" , err )
}
if res . StatusCode != 200 {
body , _ := io . ReadAll ( res . Body )
res . Body . Close ( )
return nil , fmt . Errorf ( "request returned (%d): %s" , res . StatusCode , string ( body ) )
}
a := new ( tailcfg . TKADisableResponse )
err = json . NewDecoder ( & io . LimitedReader { R : res . Body , N : 1024 * 1024 } ) . Decode ( a )
res . Body . Close ( )
if err != nil {
return nil , fmt . Errorf ( "decoding JSON: %w" , err )