{"record":{"id":"1df82978d8c2da36","repo":"zeroclaw-labs/zeroclaw","slug":"amqp-channel-client-cert-is-set-but-client-k","errorCode":null,"errorMessage":"amqp channel '{}': client_cert is set but client_key is missing","messagePattern":"amqp channel '(.+?)': client_cert is set but client_key is missing","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/amqp.rs","lineNumber":282,"sourceCode":"\n        Connection::connect_with_config(\n            &self.amqp_url,\n            props,\n            OwnedTLSConfig {\n                identity,\n                cert_chain,\n            },\n        )\n        .await\n        .map_err(Into::into)\n    }\n\n    fn build_client_identity(&self) -> anyhow::Result<Option<OwnedIdentity>> {\n        let (cert_path, key_path) = match (&self.client_cert, &self.client_key) {\n            (Some(cert), Some(key)) => (cert, key),\n            (None, None) => return Ok(None),\n            (Some(_), None) => {\n                anyhow::bail!(\n                    \"amqp channel '{}': client_cert is set but client_key is missing\",\n                    self.alias\n                )\n            }\n            (None, Some(_)) => {\n                anyhow::bail!(\n                    \"amqp channel '{}': client_key is set but client_cert is missing\",\n                    self.alias\n                )\n            }\n        };\n\n        let cert_pem = std::fs::read(cert_path)?;\n        let key_pem = std::fs::read(key_path)?;\n        let der = pem_to_pkcs12_der(&cert_pem, &key_pem, &self.alias)?;\n\n        Ok(Some(OwnedIdentity {\n            der,","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/amqp.rs#L264-L300","documentation":"The AMQP channel was configured for TLS client authentication with client_cert set but client_key missing. lapin's OwnedIdentity needs a cert+key pair to build the PKCS#12 client identity, so build_client_identity refuses the half-pair during connect rather than attempting an anonymous connection against a broker expecting mTLS.","triggerScenarios":"AmqpChannelConfig (or the amqp channel config block) sets client_cert without client_key; connect() then calls build_client_identity and hits the (Some, None) arm.","commonSituations":"Secrets distributed as separate files where only the cert path got templated; config snippets copied between environments dropping one line; a key loader that failed silently and left the field unset.","solutions":["Set the matching client_key path in the same channel config block.","Verify both files exist and are readable by the service user before restarting.","If client-certificate auth is not required by the broker, remove client_cert as well — both unset means no client identity."],"exampleFix":"# before\n[channels.amqp.prod]\nurl = \"amqps://broker:5671\"\nclient_cert = \"/etc/zeroclaw/tls/client.pem\"\n\n# after\n[channels.amqp.prod]\nurl = \"amqps://broker:5671\"\nclient_cert = \"/etc/zeroclaw/tls/client.pem\"\nclient_key = \"/etc/zeroclaw/tls/client.key\"","handlingStrategy":"validation","validationCode":"match (&cfg.client_cert, &cfg.client_key) {\n    (Some(_), None) | (None, Some(_)) => {\n        anyhow::bail!(\"client_cert and client_key must be set together\");\n    }\n    _ => {}\n}","typeGuard":"fn has_complete_client_identity(cfg: &AmqpChannelConfig) -> bool {\n    cfg.client_cert.is_some() == cfg.client_key.is_some()\n}","tryCatchPattern":null,"preventionTips":["Validate cert/key as a pair at config load, not at connect time","Template both TLS paths from the same secret source","Smoke-test mTLS with openssl s_client before deploying"],"tags":["rust","amqp","mtls","tls","config-validation"],"backgroundTag":"missing-tls-client-key","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}