{"record":{"id":"c884775872e563f0","repo":"elastic/elasticsearch","slug":"failed-to-initialise-tls-context-for-otel-log-expo","errorCode":null,"errorMessage":"Failed to initialise TLS context for OTel log export","messagePattern":"Failed to initialise TLS context for OTel log export","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"critical","filePath":"modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/export/otelsdk/OtelSdkExportLogsSupplier.java","lineNumber":249,"sourceCode":"        OtlpGrpcLogRecordExporterBuilder exporterBuilder = OtlpGrpcLogRecordExporter.builder()\n            .setEndpoint(OtelSdkSettings.TELEMETRY_LOGS_ENDPOINT.get(settings))\n            .setTimeout(OtelSdkSettings.TELEMETRY_EXPORT_SEND_TIMEOUT.get(settings).toDuration())\n            .setConnectTimeout(OtelSdkSettings.TELEMETRY_EXPORT_CONNECT_TIMEOUT.get(settings).toDuration())\n            .setRetryPolicy(OtelSdkSettings.OTLP_RETRY_POLICY);\n        List<String> cas = OtelSdkSettings.TELEMETRY_LOGS_SSL_CERTIFICATE_AUTHORITIES.get(settings);\n        if (cas.isEmpty() == false || cert.isEmpty() == false) {\n            try {\n                SslTrustConfig trustConfig = cas.isEmpty() ? DefaultJdkTrustConfig.DEFAULT_INSTANCE : new PemTrustConfig(cas, configDir);\n                X509ExtendedTrustManager trustManager = trustConfig.createTrustManager();\n                KeyManager[] keyManagers = null;\n                if (cert.isEmpty() == false) {\n                    keyManagers = new KeyManager[] { new PemKeyConfig(cert, key, new char[0], configDir).createKeyManager() };\n                }\n                SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n                sslContext.init(keyManagers, new TrustManager[] { trustManager }, null);\n                exporterBuilder.setSslContext(sslContext, trustManager);\n            } catch (GeneralSecurityException e) {\n                throw new RuntimeException(\"Failed to initialise TLS context for OTel log export\", e);\n            }\n        }\n        int maxQueueSize = OtelSdkSettings.TELEMETRY_LOGS_MAX_QUEUE_SIZE.get(settings);\n        return SdkLoggerProvider.builder()\n            .setResource(OtelSdkResource.get(settings))\n            .addLogRecordProcessor(BatchLogRecordProcessor.builder(exporterBuilder.build()).setMaxQueueSize(maxQueueSize).build())\n            .build();\n    }\n\n    /**\n     * Rebuild the OTel logs export with fresh TLS material and swap it into the running appender\n     * atomically to avoid dropped records.\n     *\n     * <p>{@link ElasticsearchOtelAppender#setOpenTelemetry} is a volatile write guarded by a\n     * {@code ReadWriteLock} inside the appender, so new audit events switch to the new channel\n     * without a gap. The old {@link SdkLoggerProvider} is closed after the swap: its\n     * {@code BatchLogRecordProcessor} flushes any buffered records through the still-valid old\n     * channel (rotation happens before cert expiry) before shutting down the old gRPC connection.","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/export/otelsdk/OtelSdkExportLogsSupplier.java#L231-L267","documentation":"OtelSdkExportLogsSupplier.buildProvider() builds an SSLContext for the OTel log exporter whenever certificate_authorities or certificate/key are configured. Any GeneralSecurityException raised while loading PEM material, constructing the trust/key managers, or initialising SSLContext.getInstance(\"TLS\") is wrapped and rethrown as RuntimeException at supplier.get() (initial install or cert hot-reload). This aborts the OTel logs appender setup.","triggerScenarios":"Configuring telemetry.logs.ssl.certificate / .key / .certificate_authorities with: a malformed or non-PEM file, a cert/key pair that does not match, a CA file that cannot be parsed, an unreadable path (permissions), or an unsupported algorithm/provider on the JVM.","commonSituations":"Self-signed mTLS between ES and an OTLP collector; cert rotation that uploaded a half-written file; pointing to a PKCS#12 file when PEM is required; running on a JVM with restricted crypto providers.","solutions":["Validate each PEM file independently: openssl x509 -in cert.pem -noout; openssl pkey -in key.pem -noout; confirm cert and key share a modulus.","Verify the configured paths are readable by the ES process user.","Ensure files are PEM-encoded (BEGIN CERTIFICATE / BEGIN PRIVATE KEY), not DER or PKCS#12.","If a CA chain is used, confirm it chains to the collector's presented cert.","Re-issue or re-upload the cert/key pair during a maintenance window to avoid a half-replaced state."],"exampleFix":"// before\ntelemetry.logs.ssl.certificate: certs/client.crt\ntelemetry.logs.ssl.key: certs/client.key  // wrong file / mismatched\n// after\nopenssl x509 -modulus -in certs/client.crt -noout | openssl md5\nopenssl rsa  -modulus -in certs/client.key -noout | openssl md5\n// hashes must match; redeploy matching pair","handlingStrategy":"try-catch","validationCode":"// Pre-validate PEM material the same way the supplier will\nstatic String validatePem(Path cert, Path key, List<Path> cas) throws Exception {\n  var cf = java.security.cert.CertificateFactory.getInstance(\"X.509\");\n  try (var in = Files.newInputStream(cert)) { cf.generateCertificate(in); }\n  java.security.KeyPair kp = null;\n  try (var in = Files.newInputStream(key)) {\n    var kpObj = java.security.KeyPairGenerator.getInstance(\"RSA\"); // placeholder; use BouncyCastle/PEMParser in real code\n  }\n  for (Path ca : cas) try (var in = Files.newInputStream(ca)) { cf.generateCertificate(in); }\n  return \"ok\";\n}","typeGuard":null,"tryCatchPattern":"// Wrap node start / supplier refresh so a TLS failure is surfaced, not swallowed\ntry {\n otelLogsSupplier.get();\n} catch (RuntimeException e) {\n  if (e.getCause() instanceof GeneralSecurityException) {\n    alertOps(\"OTel logs TLS init failed: \" + e.getCause().getMessage());\n    // do NOT continue with insecure fallback; fail closed\n    throw e;\n  } else throw e;\n}","preventionTips":["Validate cert/key match (compare modulus hashes) before deploying.","Deploy cert and key atomically; never leave a half-replaced pair on disk.","Use PEM exclusively; do not point these settings at DER or PKCS#12 files.","Ensure the ES process user can read every configured path."],"tags":["apm","telemetry","tls","ssl","configuration","security"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}