Add a checksum telemetry request (#7168)

Conrad Irwin created

We're seeing a bit of nonsense on telemetry. Although the checksum seed
isn't secret per-se, it does make sending nonsense a little more effort.

Release Notes:

- N/A

Change summary

.github/workflows/ci.yml       |  1 
Cargo.lock                     |  2 +
crates/client/Cargo.toml       |  2 +
crates/client/src/telemetry.rs | 38 +++++++++++++++++++++++++++++++----
4 files changed, 38 insertions(+), 5 deletions(-)

Detailed changes

.github/workflows/ci.yml 🔗

@@ -81,6 +81,7 @@ jobs:
       MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
       APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
       APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
+      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
     steps:
       - name: Install Node
         uses: actions/setup-node@v3

Cargo.lock 🔗

@@ -1367,6 +1367,7 @@ dependencies = [
  "image",
  "lazy_static",
  "log",
+ "once_cell",
  "parking_lot 0.11.2",
  "postage",
  "rand 0.8.5",
@@ -1377,6 +1378,7 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "settings",
+ "sha2 0.10.7",
  "smol",
  "sum_tree",
  "sysinfo",

crates/client/Cargo.toml 🔗

@@ -32,6 +32,7 @@ futures.workspace = true
 image = "0.23"
 lazy_static.workspace = true
 log.workspace = true
+once_cell = "1.19.0"
 parking_lot.workspace = true
 postage.workspace = true
 rand.workspace = true
@@ -39,6 +40,7 @@ schemars.workspace = true
 serde.workspace = true
 serde_derive.workspace = true
 serde_json.workspace = true
+sha2 = "0.10"
 smol.workspace = true
 sysinfo.workspace = true
 tempfile.workspace = true

crates/client/src/telemetry.rs 🔗

@@ -4,16 +4,19 @@ use crate::TelemetrySettings;
 use chrono::{DateTime, Utc};
 use futures::Future;
 use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task};
+use once_cell::sync::Lazy;
 use parking_lot::Mutex;
 use release_channel::ReleaseChannel;
 use serde::Serialize;
 use settings::{Settings, SettingsStore};
-use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
+use sha2::{Digest, Sha256};
+use std::io::Write;
+use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
 use sysinfo::{
     CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
 };
 use tempfile::NamedTempFile;
-use util::http::{HttpClient, ZedHttpClient};
+use util::http::{self, HttpClient, Method, ZedHttpClient};
 #[cfg(not(debug_assertions))]
 use util::ResultExt;
 use util::TryFutureExt;
@@ -142,6 +145,13 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(1);
 #[cfg(not(debug_assertions))]
 const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5);
 
+static ZED_CLIENT_CHECKSUM_SEED: Lazy<Vec<u8>> = Lazy::new(|| {
+    option_env!("ZED_CLIENT_CHECKSUM_SEED")
+        .unwrap_or("development-checksum-seed")
+        .as_bytes()
+        .into()
+});
+
 impl Telemetry {
     pub fn new(client: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> {
         let release_channel =
@@ -540,9 +550,27 @@ impl Telemetry {
                         serde_json::to_writer(&mut json_bytes, &request_body)?;
                     }
 
-                    this.http_client
-                        .post_json(&this.http_client.zed_url("/api/events"), json_bytes.into())
-                        .await?;
+                    let mut summer = Sha256::new();
+                    summer.update(&*ZED_CLIENT_CHECKSUM_SEED);
+                    summer.update(&json_bytes);
+                    summer.update(&*ZED_CLIENT_CHECKSUM_SEED);
+                    let mut checksum = String::new();
+                    for byte in summer.finalize().as_slice() {
+                        use std::fmt::Write;
+                        write!(&mut checksum, "{:02x}", byte).unwrap();
+                    }
+
+                    let request = http::Request::builder()
+                        .method(Method::POST)
+                        .uri(&this.http_client.zed_url("/api/events"))
+                        .header("Content-Type", "text/plain")
+                        .header("x-zed-checksum", checksum)
+                        .body(json_bytes.into());
+
+                    let response = this.http_client.send(request?).await?;
+                    if response.status() != 200 {
+                        log::error!("Failed to send events: HTTP {:?}", response.status());
+                    }
                     anyhow::Ok(())
                 }
                 .log_err(),