{"record":{"id":"00ffb44efdbf72b0","repo":"zeroclaw-labs/zeroclaw","slug":"client-cert-is-set-but-client-key-is-missing-both","errorCode":null,"errorMessage":"client_cert is set but client_key is missing (both are required for mutual TLS)","messagePattern":"client_cert is set but client_key is missing \\(both are required for mutual TLS\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-config/src/schema.rs","lineNumber":16619,"sourceCode":"    /// - at least one routing key is bound\n    pub fn validate(&self) -> anyhow::Result<()> {\n        let is_tls = self.amqp_url.starts_with(\"amqps://\");\n        let is_plain = self.amqp_url.starts_with(\"amqp://\");\n\n        if !is_tls && !is_plain {\n            anyhow::bail!(\n                \"amqp_url must start with 'amqp://' or 'amqps://', got: {}\",\n                self.amqp_url\n            );\n        }\n\n        if is_tls && self.ca_cert.is_none() {\n            anyhow::bail!(\"amqps:// requires ca_cert to verify the broker\");\n        }\n\n        match (self.client_cert.is_some(), self.client_key.is_some()) {\n            (true, false) => {\n                anyhow::bail!(\n                    \"client_cert is set but client_key is missing (both are required for mutual TLS)\"\n                )\n            }\n            (false, true) => {\n                anyhow::bail!(\n                    \"client_key is set but client_cert is missing (both are required for mutual TLS)\"\n                )\n            }\n            _ => {}\n        }\n\n        if self.exchange.is_empty() {\n            validation_bail!(RequiredFieldEmpty, \"exchange\", \"exchange must not be empty\");\n        }\n\n        if self.routing_keys.is_empty() {\n            anyhow::bail!(\"at least one routing key must be configured\");\n        }","sourceCodeStart":16601,"sourceCodeEnd":16637,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-config/src/schema.rs#L16601-L16637","documentation":"Broker mutual TLS on the AMQP channel needs a certificate/key pair. The validator requires `client_cert` and `client_key` to be supplied together; setting only `client_cert` produces an incomplete identity that could never complete an mTLS handshake, so the config is rejected up front.","triggerScenarios":"`client_cert = \"/etc/zeroclaw/client.pem\"` with no `client_key`; renaming or moving the key file and updating only the cert path; generating a new cert and forgetting to re-point the key.","commonSituations":"Setting up Fedora Messaging, which mandates client certificates; ops runbooks that rotate the cert but not the key path reference; copy-pasting half of an mTLS example block.","solutions":["Add the matching key: `client_key = \"/etc/zeroclaw/client.key\"`.","Confirm both paths exist and the key matches the cert (`openssl x509 -noout -modulus` vs key modulus, or `openssl verify`).","If mTLS is not needed, remove `client_cert` entirely (plain `amqps://` with just `ca_cert` is valid)."],"exampleFix":"# before\nclient_cert = \"/etc/zeroclaw/client.pem\"\n# client_key missing\n\n# after\nclient_cert = \"/etc/zeroclaw/client.pem\"\nclient_key = \"/etc/zeroclaw/client.key\"","handlingStrategy":"validation","validationCode":"anyhow::ensure!(\n    cfg.client_cert.is_some() == cfg.client_key.is_some(),\n    \"client_cert and client_key must be set together\"\n);","typeGuard":"fn mtls_pair_consistent(cert: &Option<PathBuf>, key: &Option<PathBuf>) -> bool {\n    cert.is_some() == key.is_some()\n}","tryCatchPattern":null,"preventionTips":["Always edit client_cert and client_key as a pair.","Verify the pair matches (openssl modulus check) after any rotation.","If mTLS is unnecessary, omit both fields rather than one."],"tags":["amqp","mtls","config","certificates","zeroclaw"],"backgroundTag":"mtls-keypair-incomplete","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}