hashicorp/terraform · error

failed to parse private key

Error message

failed to parse private key %q: %s

What it means

In signCertWithPrivateKey, the user-supplied private key string is parsed via ssh.ParseRawPrivateKey. If the key is not valid PEM, is corrupted, uses an unsupported algorithm, or is in the wrong format, parsing fails. SECURITY NOTE: the error message interpolates the full private key material via %q, potentially exposing secrets in logs and error output.

Solutions

  1. Verify the private_key value is a complete, unmodified PEM private key (begins with -----BEGIN ... PRIVATE KEY-----).
  2. Ensure you are not accidentally using the .pub file or a public key string.
  3. If using ed25519 or new-format keys, update golang.org/x/crypto to a version that supports them, or regenerate an RSA/ECDSA key.
  4. Check for newline corruption: the key must preserve all newlines. If loading from a file, use file() not a variable that may have been mangled.
  5. Strip any trailing whitespace or CRLF line endings from the key material.

Example fix

# before — accidentally using public key or mangled key
connection {
  private_key = var.some_key  # might be .pub or truncated
  certificate  = var.cert
}

# after — valid PEM private key with preserved newlines
connection {
  private_key = file("~/.ssh/id_rsa")       # not id_rsa.pub
certificate  = file("~/.ssh/id_rsa-cert.pub")
}
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate the private key parses before using it in a connection
import "golang.org/x/crypto/ssh"

func validatePrivateKey(pk string) error {
    _, err := ssh.ParseRawPrivateKey([]byte(pk))
    if err != nil {
        // Do NOT log pk contents — it is secret
        return fmt.Errorf("private key is not parseable: %w", err)
    }
    return nil
}

Type guard

func isValidPEMPrivateKey(pk string) bool {
    block, _ := pem.Decode([]byte(pk))
    return block != nil &&
        (strings.Contains(block.Type, "PRIVATE KEY")) &&
        block.Headers["Proc-Type"] != "4,ENCRYPTED"
}

Try / catch

// Wrap signCertWithPrivateKey; never log the key material
signer, err := signCertWithPrivateKey(privateKey, cert)
if err != nil {
    // Log a generic message — DO NOT include pk or certificate in logs
    return fmt.Errorf("SSH certificate authentication setup failed: %w", sanitizeErr(err))
}

Prevention

When it happens

Trigger: Configuring an SSH connection with both a private_key and a certificate where the private_key value is not a parseable OpenSSH/PKCS PEM key. Triggered by: pasting a public key instead of a private key, providing an OpenSSH new-format key that ParseRawPrivateKey doesn't support in older x/crypto versions, truncated key, or wrong-line-ending corruption.

Common situations: User accidentally references the .pub file in private_key. Key was generated with a newer ssh-keygen format (ed25519 or RFC 4716) unsupported by the linked golang.org/x/crypto version. Key pasted from a secrets manager that stripped newlines. Key has Windows CRLF line endings that confuse the PEM decoder.

Understand the failure class

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/58bd12f10113ee32. Report an issue: GitHub.

Appendix: source

Thrown at internal/communicator/ssh/provisioner.go:400

	if opts.password != "" {
		conf.Auth = append(conf.Auth, ssh.Password(opts.password))
		conf.Auth = append(conf.Auth, ssh.KeyboardInteractive(
			PasswordKeyboardInteractive(opts.password)))
	}

	if opts.sshAgent != nil {
		conf.Auth = append(conf.Auth, opts.sshAgent.Auth())
	}

	return conf, nil
}

// Create a Cert Signer and return ssh.AuthMethod
func signCertWithPrivateKey(pk string, certificate string) (ssh.AuthMethod, error) {
	rawPk, err := ssh.ParseRawPrivateKey([]byte(pk))
	if err != nil {
		return nil, fmt.Errorf("failed to parse private key %q: %s", pk, err)
	}

	pcert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(certificate))
	if err != nil {
		return nil, fmt.Errorf("failed to parse certificate %q: %s", certificate, err)
	}

	usigner, err := ssh.NewSignerFromKey(rawPk)
	if err != nil {
		return nil, fmt.Errorf("failed to create signer from raw private key %q: %s", rawPk, err)
	}

	ucertSigner, err := ssh.NewCertSigner(pcert.(*ssh.Certificate), usigner)
	if err != nil {
		return nil, fmt.Errorf("failed to create cert signer %q: %s", usigner, err)
	}

	return ssh.PublicKeys(ucertSigner), nil

View on GitHub (pinned to d32a084675)