tailscale/tailscale · error

--hardware-attestation cannot be used with portable state st

Error message

--hardware-attestation cannot be used with portable state stores (kube:, arn:) because TPM-bound keys cannot be migrated between machines

What it means

canUseHardwareAttestation's second rule (tailscaled.go:994-1000): attestation keys are TPM-bound, so --hardware-attestation is refused when --state points at a portable store — any known provider prefix (kube:, arn:, etc., via store.HasKnownProviderPrefix) other than the TPM: prefix, per isPortableStore. The error is a deliberate fail-fast to prevent a state blob that could never be restored on different hardware.

Source

Thrown at cmd/tailscaled/tailscaled.go:998

	case !args.encryptState.set:
		policyEncrypt, _ := policyclient.Get().GetBoolean(pkey.EncryptState, false)
		if err := canEncryptState(); policyEncrypt && err == nil {
			args.encryptState.v = true
		}
	}
}

// canUseHardwareAttestation returns an error if hardware attestation can't be
// enabled, either due to availability or compatibility with other settings.
func canUseHardwareAttestation() error {
	if _, err := key.NewEmptyHardwareAttestationKey(); err == key.ErrUnsupported {
		return errors.New("--hardware-attestation is not supported on this platform or in this build of tailscaled")
	}
	// Hardware attestation keys are TPM-bound and cannot be migrated between
	// machines. Disable when using portable state stores like kube: or arn:
	// where state may be loaded on a different machine.
	if args.statepath != "" && isPortableStore(args.statepath) {
		return errors.New("--hardware-attestation cannot be used with portable state stores (kube:, arn:) because TPM-bound keys cannot be migrated between machines")
	}
	return nil
}

// isPortableStore reports whether the given state path refers to a portable
// state store where state may be loaded on different machines.
// All stores apart from file store and TPM store are portable.
func isPortableStore(path string) bool {
	if store.HasKnownProviderPrefix(path) && !strings.HasPrefix(path, store.TPMPrefix) {
		return true
	}
	// In most cases Kubernetes Secret and AWS SSM stores would have been caught
	// by the earlier check - but that check relies on those stores having been
	// registered. This additional check is here to ensure that if we ever
	// produce a faulty build that failed to register some store, users who
	// upgraded to that don't get hardware keys generated.
	if strings.HasPrefix(path, "kube:") || strings.HasPrefix(path, "arn:") {
		return true

View on GitHub (pinned to cfe32b8be6)

Solutions

  1. Use a local file state (--state=/var/lib/tailscale/tailscaled.state) or the TPM store (TPM: prefix) together with --hardware-attestation
  2. If the portable store is required, drop --hardware-attestation
  3. Re-register the node on the new store rather than trying to combine both features

Example fix

# before
tailscaled --hardware-attestation --state=kube:ts-ns/tailscaled-state
# error: --hardware-attestation cannot be used with portable state stores (kube:, arn:)

# after
tailscaled --hardware-attestation --state=/var/lib/tailscale/tailscaled.state
Defensive patterns

Strategy: validation

Validate before calling

// Mirror canUseHardwareAttestation's rule before combining flags:
import "tailscale.com/ipn/store"

func attestationCompatible(statePath string) bool {
    if statePath == "" {
        return true
    }
    return !store.HasKnownProviderPrefix(statePath) || strings.HasPrefix(statePath, store.TPMPrefix)
}

if wantAttestation && !attestationCompatible(cfg.State) {
    return errors.New("pick TPM: or file state, or drop --hardware-attestation")
}

Try / catch

if err := start(); err != nil {
    if strings.Contains(err.Error(), "portable state stores") {
        // choose: local file + attestation, or provider store without attestation
        return reconfigure(state: localFile, attestation: true)
    }
    return err
}

Prevention

When it happens

Trigger: `tailscaled --hardware-attestation --state=kube:tailnet/tailscaled` or `--state=arn:aws:ssm:...`; also Kubernetes deployments enabling attestation while storing state in Secrets/SSM per the sidecar pattern.

Common situations: K8s sidecar or AWS deployments copying state in object stores; users wanting 'HA' state via kube: then adding attestation; migration from file state to a provider store without re-evaluating attestation flags.

Related errors


AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15). Data as JSON: /api/errors/d3bfb6929b6c23e9. Report an issue: GitHub.