ipn/ipnlocal: don't fail profile unmarshal due to attestation keys (#18335)

Soft-fail on initial unmarshal and try again, ignoring the
AttestationKey. This helps in cases where something about the
attestation key storage (usually a TPM) is messed up. The old key will
be lost, but at least the node can start again.

Updates #18302
Updates #15830

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
Andrew Lytvynov
2026-01-05 16:58:59 -08:00
committed by GitHub
parent 39a61888b8
commit 2e77b75e96
2 changed files with 79 additions and 7 deletions
+38
View File
@@ -4,6 +4,7 @@
package ipnlocal
import (
"errors"
"fmt"
"os/user"
"strconv"
@@ -1147,3 +1148,40 @@ func TestProfileStateChangeCallback(t *testing.T) {
})
}
}
func TestProfileBadAttestationKey(t *testing.T) {
store := new(mem.Store)
pm, err := newProfileManagerWithGOOS(store, t.Logf, health.NewTracker(eventbustest.NewBus(t)), "linux")
if err != nil {
t.Fatal(err)
}
fk := new(failingHardwareAttestationKey)
pm.newEmptyHardwareAttestationKey = func() (key.HardwareAttestationKey, error) {
return fk, nil
}
sk := ipn.StateKey(t.Name())
if err := pm.store.WriteState(sk, []byte(`{"Config": {"AttestationKey": {}}}`)); err != nil {
t.Fatal(err)
}
prefs, err := pm.loadSavedPrefs(sk)
if err != nil {
t.Fatal(err)
}
ak := prefs.Persist().AsStruct().AttestationKey
if _, ok := ak.(noopAttestationKey); !ok {
t.Errorf("loaded attestation key of type %T, want noopAttestationKey", ak)
}
if !fk.unmarshalCalled {
t.Error("UnmarshalJSON was not called on failingHardwareAttestationKey")
}
}
type failingHardwareAttestationKey struct {
noopAttestationKey
unmarshalCalled bool
}
func (k *failingHardwareAttestationKey) UnmarshalJSON([]byte) error {
k.unmarshalCalled = true
return errors.New("failed to unmarshal attestation key!")
}