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
- Verify the input starts with '-----BEGIN' and is a PRIVATE KEY block, not a CERTIFICATE block.
- Check the file/state store entry is non-empty and not truncated (a common cause is failed writes or partial secret fetches).
- If the key is base64, decode with base64.StdEncoding and confirm the result is PEM before passing it.
- 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
- Confirm the file is the private key, not the certificate or CSR, before parsing.
- Verify non-empty input: ParsePEMPrivateKey returns (nil, nil) for empty data — treat that as an error too.
- When base64 is involved, decode with base64.StdEncoding and validate PEM headers first.
- Guard state-store reads against truncation and verify round-trip after writes.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- AsString called on nil private key
- error writing SSL private key: %v
- AsBytes called on nil private key
- error writing SSL PrivateKey: %v
- error parsing private key: %v
AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05).
Data as JSON: /api/errors/e27def6300a73271.
Report an issue: GitHub.