kubernetes/kubernetes · error
no PEM block headers are permitted
Error message
no PEM block headers are permitted
What it means
Thrown by validateCertificate() when PEM-decoding a CSR's status.certificate field and a decoded PEM block contains non-empty Headers (e.g. Proc-Type, DEK-Info). Kubernetes requires the certificate PEM block to be a bare base64 body with the type 'CERTIFICATE' and zero headers, because encrypted or annotated PEM blocks are ambiguous and not X.509-trust-safe. The check fires during CSR status updates and ClusterTrustBundle-style certificate validation paths where allowArbitraryCertificate is false.
Source
Thrown at pkg/apis/certificates/validation/validation.go:121
}
func validateCertificate(pemData []byte) error {
if len(pemData) == 0 {
return nil
}
blocks := 0
for {
block, remainingData := pem.Decode(pemData)
if block == nil {
break
}
if block.Type != utilcert.CertificateBlockType {
return fmt.Errorf("only CERTIFICATE PEM blocks are allowed, found %q", block.Type)
}
if len(block.Headers) != 0 {
return fmt.Errorf("no PEM block headers are permitted")
}
blocks++
certs, err := x509.ParseCertificates(block.Bytes)
if err != nil {
return err
}
if len(certs) == 0 {
return fmt.Errorf("found CERTIFICATE PEM block containing 0 certificates")
}
pemData = remainingData
}
if blocks == 0 {
return fmt.Errorf("must contain at least one CERTIFICATE PEM block")
}
View on GitHub (pinned to b882c60b40)
Solutions
- Regenerate the certificate PEM with a plain 'CERTIFICATE' block and no header lines: openssl x509 -in cert.pem -outform PEM removes annotations.
- Strip any non-CERTIFICATE blocks (e.g. PRIVATE KEY, EC PARAMETERS) from the payload before writing status.certificate.
- If using a custom signer, ensure it emits only RFC 7468 bare CERTIFICATE blocks with empty Headers.
- Validate locally with encoding/pem and assert len(block.Headers)==0 && block.Type=="CERTIFICATE" before PATCHing the CSR status.
Example fix
// before
block, _ := pem.Decode(pemData)
// block.Headers = map[string]string{"Proc-Type": "4,ENCRYPTED"}
// after - emit a clean block
pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Headers: map[string]string{}, Bytes: derCert}) Defensive patterns
Strategy: validation
Validate before calling
import "encoding/pem"
func validateCertPEM(pemData []byte) error {
for len(pemData) > 0 {
var block *pem.Block
block, pemData = pem.Decode(pemData)
if block == nil { break }
if block.Type != "CERTIFICATE" { return fmt.Errorf("bad block type %q", block.Type) }
if len(block.Headers) != 0 { return fmt.Errorf("PEM block must have no headers") }
}
return nil
} Type guard
func isCleanCertPEM(pemData []byte) bool {
block, _ := pem.Decode(pemData)
return block != nil && block.Type == "CERTIFICATE" && len(block.Headers) == 0
} Prevention
- Always emit PEM via pem.EncodeToMemory with an empty Headers map.
- Never paste encrypted PEM (Proc-Type/DEK-Info) into status.certificate.
- Run `openssl x509 -in cert.pem -noout` as a pre-submit smoke test.
When it happens
Trigger: Setting csr.Status.Certificate to a PEM blob whose block carries header lines (e.g. an encrypted private-key-style block mistakenly attached, or a PEM generated by an older OpenSSL that emits 'Proc-Type: 4,ENCRYPTED'). Reproduced by a status subresource update on a CertificateSigningRequest where the signer writes such a block.
Common situations: Piping a certificate through a tool that re-emits PEM with headers; copy-pasting a combined key+cert PEM into the certificate field; signer implementations that prepend annotations; upgrading Go's encoding/pem which now surfaces previously-tolerated headers.
Related errors
- found CERTIFICATE PEM block containing 0 certificates
- must contain at least one CERTIFICATE PEM block
- only CERTIFICATE PEM blocks are allowed, found %q
- PEM block type must be CERTIFICATE REQUEST
- PEM block type must be CERTIFICATE REQUEST
AI-assisted analysis of kubernetes/kubernetes@b882c60b40 (2026-08-07).
Data as JSON: /api/errors/ee1a0731f15df181.
Report an issue: GitHub.