{"record":{"id":"9c2c909ae546446c","repo":"aaif-goose/goose","slug":"failed-to-read-client-private-key","errorCode":null,"errorMessage":"Failed to read client private key: {}","messagePattern":"Failed to read client private key: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose-providers/src/api_client.rs","lineNumber":94,"sourceCode":"        self\n    }\n\n    pub fn with_ca_cert(mut self, path: PathBuf) -> Self {\n        self.ca_cert_path = Some(path);\n        self\n    }\n\n    pub fn is_configured(&self) -> bool {\n        self.client_identity.is_some() || self.ca_cert_path.is_some()\n    }\n\n    #[cfg(any(feature = \"rustls-tls\", feature = \"native-tls\"))]\n    fn load_identity(&self) -> Result<Option<Identity>> {\n        if let Some(cert_key_pair) = &self.client_identity {\n            let cert_pem = read_to_string(&cert_key_pair.cert_path)\n                .map_err(|e| anyhow::anyhow!(\"Failed to read client certificate: {}\", e))?;\n            let key_pem = read_to_string(&cert_key_pair.key_path)\n                .map_err(|e| anyhow::anyhow!(\"Failed to read client private key: {}\", e))?;\n\n            #[cfg(not(feature = \"native-tls\"))]\n            let identity = {\n                let combined_pem = format!(\"{}\\n{}\", cert_pem, key_pem);\n                Identity::from_pem(combined_pem.as_bytes()).map_err(|e| {\n                    anyhow::anyhow!(\"Failed to create identity from cert and key: {}\", e)\n                })?\n            };\n\n            #[cfg(feature = \"native-tls\")]\n            let identity = {\n                let pkcs8_key_pem = convert_key_to_pkcs8_pem(&key_pem)?;\n                Identity::from_pkcs8_pem(cert_pem.as_bytes(), pkcs8_key_pem.as_bytes()).map_err(\n                    |e| anyhow::anyhow!(\"Failed to create identity from cert and key: {}\", e),\n                )?\n            };\n\n            Ok(Some(identity))","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-providers/src/api_client.rs#L76-L112","documentation":"Companion to the certificate read: TlsConfig::load_identity also reads the client private key PEM from key_path. Any I/O failure on that file (missing, unreadable, non-UTF8) is reported as 'Failed to read client private key: {io_error}' before any identity parsing is attempted.","triggerScenarios":"Provider config includes client_identity whose key_path is wrong/unreadable, or the key file is binary DER rather than PEM text. Triggered at client build time; the cert read (previous line) must have succeeded, so the cert path was fine and only the key is at fault.","commonSituations":"Cert and key in different directories and only the cert path updated; key generated by openssl genrsa outputting PKCS#8 binary without -outform PEM; Kubernetes secret mounted with the key at a slightly different name; file permission 600 owned by another user.","solutions":["Check the exact key_path in the config and ls -l it from the goose process context","Convert DER keys to PEM: openssl pkey -inform der -in key.der -out key.pem","Use absolute paths and readable permissions for the goose user","Confirm the file is a PEM text starting with -----BEGIN ... PRIVATE KEY-----"],"exampleFix":"# before\nclient_identity:\n  cert_path: /etc/goose/tls/client-cert.pem\n  key_path: /etc/goose/tls/client-key   # DER binary or typo'd name\n\n# after\nclient_identity:\n  cert_path: /etc/goose/tls/client-cert.pem\n  key_path: /etc/goose/tls/client-key.pem","handlingStrategy":"validation","validationCode":"fn readable_utf8_file(path: &std::path::Path) -> bool {\n    std::fs::metadata(path).map(|m| m.is_file()).unwrap_or(false)\n        && std::fs::read_to_string(path).is_ok()\n}\n\nif !readable_utf8_file(std::path::Path::new(&key_path)) {\n    anyhow::bail!(\"client key missing/unreadable: {key_path}\");\n}","typeGuard":"fn key_file_is_pem(path: &std::path::Path) -> bool {\n    std::fs::read_to_string(path)\n        .map(|s| s.contains(\"-----BEGIN\") && s.contains(\"PRIVATE KEY-----\"))\n        .unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Keep cert and key in the same directory so one path fix covers both","Generate keys as PEM: openssl genpkey or add -outform PEM","Check permissions as the goose user, not your admin shell","In Kubernetes, verify secret key names match mounted filenames exactly"],"tags":["rust","tls","mtls","private-key","filesystem","config"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}