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

  1. Log the received challenge struct (excluding the secret) to see which fields the server actually sent
  2. Ensure the server populates ChallengeID when issuing challenges (NewChallengeServer flow)
  3. Align nodeup and kops server versions so the challenge struct serialization matches
  4. 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

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


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/21c3982abddf58b9. Report an issue: GitHub.