Browse Source

ipn/ipnlocal: refresh node key without blocking if cap enabled (#10529)

Updates tailscale/corp#16016

Signed-off-by: James Sanderson <[email protected]>
Co-authored-by: Maisem Ali <[email protected]>
James 'zofrex' Sanderson 2 years ago
parent
commit
10c595d962
3 changed files with 35 additions and 7 deletions
  1. 8 0
      control/controlknobs/controlknobs.go
  2. 21 6
      ipn/ipnlocal/local.go
  3. 6 1
      tailcfg/tailcfg.go

+ 8 - 0
control/controlknobs/controlknobs.go

@@ -64,6 +64,11 @@ type Knobs struct {
 	// LinuxForceNfTables is whether the node should use nftables for Linux
 	// netfiltering, unless overridden by the user.
 	LinuxForceNfTables atomic.Bool
+
+	// SeamlessKeyRenewal is whether to enable the alpha functionality of
+	// renewing node keys without breaking connections.
+	// http://go/seamless-key-renewal
+	SeamlessKeyRenewal atomic.Bool
 }
 
 // UpdateFromNodeAttributes updates k (if non-nil) based on the provided self
@@ -89,6 +94,7 @@ func (k *Knobs) UpdateFromNodeAttributes(selfNodeAttrs []tailcfg.NodeCapability,
 		silentDisco                   = has(tailcfg.NodeAttrSilentDisco)
 		forceIPTables                 = has(tailcfg.NodeAttrLinuxMustUseIPTables)
 		forceNfTables                 = has(tailcfg.NodeAttrLinuxMustUseNfTables)
+		seamlessKeyRenewal            = has(tailcfg.NodeAttrSeamlessKeyRenewal)
 	)
 
 	if has(tailcfg.NodeAttrOneCGNATEnable) {
@@ -109,6 +115,7 @@ func (k *Knobs) UpdateFromNodeAttributes(selfNodeAttrs []tailcfg.NodeCapability,
 	k.SilentDisco.Store(silentDisco)
 	k.LinuxForceIPTables.Store(forceIPTables)
 	k.LinuxForceNfTables.Store(forceNfTables)
+	k.SeamlessKeyRenewal.Store(seamlessKeyRenewal)
 }
 
 // AsDebugJSON returns k as something that can be marshalled with json.Marshal
@@ -130,5 +137,6 @@ func (k *Knobs) AsDebugJSON() map[string]any {
 		"SilentDisco":                   k.SilentDisco.Load(),
 		"LinuxForceIPTables":            k.LinuxForceIPTables.Load(),
 		"LinuxForceNfTables":            k.LinuxForceNfTables.Load(),
+		"SeamlessKeyRenewal":            k.SeamlessKeyRenewal.Load(),
 	}
 }

+ 21 - 6
ipn/ipnlocal/local.go

@@ -1074,9 +1074,11 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
 		b.blockEngineUpdates(false)
 	}
 
-	if st.LoginFinished() && wasBlocked {
-		// Auth completed, unblock the engine
-		b.blockEngineUpdates(false)
+	if st.LoginFinished() && (wasBlocked || b.seamlessRenewalEnabled()) {
+		if wasBlocked {
+			// Auth completed, unblock the engine
+			b.blockEngineUpdates(false)
+		}
 		b.authReconfig()
 		b.send(ipn.Notify{LoginFinished: &empty.Message{}})
 	}
@@ -1108,7 +1110,7 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
 		b.authURL = st.URL
 		b.authURLSticky = st.URL
 	}
-	if wasBlocked && st.LoginFinished() {
+	if (wasBlocked || b.seamlessRenewalEnabled()) && st.LoginFinished() {
 		// Interactive login finished successfully (URL visited).
 		// After an interactive login, the user always wants
 		// WantRunning.
@@ -2456,8 +2458,10 @@ func (b *LocalBackend) popBrowserAuthNow() {
 
 	b.logf("popBrowserAuthNow: url=%v", url != "")
 
-	b.blockEngineUpdates(true)
-	b.stopEngineAndWait()
+	if !b.seamlessRenewalEnabled() {
+		b.blockEngineUpdates(true)
+		b.stopEngineAndWait()
+	}
 	b.tellClientToBrowseToURL(url)
 	if b.State() == ipn.Running {
 		b.enterState(ipn.Starting)
@@ -4176,6 +4180,9 @@ func (b *LocalBackend) enterStateLockedOnEntry(newState ipn.State) {
 	switch newState {
 	case ipn.NeedsLogin:
 		systemd.Status("Needs login: %s", authURL)
+		if b.seamlessRenewalEnabled() {
+			break
+		}
 		b.blockEngineUpdates(true)
 		fallthrough
 	case ipn.Stopped:
@@ -5801,6 +5808,14 @@ func (b *LocalBackend) AdvertiseRoute(ipp netip.Prefix) error {
 	return err
 }
 
+// seamlessRenewalEnabled reports whether seamless key renewals are enabled
+// (i.e. we saw our self node with the SeamlessKeyRenewal attr in a netmap).
+// This enables beta functionality of renewing node keys without breaking
+// connections.
+func (b *LocalBackend) seamlessRenewalEnabled() bool {
+	return b.ControlKnobs().SeamlessKeyRenewal.Load()
+}
+
 var (
 	disallowedAddrs = []netip.Addr{
 		netip.MustParseAddr("::1"),

+ 6 - 1
tailcfg/tailcfg.go

@@ -124,7 +124,8 @@ type CapabilityVersion int
 //   - 81: 2023-11-17: MapResponse.PacketFilters (incremental packet filter updates)
 //   - 82: 2023-12-01: Client understands NodeAttrLinuxMustUseIPTables, NodeAttrLinuxMustUseNfTables, c2n /netfilter-kind
 //   - 83: 2023-12-18: Client understands DefaultAutoUpdate
-const CurrentCapabilityVersion CapabilityVersion = 83
+//   - 84: 2024-01-04: Client understands SeamlessKeyRenewal
+const CurrentCapabilityVersion CapabilityVersion = 84
 
 type StableID string
 
@@ -2190,6 +2191,10 @@ const (
 	// netfilter management.
 	// This cannot be set simultaneously with NodeAttrLinuxMustUseIPTables.
 	NodeAttrLinuxMustUseNfTables NodeCapability = "linux-netfilter?v=nftables"
+
+	// NodeAttrSeamlessKeyRenewal makes clients enable beta functionality
+	// of renewing node keys without breaking connections.
+	NodeAttrSeamlessKeyRenewal NodeCapability = "seamless-key-renewal"
 )
 
 // SetDNSRequest is a request to add a DNS record.