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

  1. Regenerate the certificate PEM with a plain 'CERTIFICATE' block and no header lines: openssl x509 -in cert.pem -outform PEM removes annotations.
  2. Strip any non-CERTIFICATE blocks (e.g. PRIVATE KEY, EC PARAMETERS) from the payload before writing status.certificate.
  3. If using a custom signer, ensure it emits only RFC 7468 bare CERTIFICATE blocks with empty Headers.
  4. 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

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


AI-assisted analysis of kubernetes/kubernetes@b882c60b40 (2026-08-07). Data as JSON: /api/errors/ee1a0731f15df181. Report an issue: GitHub.