{"record":{"id":"522d6da6b30e0d4a","repo":"grafana/k6","slug":"failed-to-append-certificate-from-pem-w","errorCode":null,"errorMessage":"failed to append certificate from PEM: %w","messagePattern":"failed to append certificate from PEM: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/grpc/client.go","lineNumber":176,"sourceCode":"\t\t\t}\n\t\t}\n\t}\n\n\t// Ignoring 'TLS MinVersion is too low' because this tls.Config will inherit MinValue and MaxValue\n\t// from the vu state tls.Config\n\n\ttlsCfg := &tls.Config{\n\t\tCipherSuites:       parentConfig.CipherSuites,\n\t\tInsecureSkipVerify: parentConfig.InsecureSkipVerify, //nolint:gosec\n\t\tMinVersion:         parentConfig.MinVersion,\n\t\tMaxVersion:         parentConfig.MaxVersion,\n\t\tRenegotiation:      parentConfig.Renegotiation,\n\t\tRootCAs:            cp,\n\t}\n\tif len(certificate) > 0 && len(key) > 0 {\n\t\tcert, err := tls.X509KeyPair(certificate, key)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to append certificate from PEM: %w\", err)\n\t\t}\n\t\ttlsCfg.Certificates = []tls.Certificate{cert}\n\t}\n\treturn tlsCfg, nil\n}\n\nfunc buildTLSConfigFromMap(parentConfig *tls.Config, tlsConfigMap map[string]any) (*tls.Config, error) {\n\tvar cert, key, pass []byte\n\tvar ca [][]byte\n\tvar err error\n\tif certstr, ok := tlsConfigMap[\"cert\"].(string); ok {\n\t\tcert = []byte(certstr)\n\t}\n\tif keystr, ok := tlsConfigMap[\"key\"].(string); ok {\n\t\tkey = []byte(keystr)\n\t}\n\tif passwordStr, ok := tlsConfigMap[\"password\"].(string); ok {\n\t\tpass = []byte(passwordStr)","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/grpc/client.go#L158-L194","documentation":"grpc.connect() with mutual TLS calls tls.X509KeyPair(tls.cert, tls.key); any failure to build the key pair - invalid PEM in either argument, a certificate that does not match the key, or an unsupported key encoding - is wrapped as 'failed to append certificate from PEM' (internal/js/modules/k6/grpc/client.go:176). Note the exact tls map keys: cert, key, password, cacerts (validated earlier in parseConnectTLSParam).","triggerScenarios":"connect(addr, { tls: { cert: open('client.crt'), key: open('client.key') } }) where the pair mismatches (cert rotated, stale key), either PEM is malformed, or the key is encrypted in an unsupported encoding (encrypted PKCS#8 fails earlier in decryptPrivateKey with its own message).","commonSituations":"Cert rotation where the new certificate ships but the old key remains; passing the CA bundle as cert; encrypted keys supplied without tls.password; PEMs mangled through env vars.","solutions":["Verify the pair matches: compare `openssl x509 -noout -modulus` output with `openssl rsa -noout -modulus` (moduli must be identical)","Re-export clean PEM files and reference them with open()","For encrypted keys pass tls.password (PKCS#8-encrypted is unsupported - convert with `openssl rsa -in enc.key -out plain.key`)"],"exampleFix":"# before: mismatched pair (rotated cert, old key)\nconnect(addr, { tls: { cert: open('client_v2.crt'), key: open('client_v1.key') } })\n\n# after: matching pair\nconnect(addr, { tls: { cert: open('client_v2.crt'), key: open('client_v2.key') } })","handlingStrategy":"validation","validationCode":"const isPem = (s) => typeof s === 'string' && /-----BEGIN [A-Z0-9 ]+-----/.test(s);\nfunction assertClientTLS(tls) {\n  if ((tls.cert && !isPem(tls.cert)) || (tls.key && !isPem(tls.key))) {\n    throw new Error('tls.cert and tls.key must be PEM strings (file contents, not paths)');\n  }\n  return tls;\n}\nclient.connect(addr, { tls: assertClientTLS({ cert: open('c.crt'), key: open('c.key') }) });","typeGuard":null,"tryCatchPattern":"try { client.connect(addr, { tls }); } catch (e) { if (/failed to append certificate from PEM/.test(e.message)) { /* verify cert/key pair with openssl, fix and retry */ } throw e; }","preventionTips":["Ship cert and key as one versioned pair; never mix rotations","Verify pairs offline: compare x509 and rsa modulus output","Use unencrypted PEM keys, or tls.password for supported encrypted formats"],"tags":["grpc","k6","tls","mtls","pem"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}