{"record":{"id":"d3bfb6929b6c23e9","repo":"tailscale/tailscale","slug":"hardware-attestation-cannot-be-used-with-portabl","errorCode":null,"errorMessage":"--hardware-attestation cannot be used with portable state stores (kube:, arn:) because TPM-bound keys cannot be migrated between machines","messagePattern":"--hardware-attestation cannot be used with portable state stores \\(kube:, arn:\\) because TPM-bound keys cannot be migrated between machines","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/tailscaled/tailscaled.go","lineNumber":998,"sourceCode":"\tcase !args.encryptState.set:\n\t\tpolicyEncrypt, _ := policyclient.Get().GetBoolean(pkey.EncryptState, false)\n\t\tif err := canEncryptState(); policyEncrypt && err == nil {\n\t\t\targs.encryptState.v = true\n\t\t}\n\t}\n}\n\n// canUseHardwareAttestation returns an error if hardware attestation can't be\n// enabled, either due to availability or compatibility with other settings.\nfunc canUseHardwareAttestation() error {\n\tif _, err := key.NewEmptyHardwareAttestationKey(); err == key.ErrUnsupported {\n\t\treturn errors.New(\"--hardware-attestation is not supported on this platform or in this build of tailscaled\")\n\t}\n\t// Hardware attestation keys are TPM-bound and cannot be migrated between\n\t// machines. Disable when using portable state stores like kube: or arn:\n\t// where state may be loaded on a different machine.\n\tif args.statepath != \"\" && isPortableStore(args.statepath) {\n\t\treturn errors.New(\"--hardware-attestation cannot be used with portable state stores (kube:, arn:) because TPM-bound keys cannot be migrated between machines\")\n\t}\n\treturn nil\n}\n\n// isPortableStore reports whether the given state path refers to a portable\n// state store where state may be loaded on different machines.\n// All stores apart from file store and TPM store are portable.\nfunc isPortableStore(path string) bool {\n\tif store.HasKnownProviderPrefix(path) && !strings.HasPrefix(path, store.TPMPrefix) {\n\t\treturn true\n\t}\n\t// In most cases Kubernetes Secret and AWS SSM stores would have been caught\n\t// by the earlier check - but that check relies on those stores having been\n\t// registered. This additional check is here to ensure that if we ever\n\t// produce a faulty build that failed to register some store, users who\n\t// upgraded to that don't get hardware keys generated.\n\tif strings.HasPrefix(path, \"kube:\") || strings.HasPrefix(path, \"arn:\") {\n\t\treturn true","sourceCodeStart":980,"sourceCodeEnd":1016,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/cmd/tailscaled/tailscaled.go#L980-L1016","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","solutions":["Use a local file state (--state=/var/lib/tailscale/tailscaled.state) or the TPM store (TPM: prefix) together with --hardware-attestation","If the portable store is required, drop --hardware-attestation","Re-register the node on the new store rather than trying to combine both features"],"exampleFix":"# before\ntailscaled --hardware-attestation --state=kube:ts-ns/tailscaled-state\n# error: --hardware-attestation cannot be used with portable state stores (kube:, arn:)\n\n# after\ntailscaled --hardware-attestation --state=/var/lib/tailscale/tailscaled.state","handlingStrategy":"validation","validationCode":"// Mirror canUseHardwareAttestation's rule before combining flags:\nimport \"tailscale.com/ipn/store\"\n\nfunc attestationCompatible(statePath string) bool {\n    if statePath == \"\" {\n        return true\n    }\n    return !store.HasKnownProviderPrefix(statePath) || strings.HasPrefix(statePath, store.TPMPrefix)\n}\n\nif wantAttestation && !attestationCompatible(cfg.State) {\n    return errors.New(\"pick TPM: or file state, or drop --hardware-attestation\")\n}","typeGuard":null,"tryCatchPattern":"if err := start(); err != nil {\n    if strings.Contains(err.Error(), \"portable state stores\") {\n        // choose: local file + attestation, or provider store without attestation\n        return reconfigure(state: localFile, attestation: true)\n    }\n    return err\n}","preventionTips":["Never template --state=kube:/arn: into hosts that also carry --hardware-attestation","Encode the mutual exclusion in config management (schema validation, not runtime)","Remember TPM: store is the only provider prefix compatible with attestation"],"tags":["tailscaled","tpm","attestation","state-store","kubernetes","aws"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}