{"record":{"id":"4e4dd51abf360493","repo":"EpicGames/lore","slug":"maintenance-tls-is-partially-configured-cert-file-and-pkey","errorCode":null,"errorMessage":"Maintenance TLS is partially configured: cert_file and pkey_file must both be set or both be absent","messagePattern":"Maintenance TLS is partially configured: cert_file and pkey_file must both be set or both be absent","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"lore-server/src/grpc/server.rs","lineNumber":952,"sourceCode":"    addr: SocketAddr,\n    cert_path: Option<PathBuf>,\n    key_path: Option<PathBuf>,\n    cert_chain_path: Option<PathBuf>,\n    signal: impl Future<Output = ()> + Send + 'static,\n) -> Result<()> {\n    let environment_svc = LoreEnvironmentService::maintenance(environment.clone());\n    let environment_v1_svc = LoreEnvironmentV1Service::maintenance(environment);\n\n    let mut server = Server::builder();\n    match (cert_path, key_path) {\n        (Some(cert_path), Some(key_path)) => {\n            info!(\"Loading maintenance TLS certs - cert: {cert_path:?} key: {key_path:?}\");\n            let tls_config = build_server_tls_config(cert_path, key_path, cert_chain_path)?;\n            server = server.tls_config(tls_config)?;\n        }\n        (None, None) => {}\n        _ => {\n            return Err(anyhow!(\n                \"Maintenance TLS is partially configured: cert_file and pkey_file must both be set or both be absent\"\n            ));\n        }\n    }\n\n    // Served from net like the other listeners. No `CoreHopLayer`: both handlers\n    // only return UNAVAILABLE, so there is nothing to keep off net.\n    let router = server\n        .add_service(EnvironmentServiceServer::new(environment_svc))\n        .add_service(environment_v1_server::EnvironmentServiceServer::new(\n            environment_v1_svc,\n        ));\n    lore_spawn_net!(async move { router.serve_with_shutdown(addr, signal).await }).await??;\n\n    Ok(())\n}\n\n#[cfg(test)]","sourceCodeStart":934,"sourceCodeEnd":970,"githubUrl":"https://github.com/EpicGames/lore/blob/074eb0b0d1194c997d7cf28b55519e3e197b3e23/lore-server/src/grpc/server.rs#L934-L970","documentation":"lore-server refuses to start the maintenance gRPC server when TLS is half-configured: only one of cert_file and pkey_file was provided. A certificate without its private key (or vice versa) cannot form a usable TLS identity, so the code rejects it explicitly instead of failing later at bind time.","triggerScenarios":"Setting maintenance_tls.cert_file without maintenance_tls.pkey_file (or the reverse) in the server config before calling the gRPC server setup; the match arm (Some,None)/(None,Some) in the maintenance TLS setup returns this anyhow error.","commonSituations":"Hand-edited TOML/YAML config where one line was commented out; secret-management systems injecting only one of the two paths; templated configs where one mount failed; partial migration from plaintext to TLS.","solutions":["Set both cert_file and pkey_file in the maintenance TLS config, or remove both to run without TLS","Check the config file/template for a missing or commented-out line","Verify secret/file mounts or env vars delivering both paths are present at startup"],"exampleFix":"// before\ntls = { cert_file = \"/etc/certs/server.crt\" }\n// after\ntls = { cert_file = \"/etc/certs/server.crt\", pkey_file = \"/etc/certs/server.key\" }","handlingStrategy":"validation","validationCode":"fn validate_maintenance_tls(tls: &MaintenanceTlsConfig) -> Result<(), String> {\n    match (tls.cert_file.as_deref(), tls.pkey_file.as_deref()) {\n        (Some(_), Some(_)) | (None, None) => Ok(()),\n        _ => Err(\"cert_file and pkey_file must both be set or both absent\".into()),\n    }\n}","typeGuard":"fn tls_config_complete(tls: &MaintenanceTlsConfig) -> bool {\n    tls.cert_file.is_some() == tls.pkey_file.is_some()\n}","tryCatchPattern":"match server::launch(cfg) {\n    Err(e) if e.to_string().contains(\"partially configured\") => eprintln!(\"fix TLS config: set both cert_file and pkey_file\"),\n    Err(e) => return Err(e),\n    Ok(s) => s,\n}","preventionTips":["Validate TLS config completeness at config-load time","Keep cert/key pairs defined together in templates and secret manifests","Add a startup smoke test that loads the production config"],"tags":["rust","grpc","tls","configuration"],"backgroundTag":"conflicting-config-options","analyzedSha":"074eb0b0d1194c997d7cf28b55519e3e197b3e23","analyzedAt":"2026-09-13T09:00:57.509Z","contentChangedAt":"2026-09-13T09:00:57.509Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}