hashicorp/nomad · error

failed to parse %s bytes: %w

Error message

failed to parse %s bytes: %w

What it means

getCassCert decodes a PEM blob and parses the first certificate. This error wraps the x509.ParseCertificate failure, meaning the PEM block decoded successfully but its DER payload is not a valid X.509 certificate (or is truncated/corrupt). The wrapped error (%w) carries the underlying asn.1 parse detail.

Source

Thrown at lib/auth/oidc/client_assertion.go:180

			return nil, fmt.Errorf("error reading %s: %w", source, err)
		}
	}
	// or pem string
	if k.PemCert != "" {
		source = "PemCert"
		bts = []byte(k.PemCert)
	}

	// ensure newlines around pem header/footer
	bts = newlineHeaders(bts)

	block, _ := pem.Decode(bts)
	if block == nil {
		return nil, fmt.Errorf("failed to decode %s PEM block", source)
	}
	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("failed to parse %s bytes: %w", source, err)
	}
	now := time.Now()
	if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
		return nil, errors.New("certificate has expired or is not yet valid")
	}
	return cert, nil
}

// hashKeyID derives a "certificate thumbprint" that the OIDC provider uses
// to find the certificate to verify the private key JWT signature.
// https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.7
func hashKeyID(cert *x509.Certificate, header structs.OIDCClientAssertionKeyIDHeader) (string, error) {
	var hasher hash.Hash
	switch header {
	case structs.OIDCClientAssertionHeaderX5t:
		if fips140.Enabled() {
			return "", errors.New("x5t assertion headers use SHA-1, which is forbidden in FIPS-140 mode")
		}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Inspect the file and confirm the block is a full '-----BEGIN CERTIFICATE-----' PEM with an intact base64 body
  2. If the file is a private key or public key, export the matching certificate (e.g. openssl x509 -in req.pem -out cert.pem) and point the config at it
  3. Verify integrity with: openssl x509 -in <file> -noout -text; fix or regenerate the certificate
  4. Regenerate the keypair/certificate and re-upload via the OIDC client upsert

Example fix

// before (config pointed at private key)
client_assertion_key = "/etc/nomad/oidc/client.key"
// after (point at the X.509 certificate)
client_assertion_key = "/etc/nomad/oidc/client.crt"
Defensive patterns

Strategy: validation

Validate before calling

pemBytes, _ := os.ReadFile(certPath)
block, _ := pem.Decode(pemBytes)
if block == nil || block.Type != "CERTIFICATE" {
	return fmt.Errorf("%s is not a CERTIFICATE PEM block", certPath)
}
if _, err := x509.ParseCertificate(block.Bytes); err != nil {
	return fmt.Errorf("cert at %s is not valid DER: %w", certPath, err)
}

Type guard

func isCertificatePEM(b []byte) bool {
	block, _ := pem.Decode(b)
	return block != nil && block.Type == "CERTIFICATE"
}

Prevention

When it happens

Trigger: BuildClientAssertionJWT loads a key/cert blob via getCassCert where pem.Decode succeeds but block.Bytes is not parseable DER — e.g. the file contains a PUBLIC KEY, PRIVATE KEY, CSR, or arbitrary base64 block instead of a CERTIFICATE; or the cert body was truncated/edited.

Common situations: Misconfigured Nomad OIDC client assertion key file pointing at a private key (.key) or public key instead of the certificate; copy-paste truncating PEM body; wrong file mounted in a secret; multi-document PEM where the first block is not the certificate.

Understand the failure class

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/37952569ffda2b40. Report an issue: GitHub.