kubernetes/kops · error
request's keypair ID %q for %s didn't match server's %q
Error message
request's keypair ID %q for %s didn't match server's %q
What it means
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.
Source
Thrown at cmd/kops-controller/pkg/server/server.go:345
}
issueReq.AlternateNames = id.CertificateNames
issueReq.Type = "server"
case "kube-proxy":
issueReq.Subject = pkix.Name{
CommonName: rbac.KubeProxy,
}
case "kube-router":
issueReq.Subject = pkix.Name{
CommonName: rbac.KubeRouter,
}
default:
return "", fmt.Errorf("unexpected key name")
}
// This field was added to the protocol in kOps 1.22.
if len(keypairIDs) > 0 {
if keypairIDs[issueReq.Signer] != s.keypairIDs[issueReq.Signer] {
return "", fmt.Errorf("request's keypair ID %q for %s didn't match server's %q", keypairIDs[issueReq.Signer], issueReq.Signer, s.keypairIDs[issueReq.Signer])
}
}
cert, _, _, err := pki.IssueCert(ctx, issueReq, s.keystore)
if err != nil {
return "", fmt.Errorf("issuing certificate: %v", err)
}
return cert.AsString()
}
// recovery is responsible for ensuring we don't exit on a panic.
func recovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
defer func() {
if err := recover(); err != nil {
w.WriteHeader(http.StatusInternalServerError)
View on GitHub (pinned to 4c8573c808)
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
Defensive patterns
Strategy: retry
Validate before calling
// compare keypair IDs before issuing
for signer, id := range keypairIDs {
if current, ok := serverKeypairIDs[signer]; ok && id != current {
return fmt.Errorf("stale keypair ID for %s: have %q want %q", signer, id, current)
}
} Try / catch
cert, err := issueCert(ctx, req)
if err != nil && strings.Contains(err.Error(), "didn't match server's") {
// refresh keypair IDs and re-bootstrap once
if rerr := refreshKeypairIDsAndRetry(ctx); rerr != nil {
return rerr
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: CA rotation performed between node bootstrap attempts; stale nodeup state replayed; mixed-version clusters where the node sends keypairIDs from an old keystore.
Related errors
AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05).
Data as JSON: /api/errors/5f63cfcfab30af4e.
Report an issue: GitHub.