{"record":{"id":"6eca17b61b67e272","repo":"hashicorp/terraform","slug":"failed-to-parse-ssh-private-key-s","errorCode":null,"errorMessage":"Failed to parse ssh private key: %s","messagePattern":"Failed to parse ssh private key: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/communicator/ssh/provisioner.go","lineNumber":436,"sourceCode":"\treturn ssh.PublicKeys(ucertSigner), nil\n}\n\nfunc readPrivateKey(pk string) (ssh.AuthMethod, error) {\n\t// We parse the private key on our own first so that we can\n\t// show a nicer error if the private key has a password.\n\tblock, _ := pem.Decode([]byte(pk))\n\tif block == nil {\n\t\treturn nil, errors.New(\"Failed to read ssh private key: no key found\")\n\t}\n\tif block.Headers[\"Proc-Type\"] == \"4,ENCRYPTED\" {\n\t\treturn nil, errors.New(\n\t\t\t\"Failed to read ssh private key: password protected keys are\\n\" +\n\t\t\t\t\"not supported. Please decrypt the key prior to use.\")\n\t}\n\n\tsigner, err := ssh.ParsePrivateKey([]byte(pk))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Failed to parse ssh private key: %s\", err)\n\t}\n\n\treturn ssh.PublicKeys(signer), nil\n}\n\nfunc connectToAgent(connInfo *connectionInfo) (*sshAgent, error) {\n\tif !connInfo.Agent {\n\t\t// No agent configured\n\t\treturn nil, nil\n\t}\n\n\tagent, conn, err := sshagent.New()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// connection close is handled over in Communicator\n\treturn &sshAgent{","sourceCodeStart":418,"sourceCodeEnd":454,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/communicator/ssh/provisioner.go#L418-L454","documentation":"Raised by the SSH communicator's readPrivateKey when ssh.ParsePrivateKey cannot interpret the private key string supplied via the connection block's private_key. The function already rejects PEM-encrypted/`Proc-Type: 4,ENCRYPTED` keys earlier (lines 428-431) and no-key input at line 426, so reaching line 436 means a PEM block decoded but x/crypto/ssh refused it: unsupported key type, malformed/corrupted body, wrong OpenSSH/PEM format, or non-key data. It surfaces during Terraform remote-exec provisioner / connection setup when authenticating to the target over SSH.","triggerScenarios":"A `connection { type=\"ssh\" private_key = ... }` whose value is not a parseable unencrypted OpenSSH/PEM private key: trailing whitespace or literal quotes wrapped around the key, a key in the new OpenSSH format unsupported by the bundled golang.org/x/crypto, an ECDSA/Ed25519/DSS key the build rejects, or the public-key half pasted by mistake. Also when file(\"...\") returns an error string because the path is wrong.","commonSituations":"Pasting a key via an inline heredoc that mangles newlines, loading a key whose file path is wrong so file() yields an error message, encrypted keys that bypass the Proc-Type check (e.g. `-----BEGIN OPENSSH PRIVATE KEY-----` with a passphrase), or running a Terraform version linked against an older x/crypto predating a key type.","solutions":["Regenerate/convert the key to an unencrypted PEM or OpenSSH format: `ssh-keygen -p -f id_rsa` to strip the passphrase, or `ssh-keygen -t rsa -m PEM -f id_rsa` for classic PEM.","Verify the key is valid for the same Go crypto Terraform uses: `ssh-keygen -l -f id_rsa` and `file id_rsa` should report an OpenSSH/RSA private key, not ASCII text.","Pass the key from a clean source: reference `file(\"~/.ssh/id_rsa\")` rather than an inline heredoc, ensuring no surrounding quotes, whitespace, or escaped newlines survive interpolation.","If you need a passphrase-protected key, decrypt it to a temp file for Terraform or switch to `agent = true` and use ssh-agent (`ssh-add`)."],"exampleFix":"// before\nconnection {\n  type        = \"ssh\"\n  private_key = \"-----BEGIN RSA PRIVATE KEY-----\\n...truncated/garbled...\\n\"\n}\n\n// after\nconnection {\n  type        = \"ssh\"\n  private_key = file(\"~/.ssh/id_rsa_nopass\")  // unencrypted PEM key\n}","handlingStrategy":"validation","validationCode":"// Validate an SSH private key before handing it to Terraform's SSH communicator.\nimport (\n    \"encoding/pem\"\n    \"crypto/x509\"\n    \"golang.org/x/crypto/ssh\"\n)\nfunc validPrivateKeyMaterial(s string) error {\n    block, _ := pem.Decode([]byte(s))\n    if block == nil { return fmt.Errorf(\"no PEM block in private key\") }\n    if block.Headers[\"Proc-Type\"] == \"4,ENCRYPTED\" { return fmt.Errorf(\"key is passphrase-protected\") }\n    if _, err := ssh.ParsePrivateKey([]byte(s)); err != nil {\n        return fmt.Errorf(\"key not parseable by x/crypto/ssh: %w\", err)\n    }\n    _ = x509.ParsePKCS1PrivateKey // accepted formats also covered by ssh.ParsePrivateKey\n    return nil\n}","typeGuard":"// Guard against empty/garbage key values when generating config.\nfunc isNonEmptyPEM(v string) bool {\n    return strings.Contains(v, \"-----BEGIN \") && strings.Contains(v, \"-----END \")\n}","tryCatchPattern":null,"preventionTips":["Store keys as files and load with file(); avoid inline heredocs that mangle newlines.","Never commit passphrase-protected keys; strip passphrases (ssh-keygen -p) or use agent auth.","Pin a Terraform version whose golang.org/x/crypto supports your key type (Ed25519, new OpenSSH format)."],"tags":["ssh","provisioner","authentication","connection-block"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}