From 68617bb82e3205d9b6eb0a90589e0f3c9033a12f Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Mon, 5 Jan 2026 17:05:00 -0800 Subject: [PATCH] cmd/tailscaled: disable state encryption / attestation by default (#18336) TPM-based features have been incredibly painful due to the heterogeneous devices in the wild, and many situations in which the TPM "changes" (is reset or replaced). All of this leads to a lot of customer issues. We hoped to iron out all the kinks and get all users to benefit from state encryption and hardware attestation without manually opting in, but the long tail of kinks is just too long. This change disables TPM-based features on Windows and Linux by default. Node state should get auto-decrypted on update, and old attestation keys will be removed. There's also tailscaled-on-macOS, but it won't have a TPM or Keychain bindings anyway. Updates #18302 Updates #15830 Signed-off-by: Andrew Lytvynov --- cmd/tailscaled/tailscaled.go | 18 ++++-------------- ipn/ipnlocal/local.go | 8 +++++++- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 5c8611c8e..6abe0cb79 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -910,13 +910,8 @@ func handleTPMFlags() { log.Fatalf("--hardware-attestation is not supported on this platform or in this build of tailscaled") } case !args.hardwareAttestation.set: - policyHWAttestation, _ := policyclient.Get().GetBoolean(pkey.HardwareAttestation, feature.HardwareAttestationAvailable()) - if !policyHWAttestation { - break - } - if feature.TPMAvailable() { - args.hardwareAttestation.v = true - } + policyHWAttestation, _ := policyclient.Get().GetBoolean(pkey.HardwareAttestation, false) + args.hardwareAttestation.v = policyHWAttestation } switch { @@ -927,13 +922,8 @@ func handleTPMFlags() { log.Fatal(err) } case !args.encryptState.set: - policyEncrypt, _ := policyclient.Get().GetBoolean(pkey.EncryptState, feature.TPMAvailable()) - if !policyEncrypt { - // Default disabled, no need to validate. - return - } - // Default enabled if available. - if err := canEncryptState(); err == nil { + policyEncrypt, _ := policyclient.Get().GetBoolean(pkey.EncryptState, false) + if err := canEncryptState(); policyEncrypt && err == nil { args.encryptState.v = true } } diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index ef89af5af..cebb96130 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -2507,7 +2507,7 @@ func (b *LocalBackend) startLocked(opts ipn.Options) error { // neither UpdatePrefs or reconciliation should change Persist newPrefs.Persist = b.pm.CurrentPrefs().Persist().AsStruct() - if buildfeatures.HasTPM { + if buildfeatures.HasTPM && b.HardwareAttested() { if genKey, ok := feature.HookGenerateAttestationKeyIfEmpty.GetOk(); ok { newKey, err := genKey(newPrefs.Persist, logf) if err != nil { @@ -2519,6 +2519,12 @@ func (b *LocalBackend) startLocked(opts ipn.Options) error { } } } + // Remove any existing attestation key if HardwareAttested is false. + if !b.HardwareAttested() && newPrefs.Persist != nil && newPrefs.Persist.AttestationKey != nil && !newPrefs.Persist.AttestationKey.IsZero() { + newPrefs.Persist.AttestationKey = nil + prefsChanged = true + prefsChangedWhy = append(prefsChangedWhy, "removeAttestationKey") + } if prefsChanged { logf("updated prefs: %v, reason: %v", newPrefs.Pretty(), prefsChangedWhy)