{"record":{"id":"21c3982abddf58b9","repo":"kubernetes/kops","slug":"challenge-id-not-set","errorCode":null,"errorMessage":"challenge.id not set","messagePattern":"challenge\\.id not set","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/bootstrap/challenge_client.go","lineNumber":74,"sourceCode":"\t}\n\n\t// TODO: Caching and rotation\n\tclientCertificate := &tls.Certificate{\n\t\tPrivateKey:  privateKey.Key,\n\t\tCertificate: [][]byte{certificate.Certificate.Raw},\n\t\tLeaf:        certificate.Certificate,\n\t}\n\treturn clientCertificate, nil\n}\n\nfunc (c *ChallengeClient) DoCallbackChallenge(ctx context.Context, clusterName string, targetEndpoint string, bootstrapRequest *nodeup.BootstrapRequest) error {\n\tchallenge := bootstrapRequest.Challenge\n\n\tif challenge == nil {\n\t\treturn fmt.Errorf(\"challenge not set\")\n\t}\n\tif challenge.ChallengeID == \"\" {\n\t\treturn fmt.Errorf(\"challenge.id not set\")\n\t}\n\tif len(challenge.ChallengeSecret) == 0 {\n\t\treturn fmt.Errorf(\"challenge.secret not set\")\n\t}\n\tif challenge.Endpoint == \"\" {\n\t\treturn fmt.Errorf(\"challenge.endpoint not set\")\n\t}\n\tif len(challenge.ServerCA) == 0 {\n\t\treturn fmt.Errorf(\"challenge.ca not set\")\n\t}\n\n\tclientCertificate, err := c.getClientCertificate(ctx, clusterName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tserverCAs := x509.NewCertPool()\n\tif !serverCAs.AppendCertsFromPEM(challenge.ServerCA) {","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/bootstrap/challenge_client.go#L56-L92","documentation":"DoCallbackChallenge requires challenge.ChallengeID to be non-empty after confirming the Challenge object exists. An empty ID means the challenge response from the server was partially populated — the client cannot reference the challenge when calling back, so it fails fast with 'challenge.id not set'.","triggerScenarios":"bootstrap -> DoCallbackChallenge with bootstrapRequest.Challenge != nil but ChallengeID == \"\" — server serialized an incomplete challenge or a custom flow constructed Challenge{ChallengeSecret: ..., ...} without an ID.","commonSituations":"Version skew or serialization mismatch between server and nodeup dropping the ID field; hand-rolled bootstrap clients building the challenge struct incompletely; JSON field-name mismatches in custom transports.","solutions":["Log the received challenge struct (excluding the secret) to see which fields the server actually sent","Ensure the server populates ChallengeID when issuing challenges (NewChallengeServer flow)","Align nodeup and kops server versions so the challenge struct serialization matches","Fix custom bootstrap clients to copy all challenge fields (ID, secret, endpoint) from the server response"],"exampleFix":"// before\nreq.Challenge = &nodeup.Challenge{ChallengeSecret: secret}\n// after\nreq.Challenge = &nodeup.Challenge{ChallengeID: id, ChallengeSecret: secret, Endpoint: endpoint}","handlingStrategy":"validation","validationCode":"if req.Challenge == nil || req.Challenge.ChallengeID == \"\" {\n  return errors.New(\"challenge or challenge.id missing from bootstrap request\")\n}","typeGuard":"func hasChallengeID(c *nodeup.Challenge) bool {\n  return c != nil && c.ChallengeID != \"\"\n}","tryCatchPattern":"if err := challengeClient.DoCallbackChallenge(ctx, cluster, endpoint, req); err != nil {\n  return fmt.Errorf(\"callback challenge failed: %w\", err)\n}","preventionTips":["Copy all challenge fields (ID, secret, endpoint) from the server response, not just the secret","Check server-side serialization includes the ChallengeID field","Log the challenge struct (redacting the secret) on failure to spot dropped fields"],"tags":["bootstrap","challenge","nodeup","validation"],"backgroundTag":"missing-challenge-payload","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"}