Browse Source

control, ipn, tailcfg: enable seamless key renewal by default

Previously, seamless key renewal was an opt-in feature.  Customers had
to set a `seamless-key-renewal` node attribute in their policy file.

This patch enables seamless key renewal by default for all clients.

It includes a `disable-seamless-key-renewal` node attribute we can set
in Control, so we can manage the rollout and disable the feature for
clients with known bugs.  This new attribute makes the feature opt-out.

Updates tailscale/corp#31479

Signed-off-by: Alex Chan <[email protected]>
Alex Chan 5 months ago
parent
commit
cd153aa644
3 changed files with 38 additions and 10 deletions
  1. 19 3
      control/controlknobs/controlknobs.go
  2. 4 4
      ipn/ipnlocal/local.go
  3. 15 3
      tailcfg/tailcfg.go

+ 19 - 3
control/controlknobs/controlknobs.go

@@ -62,8 +62,9 @@ type Knobs struct {
 	// netfiltering, unless overridden by the user.
 	LinuxForceNfTables atomic.Bool
 
-	// SeamlessKeyRenewal is whether to enable the alpha functionality of
-	// renewing node keys without breaking connections.
+	// SeamlessKeyRenewal is whether to renew node keys without breaking connections.
+	// This is enabled by default in 1.90 and later, but we but we can remotely disable
+	// it from the control plane if there's a problem.
 	// http://go/seamless-key-renewal
 	SeamlessKeyRenewal atomic.Bool
 
@@ -128,6 +129,7 @@ func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) {
 		forceIPTables                        = has(tailcfg.NodeAttrLinuxMustUseIPTables)
 		forceNfTables                        = has(tailcfg.NodeAttrLinuxMustUseNfTables)
 		seamlessKeyRenewal                   = has(tailcfg.NodeAttrSeamlessKeyRenewal)
+		disableSeamlessKeyRenewal            = has(tailcfg.NodeAttrDisableSeamlessKeyRenewal)
 		probeUDPLifetime                     = has(tailcfg.NodeAttrProbeUDPLifetime)
 		appCStoreRoutes                      = has(tailcfg.NodeAttrStoreAppCRoutes)
 		userDialUseRoutes                    = has(tailcfg.NodeAttrUserDialUseRoutes)
@@ -154,7 +156,6 @@ func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) {
 	k.SilentDisco.Store(silentDisco)
 	k.LinuxForceIPTables.Store(forceIPTables)
 	k.LinuxForceNfTables.Store(forceNfTables)
-	k.SeamlessKeyRenewal.Store(seamlessKeyRenewal)
 	k.ProbeUDPLifetime.Store(probeUDPLifetime)
 	k.AppCStoreRoutes.Store(appCStoreRoutes)
 	k.UserDialUseRoutes.Store(userDialUseRoutes)
@@ -162,6 +163,21 @@ func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) {
 	k.DisableLocalDNSOverrideViaNRPT.Store(disableLocalDNSOverrideViaNRPT)
 	k.DisableCaptivePortalDetection.Store(disableCaptivePortalDetection)
 	k.DisableSkipStatusQueue.Store(disableSkipStatusQueue)
+
+	// If both attributes are present, then "enable" should win.  This reflects
+	// the history of seamless key renewal.
+	//
+	// Before 1.90, seamless was a private alpha, opt-in feature.  Devices would
+	// only seamless do if customers opted in using the seamless renewal attr.
+	//
+	// In 1.90 and later, seamless is the default behaviour, and devices will use
+	// seamless unless explicitly told not to by control (e.g. if we discover
+	// a bug and want clients to use the prior behaviour).
+	//
+	// If a customer has opted in to the pre-1.90 seamless implementation, we
+	// don't want to switch it off for them -- we only want to switch it off for
+	// devices that haven't opted in.
+	k.SeamlessKeyRenewal.Store(seamlessKeyRenewal || !disableSeamlessKeyRenewal)
 }
 
 // AsDebugJSON returns k as something that can be marshalled with json.Marshal

+ 4 - 4
ipn/ipnlocal/local.go

@@ -7420,10 +7420,10 @@ func (b *LocalBackend) readRouteInfoLocked() (*appc.RouteInfo, error) {
 	return ri, nil
 }
 
-// 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.
+// seamlessRenewalEnabled reports whether seamless key renewals are enabled.
+//
+// As of 2025-09-11, this is the default behaviour unless nodes receive
+// [tailcfg.NodeAttrDisableSeamlessKeyRenewal] in their netmap.
 func (b *LocalBackend) seamlessRenewalEnabled() bool {
 	return b.ControlKnobs().SeamlessKeyRenewal.Load()
 }

+ 15 - 3
tailcfg/tailcfg.go

@@ -170,7 +170,8 @@ type CapabilityVersion int
 //   - 123: 2025-07-28: fix deadlock regression from cryptokey routing change (issue #16651)
 //   - 124: 2025-08-08: removed NodeAttrDisableMagicSockCryptoRouting support, crypto routing is now mandatory
 //   - 125: 2025-08-11: dnstype.Resolver adds UseWithExitNode field.
-const CurrentCapabilityVersion CapabilityVersion = 125
+//   - 126: 2025-09-17: Client uses seamless key renewal unless disabled by control (tailscale/corp#31479)
+const CurrentCapabilityVersion CapabilityVersion = 126
 
 // ID is an integer ID for a user, node, or login allocated by the
 // control plane.
@@ -2530,8 +2531,19 @@ const (
 	// 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.
+	// NodeAttrDisableSeamlessKeyRenewal disables seamless key renewal, which is
+	// enabled by default in clients as of 2025-09-17 (1.90 and later).
+	//
+	// We will use this attribute to manage the rollout, and disable seamless in
+	// clients with known bugs.
+	// http://go/seamless-key-renewal
+	NodeAttrDisableSeamlessKeyRenewal NodeCapability = "disable-seamless-key-renewal"
+
+	// NodeAttrSeamlessKeyRenewal was used to opt-in to seamless key renewal
+	// during its private alpha.
+	//
+	// Deprecated: NodeAttrSeamlessKeyRenewal is deprecated as of CapabilityVersion 126,
+	// because seamless key renewal is now enabled by default.
 	NodeAttrSeamlessKeyRenewal NodeCapability = "seamless-key-renewal"
 
 	// NodeAttrProbeUDPLifetime makes the client probe UDP path lifetime at the