kubernetes/kops · error

could not parse private key (unable to decode PEM)

Error message

could not parse private key (unable to decode PEM)

What it means

parsePEMPrivateKey loops pem.Decode over the input looking for an 'RSA PRIVATE KEY', 'EC PRIVATE KEY', or 'PRIVATE KEY' block. If pem.Decode cannot extract any block at all (input is empty, whitespace, non-PEM text, or binary), it returns 'could not parse private key (unable to decode PEM)'.

Source

Thrown at pkg/pki/privatekey.go:190

}

func (k *PrivateKey) WriteToFile(filename string, perm os.FileMode) error {
	f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
	if err != nil {
		return err
	}
	_, err = k.WriteTo(f)
	if err1 := f.Close(); err == nil {
		err = err1
	}
	return err
}

func parsePEMPrivateKey(pemData []byte) (crypto.Signer, error) {
	for {
		block, rest := pem.Decode(pemData)
		if block == nil {
			return nil, fmt.Errorf("could not parse private key (unable to decode PEM)")
		}

		switch block.Type {
		case "RSA PRIVATE KEY":
			klog.V(10).Infof("Parsing pem block: %q", block.Type)
			return x509.ParsePKCS1PrivateKey(block.Bytes)
		case "EC PRIVATE KEY":
			klog.V(10).Infof("Parsing pem block: %q", block.Type)
			return x509.ParseECPrivateKey(block.Bytes)
		case "PRIVATE KEY":
			klog.V(10).Infof("Parsing pem block: %q", block.Type)
			k, err := x509.ParsePKCS8PrivateKey(block.Bytes)
			if err != nil {
				return nil, err
			}
			return k.(crypto.Signer), nil
		default:
			klog.Infof("Ignoring unexpected PEM block: %q", block.Type)

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Verify the input starts with '-----BEGIN' and is a PRIVATE KEY block, not a CERTIFICATE block.
  2. Check the file/state store entry is non-empty and not truncated (a common cause is failed writes or partial secret fetches).
  3. If the key is base64, decode with base64.StdEncoding and confirm the result is PEM before passing it.
  4. Regenerate the keypair (e.g. 'kops create keypair <cluster>') if the stored material is lost or corrupt.

Example fix

// before
key, err := pki.ParsePEMPrivateKey(certPEM) // cert passed instead of key
// after
if !bytes.Contains(keyPEM, []byte("-----BEGIN")) || !bytes.Contains(keyPEM, []byte("PRIVATE KEY")) {
    return fmt.Errorf("input is not a PEM private key")
}
key, err := pki.ParsePEMPrivateKey(keyPEM)
Defensive patterns

Strategy: validation

Validate before calling

func looksLikePEMKey(b []byte) bool {
    s := string(b)
    return strings.HasPrefix(s, "-----BEGIN") && strings.Contains(s, "PRIVATE KEY-----")
}

Try / catch

key, err := pki.ParsePEMPrivateKey(data)
if err != nil {
    if strings.Contains(err.Error(), "unable to decode PEM") {
        // input is not PEM: check for cert vs key, truncation, or base64 mismatch
    }
    return err
}

Prevention

When it happens

Trigger: ParsePEMPrivateKey called with empty/nil data; UnmarshalJSON given a string that is neither PEM nor base64-decodable PEM; passing a certificate, public key, CSR, or plaintext secret instead of a private key.

Common situations: Pasting the wrong file (cert vs key) into cluster specs; state store keyset entries truncated or corrupted; base64 conventions differing (URL-safe vs StdEncoding) so the fallback decode also fails; double-quoted/escaped PEM with corrupted newlines.

Understand the failure class

Related errors


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