From 3e55d3b11f1331cf872db7ddff994ef9894e34df Mon Sep 17 00:00:00 2001 From: chaosinthecrd Date: Thu, 2 Apr 2026 14:07:50 +0100 Subject: [PATCH] kube/authkey,kube/state,cmd/containerboot: preserve device_id across restarts Stop clearing device_id, device_fqdn, and device_ips from state on startup. These keys are now preserved across restarts so the operator can track device identity. Expand ClearReissueAuthKey to clear device state and tailscaled profile data when performing a full auth key reissue. Signed-off-by: chaosinthecrd --- kube/authkey/authkey.go | 19 ++++++++++++++++++- kube/authkey/authkey_test.go | 19 ++++++++++++++++++- kube/state/state.go | 13 +------------ 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/kube/authkey/authkey.go b/kube/authkey/authkey.go index 5698f55f0..c56301b12 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, fieldManager 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/authkey/authkey_test.go b/kube/authkey/authkey_test.go index bb01b6a44..268bc46d6 100644 --- a/kube/authkey/authkey_test.go +++ b/kube/authkey/authkey_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "tailscale.com/ipn" "tailscale.com/kube/kubeapi" "tailscale.com/kube/kubeclient" "tailscale.com/kube/kubetypes" @@ -42,6 +43,15 @@ func TestSetReissueAuthKey(t *testing.T) { func TestClearReissueAuthKey(t *testing.T) { var patched map[string][]byte kc := &kubeclient.FakeClient{ + GetSecretImpl: func(ctx context.Context, name string) (*kubeapi.Secret, error) { + return &kubeapi.Secret{ + Data: map[string][]byte{ + "_current-profile": []byte("profile-abc1"), + "profile-abc1": []byte("some-profile-data"), + "_machinekey": []byte("machine-key-data"), + }, + }, nil + }, StrategicMergePatchSecretImpl: func(ctx context.Context, name string, secret *kubeapi.Secret, _ string) error { patched = secret.Data return nil @@ -54,7 +64,14 @@ func TestClearReissueAuthKey(t *testing.T) { } want := 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, + "profile-abc1": nil, } if diff := cmp.Diff(want, patched); diff != "" { t.Errorf("ClearReissueAuthKey() mismatch (-want +got):\n%s", diff) 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) }