kubernetes/kops · error

found cert in store, but did not find private key: %q

Error message

found cert in store, but did not find private key: %q

What it means

During Find, the Keypair task fetches the keyset named `name` from the keystore. If a certificate exists as the primary item but its associated private key is missing, kops cannot reconstruct the task's actual state (the keypair is unusable), so it fails hard instead of treating the entry as absent. This indicates a partially-written or corrupted keyset in the keystore (e.g. the keybase/secret store).

Source

Thrown at upup/pkg/fi/fitasks/keypair.go:89

func (e *Keypair) Find(c *fi.CloudupContext) (*Keypair, error) {
	ctx := c.Context()

	name := fi.ValueOf(e.Name)
	if name == "" {
		return nil, nil
	}

	keyset, err := c.T.Keystore.FindKeyset(ctx, name)
	if err != nil {
		return nil, err
	}
	if keyset == nil || keyset.Primary == nil || keyset.Primary.Certificate == nil {
		return nil, nil
	}
	cert := keyset.Primary.Certificate
	if keyset.Primary.PrivateKey == nil {
		return nil, fmt.Errorf("found cert in store, but did not find private key: %q", name)
	}

	var alternateNames []string
	alternateNames = append(alternateNames, cert.Certificate.DNSNames...)
	alternateNames = append(alternateNames, cert.Certificate.EmailAddresses...)
	for _, ip := range cert.Certificate.IPAddresses {
		alternateNames = append(alternateNames, ip.String())
	}
	sort.Strings(alternateNames)

	actual := &Keypair{
		Name:           &name,
		AlternateNames: alternateNames,
		Subject:        pki.PkixNameToString(&cert.Subject),
		Issuer:         pki.PkixNameToString(&cert.Certificate.Issuer),
		Type:           pki.BuildTypeDescription(cert.Certificate),
		LegacyFormat:   keyset.LegacyFormat,
	}

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Restore the missing private key file in the state store (e.g. in the keyset directory under the cluster's pki/secret store) from backup
  2. Delete the incomplete keyset so the next update regenerates it: `kops delete secret keypair <name> --name <cluster>` (or remove the keyset directory), then `kops update cluster --yes`
  3. Check the state store (S3/GCS/VFSSL/Vault) for the item named <name> and compare which files (cert vs key) are present
  4. If rotation is acceptable, rotate the affected keypair rather than restoring

Example fix

// before: state store has only <name>.crt for keyset
// after
kops delete secret keypair apiserver --name mycluster.example.com
kops update cluster --yes   # regenerates cert+key together
Defensive patterns

Strategy: validation

Validate before calling

ks, err := keystore.FindKeyset(ctx, name); if err != nil { return err }; if ks != nil && ks.Primary != nil && ks.Primary.Certificate != nil && ks.Primary.PrivateKey == nil { return fmt.Errorf("keyset %q is incomplete (cert without key); restore or delete it", name) }

Type guard

func keysetComplete(ks *fi.Keyset) bool { return ks != nil && ks.Primary != nil && ks.Primary.Certificate != nil && ks.Primary.PrivateKey != nil }

Try / catch

if err != nil { if strings.Contains(err.Error(), "did not find private key") { // restore from backup or delete keyset to force regeneration } return err }

Prevention

When it happens

Trigger: c.T.Keystore.FindKeyset returns a keyset whose Primary.Certificate is set but Primary.PrivateKey is nil; occurs during any cluster update where the affected keypair task runs Find (e.g. `kops update cluster`, `kops replace`).

Common situations: Keyset was manually edited in the secret store and the .key file deleted; a failed/partial write left only the cert; mirroring or migrating a cluster state store and dropping private key files; keystore backed by git/S3 where key files were excluded by ignore rules.

Related errors


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