ipn/ipnlocal,tailcfg: introduce capability to gate TKA init paths

Previously, `TAILSCALE_USE_WIP_CODE` was needed to hit a bunch of the TKA paths. With
this change:

 - Enablement codepaths (NetworkLockInit) and initialization codepaths (tkaBootstrapFromGenesisLocked via tkaSyncIfNeeded)
   require either the WIP envknob or CapabilityTailnetLockAlpha.
 - Normal operation codepaths (tkaSyncIfNeeded, tkaFilterNetmapLocked) require TKA to be initialized, or either-or the
   envknob / capability.
 - Auxillary commands (ie: changing tka keys) require TKA to be initialized.

The end result is that it shouldn't be possible to initialize TKA (or subsequently use any of its features) without being
sent the capability or setting the envknob on tailscaled yourself.

I've also pulled out a bunch of unnecessary checks for CanSupportNetworkLock().

Signed-off-by: Tom DNetto <tom@tailscale.com>
This commit is contained in:
Tom DNetto 2022-11-30 10:34:59 -08:00 committed by Tom
parent 9a80b8fb10
commit f1ab11e961
3 changed files with 36 additions and 22 deletions

View File

@ -162,6 +162,7 @@ type LocalBackend struct {
tka *tkaState tka *tkaState
state ipn.State state ipn.State
capFileSharing bool // whether netMap contains the file sharing capability capFileSharing bool // whether netMap contains the file sharing capability
capTailnetLock bool // whether netMap contains the tailnet lock capability
// hostinfo is mutated in-place while mu is held. // hostinfo is mutated in-place while mu is held.
hostinfo *tailcfg.Hostinfo hostinfo *tailcfg.Hostinfo
// netMap is not mutated in-place once set. // netMap is not mutated in-place once set.
@ -869,6 +870,8 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
} }
// Prefs will be written out; this is not safe unless locked or cloned. // Prefs will be written out; this is not safe unless locked or cloned.
if st.NetMap != nil { if st.NetMap != nil {
b.capTailnetLock = hasCapability(st.NetMap, tailcfg.CapabilityTailnetLockAlpha)
b.mu.Unlock() // respect locking rules for tkaSyncIfNeeded b.mu.Unlock() // respect locking rules for tkaSyncIfNeeded
if err := b.tkaSyncIfNeeded(st.NetMap, prefs.View()); err != nil { if err := b.tkaSyncIfNeeded(st.NetMap, prefs.View()); err != nil {
b.logf("[v1] TKA sync error: %v", err) b.logf("[v1] TKA sync error: %v", err)

View File

@ -46,13 +46,21 @@ type tkaState struct {
filtered []ipnstate.TKAFilteredPeer filtered []ipnstate.TKAFilteredPeer
} }
// permitTKAInitLocked returns true if tailnet lock initialization may
// occur.
// b.mu must be held.
func (b *LocalBackend) permitTKAInitLocked() bool {
return envknob.UseWIPCode() || b.capTailnetLock
}
// tkaFilterNetmapLocked checks the signatures on each node key, dropping // tkaFilterNetmapLocked checks the signatures on each node key, dropping
// nodes from the netmap whose signature does not verify. // nodes from the netmap whose signature does not verify.
// //
// b.mu must be held. // b.mu must be held.
func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) { func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) {
if !envknob.UseWIPCode() { // TODO(tom): Remove this guard for 1.35 and later.
return // Feature-flag till network-lock is in Alpha. if b.tka == nil && !b.permitTKAInitLocked() {
return
} }
if b.tka == nil { if b.tka == nil {
return // TKA not enabled. return // TKA not enabled.
@ -61,7 +69,7 @@ func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) {
var toDelete map[int]bool // peer index => true var toDelete map[int]bool // peer index => true
for i, p := range nm.Peers { for i, p := range nm.Peers {
if p.UnsignedPeerAPIOnly { if p.UnsignedPeerAPIOnly {
// Not subject to TKA. // Not subject to tailnet lock.
continue continue
} }
if len(p.KeySignature) == 0 { if len(p.KeySignature) == 0 {
@ -123,18 +131,18 @@ func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) {
// tkaSyncIfNeeded immediately takes b.takeSyncLock which is held throughout, // tkaSyncIfNeeded immediately takes b.takeSyncLock which is held throughout,
// and may take b.mu as required. // and may take b.mu as required.
func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap, prefs ipn.PrefsView) error { func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap, prefs ipn.PrefsView) error {
if !envknob.UseWIPCode() {
// If the feature flag is not enabled, pretend we don't exist.
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. b.tkaSyncLock.Lock() // take tkaSyncLock to make this function an exclusive section.
defer b.tkaSyncLock.Unlock() defer b.tkaSyncLock.Unlock()
b.mu.Lock() // take mu to protect access to synchronized fields. b.mu.Lock() // take mu to protect access to synchronized fields.
defer b.mu.Unlock() defer b.mu.Unlock()
// TODO(tom): Remove this guard for 1.35 and later.
if b.tka == nil && !b.permitTKAInitLocked() {
return nil
}
b.logf("tkaSyncIfNeeded: enabled=%v, head=%v", nm.TKAEnabled, nm.TKAHead)
ourNodeKey := prefs.Persist().PublicNodeKey() ourNodeKey := prefs.Persist().PublicNodeKey()
isEnabled := b.tka != nil isEnabled := b.tka != nil
@ -352,10 +360,6 @@ func (b *LocalBackend) tkaBootstrapFromGenesisLocked(g tkatype.MarshaledAUM, per
// CanSupportNetworkLock returns nil if tailscaled is able to operate // CanSupportNetworkLock returns nil if tailscaled is able to operate
// a local tailnet key authority (and hence enforce network lock). // a local tailnet key authority (and hence enforce network lock).
func (b *LocalBackend) CanSupportNetworkLock() error { func (b *LocalBackend) CanSupportNetworkLock() error {
if !envknob.UseWIPCode() {
return errors.New("this feature is not yet complete, a later release may support this functionality")
}
if b.tka != nil { if b.tka != nil {
// If the TKA is being used, it is supported. // If the TKA is being used, it is supported.
return nil return nil
@ -453,6 +457,13 @@ func (b *LocalBackend) NetworkLockInit(keys []tka.Key, disablementValues [][]byt
var ourNodeKey key.NodePublic var ourNodeKey key.NodePublic
var nlPriv key.NLPrivate var nlPriv key.NLPrivate
b.mu.Lock() b.mu.Lock()
// TODO(tom): Remove this guard for 1.35 and later.
if !b.permitTKAInitLocked() {
b.mu.Unlock()
return errors.New("this feature is not yet complete, a later release may support this functionality")
}
if p := b.pm.CurrentPrefs(); p.Valid() && p.Persist().Valid() && !p.Persist().PrivateNodeKey().IsZero() { if p := b.pm.CurrentPrefs(); p.Valid() && p.Persist().Valid() && !p.Persist().PrivateNodeKey().IsZero() {
ourNodeKey = p.Persist().PublicNodeKey() ourNodeKey = p.Persist().PublicNodeKey()
nlPriv = p.Persist().NetworkLockKey() nlPriv = p.Persist().NetworkLockKey()
@ -631,9 +642,6 @@ func (b *LocalBackend) NetworkLockModify(addKeys, removeKeys []tka.Key) (err err
return errors.New("no node-key: is tailscale logged in?") return errors.New("no node-key: is tailscale logged in?")
} }
if err := b.CanSupportNetworkLock(); err != nil {
return err
}
var nlPriv key.NLPrivate var nlPriv key.NLPrivate
if p := b.pm.CurrentPrefs(); p.Valid() && p.Persist().Valid() { if p := b.pm.CurrentPrefs(); p.Valid() && p.Persist().Valid() {
nlPriv = p.Persist().NetworkLockKey() nlPriv = p.Persist().NetworkLockKey()
@ -690,10 +698,6 @@ func (b *LocalBackend) NetworkLockModify(addKeys, removeKeys []tka.Key) (err err
// NetworkLockDisable disables network-lock using the provided disablement secret. // NetworkLockDisable disables network-lock using the provided disablement secret.
func (b *LocalBackend) NetworkLockDisable(secret []byte) error { func (b *LocalBackend) NetworkLockDisable(secret []byte) error {
if err := b.CanSupportNetworkLock(); err != nil {
return err
}
var ( var (
ourNodeKey key.NodePublic ourNodeKey key.NodePublic
head tka.AUMHash head tka.AUMHash

View File

@ -87,7 +87,8 @@ type CapabilityVersion int
// - 48: 2022-11-02: Node.UnsignedPeerAPIOnly // - 48: 2022-11-02: Node.UnsignedPeerAPIOnly
// - 49: 2022-11-03: Client understands EarlyNoise // - 49: 2022-11-03: Client understands EarlyNoise
// - 50: 2022-11-14: Client understands CapabilityIngress // - 50: 2022-11-14: Client understands CapabilityIngress
const CurrentCapabilityVersion CapabilityVersion = 50 // - 51: 2022-11-30: Client understands CapabilityTailnetLockAlpha
const CurrentCapabilityVersion CapabilityVersion = 51
type StableID string type StableID string
@ -1707,6 +1708,12 @@ const (
CapabilityDataPlaneAuditLogs = "https://tailscale.com/cap/data-plane-audit-logs" // feature enabled CapabilityDataPlaneAuditLogs = "https://tailscale.com/cap/data-plane-audit-logs" // feature enabled
CapabilityDebug = "https://tailscale.com/cap/debug" // exposes debug endpoints over the PeerAPI CapabilityDebug = "https://tailscale.com/cap/debug" // exposes debug endpoints over the PeerAPI
// CapabilityTailnetLockAlpha indicates the node is in the tailnet lock alpha,
// and initialization of tailnet lock may proceed.
//
// TODO(tom): Remove this for 1.35 and later.
CapabilityTailnetLockAlpha = "https://tailscale.com/cap/tailnet-lock-alpha"
// Inter-node capabilities as specified in the MapResponse.PacketFilter[].CapGrants. // Inter-node capabilities as specified in the MapResponse.PacketFilter[].CapGrants.
// CapabilityFileSharingTarget grants the current node the ability to send // CapabilityFileSharingTarget grants the current node the ability to send