Repository: denoland/deno
Pull request: feat(unstable): support https otlp endpoints

Support HTTPS protocol for otel exporting. Includes support for `OTEL_EXPORTER_OTLP_CERTIFICATE`, `OTEL_EXPORTER_OTLP_CLIENT_KEY`, and `OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE`

Fixes: https://github.com/denoland/deno/issues/27703

Changed files:
filename: Cargo.lock
patch: @@ -2421,8 +2421,10 @@ dependencies = [
  "async-trait",
  "deno_core",
  "deno_error",
+ "deno_tls",
  "http-body-util",
  "hyper 1.4.1",
+ "hyper-rustls",
  "hyper-util",
  "log",
  "once_cell",

filename: ext/telemetry/Cargo.toml
patch: @@ -17,8 +17,10 @@ path = "lib.rs"
 async-trait.workspace = true
 deno_core.workspace = true
 deno_error.workspace = true
+deno_tls.workspace = true
 http-body-util.workspace = true
 hyper.workspace = true
+hyper-rustls.workspace = true
 hyper-util.workspace = true
 log.workspace = true
 once_cell.workspace = true

filename: ext/telemetry/lib.rs
patch: @@ -477,10 +477,17 @@ mod hyper_client {
   use std::task::Poll;
   use std::task::{self};
 
+  use deno_tls::create_client_config;
+  use deno_tls::load_certs;
+  use deno_tls::load_private_keys;
+  use deno_tls::SocketUse;
+  use deno_tls::TlsKey;
+  use deno_tls::TlsKeys;
   use http_body_util::BodyExt;
   use http_body_util::Full;
   use hyper::body::Body as HttpBody;
   use hyper::body::Frame;
+  use hyper_rustls::HttpsConnector;
   use hyper_util::client::legacy::connect::HttpConnector;
   use hyper_util::client::legacy::Client;
   use opentelemetry_http::Bytes;
@@ -494,14 +501,41 @@ mod hyper_client {
   // same as opentelemetry_http::HyperClient except it uses OtelSharedRuntime
   #[derive(Debug, Clone)]
   pub struct HyperClient {
-    inner: Client<HttpConnector, Body>,
+    inner: Client<HttpsConnector<HttpConnector>, Body>,
   }
 
   impl HyperClient {
-    pub fn new() -> Self {
-      Self {
-        inner: Client::builder(OtelSharedRuntime).build(HttpConnector::new()),
-      }
+    pub fn new() -> deno_core::anyhow::Result<Self> {
+      let ca_certs = match std::env::var("OTEL_EXPORTER_OTLP_CERTIFICATE") {
+        Ok(path) => vec![std::fs::read(path)?],
+        _ => vec![],
+      };
+
+      let keys = match (
+        std::env::var("OTEL_EXPORTER_OTLP_CLIENT_KEY"),
+        std::env::var("OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE"),
+      ) {
+        (Ok(key_path), Ok(cert_path)) => {
+          let key = std::fs::read(key_path)?;
+          let cert = std::fs::read(cert_path)?;
+
+          let certs = load_certs(&mut std::io::Cursor::new(cert))?;
+          let key = load_private_keys(&key)?.into_iter().next().unwrap();
+
+          TlsKeys::Static(TlsKey(certs, key))
+        }
+        _ => TlsKeys::Null,
+      };
+
+      let tls_config =
+        create_client_config(None, ca_certs, None, keys, SocketUse::Http)?;
+      let mut http_connector = HttpConnector::new();
+      http_connector.enforce_http(false);
+      let connector = HttpsConnector::from((http_connector, tls_config));
+
+      Ok(Self {
+        inner: Client::builder(OtelSharedRuntime).build(connector),
+      })
     }
   }
 
@@ -628,7 +662,7 @@ pub fn init(
   // `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable. Additional headers can
   // be specified using `OTEL_EXPORTER_OTLP_HEADERS`.
 
-  let client = hyper_client::HyperClient::new();
+  let client = hyper_client::HyperClient::new()?;
 
   let span_exporter = HttpExporterBuilder::default()
     .with_http_client(client.clone())

filename: tests/specs/cli/otel_basic/main.ts
patch: @@ -8,6 +8,8 @@ const data = {
 
 const server = Deno.serve(
   {
+    key: Deno.readTextFileSync("../../../testdata/tls/localhost.key"),
+    cert: Deno.readTextFileSync("../../../testdata/tls/localhost.crt"),
     port: 0,
     onListen({ port }) {
       const command = new Deno.Command(Deno.execPath(), {
@@ -16,7 +18,8 @@ const server = Deno.serve(
           OTEL_DENO: "true",
           DENO_UNSTABLE_OTEL_DETERMINISTIC: "1",
           OTEL_EXPORTER_OTLP_PROTOCOL: "http/json",
-          OTEL_EXPORTER_OTLP_ENDPOINT: `http://localhost:${port}`,
+          OTEL_EXPORTER_OTLP_ENDPOINT: `https://localhost:${port}`,
+          OTEL_EXPORTER_OTLP_CERTIFICATE: "../../../testdata/tls/RootCA.crt",
         },
         stdout: "null",
       });