From b97a6f15d1dbc9bbc74f36545e34b44922f20c8b Mon Sep 17 00:00:00 2001 From: chaosinthecrd Date: Wed, 1 Apr 2026 13:15:59 +0100 Subject: [PATCH] kube/authkey,kube/state,cmd/containerboot: preserve device_id across restarts Stop clearing device_id, device_fqdn, and device_ips on every startup (in SetInitialKeys and resetContainerbootState). This preserves the old device identity so the operator can read device_id during auth key reissue to delete the stale device, avoiding '-1','-2' hostname suffixes. Expand ClearReissueAuthKey to also clear device state and tsnet profile state (_machinekey, _current-profile, profile-*) during reissue, so the proxy starts fresh with the new auth key on next boot. --- kube/authkey/authkey.go | 19 ++++++++++++++++++- kube/state/state.go | 13 +------------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/kube/authkey/authkey.go b/kube/authkey/authkey.go index 7e56680f2..42ffcfc41 100644 --- a/kube/authkey/authkey.go +++ b/kube/authkey/authkey.go @@ -19,6 +19,7 @@ import ( "log" "time" + "tailscale.com/ipn" "tailscale.com/ipn/conffile" "tailscale.com/kube/kubeapi" "tailscale.com/kube/kubeclient" @@ -46,11 +47,27 @@ func SetReissueAuthKey(ctx context.Context, kc kubeclient.Client, stateSecretNam // ClearReissueAuthKey removes the reissue_authkey marker from the state Secret // to signal to the operator that we've successfully received the new key. func ClearReissueAuthKey(ctx context.Context, kc kubeclient.Client, stateSecretName string) error { + existing, err := kc.GetSecret(ctx, stateSecretName) + if err != nil { + return fmt.Errorf("error getting state secret: %w", err) + } + s := &kubeapi.Secret{ Data: map[string][]byte{ - kubetypes.KeyReissueAuthkey: nil, + kubetypes.KeyReissueAuthkey: nil, + kubetypes.KeyDeviceID: nil, + kubetypes.KeyDeviceFQDN: nil, + kubetypes.KeyDeviceIPs: nil, + string(ipn.MachineKeyStateKey): nil, + string(ipn.CurrentProfileStateKey): nil, + string(ipn.KnownProfilesStateKey): nil, }, } + + if profileKey := string(existing.Data["_current-profile"]); profileKey != "" { + s.Data[profileKey] = nil + } + return kc.StrategicMergePatchSecret(ctx, stateSecretName, s, fieldManager) } diff --git a/kube/state/state.go b/kube/state/state.go index ebedb2f72..a7f00b7f2 100644 --- a/kube/state/state.go +++ b/kube/state/state.go @@ -30,19 +30,8 @@ const ( keyDeviceFQDN = ipn.StateKey(kubetypes.KeyDeviceFQDN) ) -// SetInitialKeys sets Pod UID and cap ver and clears tailnet device state -// keys to help stop the operator using stale tailnet device state. +// SetInitialKeys sets Pod UID and cap ver. func SetInitialKeys(store ipn.StateStore, podUID string) error { - // Clear device state keys first so the operator knows if the pod UID - // matches, the other values are definitely not stale. - for _, key := range []ipn.StateKey{keyDeviceID, keyDeviceFQDN, keyDeviceIPs} { - if _, err := store.ReadState(key); err == nil { - if err := store.WriteState(key, nil); err != nil { - return fmt.Errorf("error writing %q to state store: %w", key, err) - } - } - } - if err := store.WriteState(keyPodUID, []byte(podUID)); err != nil { return fmt.Errorf("error writing pod UID to state store: %w", err) }