{"record":{"id":"5f63cfcfab30af4e","repo":"kubernetes/kops","slug":"request-s-keypair-id-q-for-s-didn-t-match-server","errorCode":null,"errorMessage":"request's keypair ID %q for %s didn't match server's %q","messagePattern":"request's keypair ID %q for (.+?) didn't match server's %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/kops-controller/pkg/server/server.go","lineNumber":345,"sourceCode":"\t\t}\n\t\tissueReq.AlternateNames = id.CertificateNames\n\t\tissueReq.Type = \"server\"\n\tcase \"kube-proxy\":\n\t\tissueReq.Subject = pkix.Name{\n\t\t\tCommonName: rbac.KubeProxy,\n\t\t}\n\tcase \"kube-router\":\n\t\tissueReq.Subject = pkix.Name{\n\t\t\tCommonName: rbac.KubeRouter,\n\t\t}\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"unexpected key name\")\n\t}\n\n\t// This field was added to the protocol in kOps 1.22.\n\tif len(keypairIDs) > 0 {\n\t\tif keypairIDs[issueReq.Signer] != s.keypairIDs[issueReq.Signer] {\n\t\t\treturn \"\", fmt.Errorf(\"request's keypair ID %q for %s didn't match server's %q\", keypairIDs[issueReq.Signer], issueReq.Signer, s.keypairIDs[issueReq.Signer])\n\t\t}\n\t}\n\n\tcert, _, _, err := pki.IssueCert(ctx, issueReq, s.keystore)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"issuing certificate: %v\", err)\n\t}\n\n\treturn cert.AsString()\n}\n\n// recovery is responsible for ensuring we don't exit on a panic.\nfunc recovery(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {\n\t\tdefer func() {\n\t\t\tif err := recover(); err != nil {\n\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n","sourceCodeStart":327,"sourceCodeEnd":363,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/cmd/kops-controller/pkg/server/server.go#L327-L363","documentation":"Since kOps 1.22, bootstrap requests may carry keypairIDs so the server can detect serving a CA that the node does not know about. If the request's keypair ID for the signer differs from the server's current one, issueCert fails rather than issuing a cert against a CA the node would not trust.","triggerScenarios":"Node bootstraps with cached keypair IDs while the server has rotated the CA / keypair for that signer (e.g. etcd-clients-ca, kubernetes-ca), so the IDs mismatch.","commonSituations":"CA rotation performed between node bootstrap attempts; stale nodeup state replayed; mixed-version clusters where the node sends keypairIDs from an old keystore.","solutions":["Re-run bootstrap/nodeup on the node so it fetches fresh keypair IDs and retries","Confirm a CA rotation completed and allow nodes to re-bootstrap with updated keypair IDs","Check keystore consistency across etcd backups/restores; restore if a partial rotation occurred","Upgrade nodes to a kOps version supporting the keypairIDs protocol field"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// compare keypair IDs before issuing\nfor signer, id := range keypairIDs {\n    if current, ok := serverKeypairIDs[signer]; ok && id != current {\n        return fmt.Errorf(\"stale keypair ID for %s: have %q want %q\", signer, id, current)\n    }\n}","typeGuard":null,"tryCatchPattern":"cert, err := issueCert(ctx, req)\nif err != nil && strings.Contains(err.Error(), \"didn't match server's\") {\n    // refresh keypair IDs and re-bootstrap once\n    if rerr := refreshKeypairIDsAndRetry(ctx); rerr != nil {\n        return rerr\n    }\n}","preventionTips":["Complete CA rotations fully and drain/re-bootstrap nodes after rotation","Avoid replaying old nodeup bootstrap requests from caches","Keep nodes on a kOps version that supports the keypairIDs protocol (>=1.22)","Back up and restore keystore state atomically during rotation"],"tags":["certificates","ca-rotation","bootstrap","version-skew"],"backgroundTag":"keypair-id-mismatch","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T07:17:12.445Z"}