kubernetes/kops · error
challenge.id not set
Error message
challenge.id not set
What it means
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'.
Source
Thrown at pkg/bootstrap/challenge_client.go:74
}
// TODO: Caching and rotation
clientCertificate := &tls.Certificate{
PrivateKey: privateKey.Key,
Certificate: [][]byte{certificate.Certificate.Raw},
Leaf: certificate.Certificate,
}
return clientCertificate, nil
}
func (c *ChallengeClient) DoCallbackChallenge(ctx context.Context, clusterName string, targetEndpoint string, bootstrapRequest *nodeup.BootstrapRequest) error {
challenge := bootstrapRequest.Challenge
if challenge == nil {
return fmt.Errorf("challenge not set")
}
if challenge.ChallengeID == "" {
return fmt.Errorf("challenge.id not set")
}
if len(challenge.ChallengeSecret) == 0 {
return fmt.Errorf("challenge.secret not set")
}
if challenge.Endpoint == "" {
return fmt.Errorf("challenge.endpoint not set")
}
if len(challenge.ServerCA) == 0 {
return fmt.Errorf("challenge.ca not set")
}
clientCertificate, err := c.getClientCertificate(ctx, clusterName)
if err != nil {
return err
}
serverCAs := x509.NewCertPool()
if !serverCAs.AppendCertsFromPEM(challenge.ServerCA) {View on GitHub (pinned to 4c8573c808)
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
Example fix
// before
req.Challenge = &nodeup.Challenge{ChallengeSecret: secret}
// after
req.Challenge = &nodeup.Challenge{ChallengeID: id, ChallengeSecret: secret, Endpoint: endpoint} Defensive patterns
Strategy: validation
Validate before calling
if req.Challenge == nil || req.Challenge.ChallengeID == "" {
return errors.New("challenge or challenge.id missing from bootstrap request")
} Type guard
func hasChallengeID(c *nodeup.Challenge) bool {
return c != nil && c.ChallengeID != ""
} Try / catch
if err := challengeClient.DoCallbackChallenge(ctx, cluster, endpoint, req); err != nil {
return fmt.Errorf("callback challenge failed: %w", err)
} Prevention
- 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
When it happens
Trigger: bootstrap -> DoCallbackChallenge with bootstrapRequest.Challenge != nil but ChallengeID == "" — server serialized an incomplete challenge or a custom flow constructed Challenge{ChallengeSecret: ..., ...} without an ID.
Common situations: 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.
Related errors
- challenge not set
- building nodeConfig for instanceGroup: %w
- unsupported cloud provider for authenticator %q
- no keypairID for %q
- challenge.secret not set
AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05).
Data as JSON: /api/errors/21c3982abddf58b9.
Report an issue: GitHub.