From 930b1477ebaa175d867789970e9b846c270d8756 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 1 Sep 2026 17:32:22 -0400 Subject: [PATCH 1/2] fix(trogon-gateway): restore the coverage test job so CI can gate changes Signed-off-by: Yordis Prieto --- rsworkspace/crates/platform/trogon-gateway/src/main.rs | 2 ++ .../platform/trogon-gateway/src/source/slack/socket_mode.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/rsworkspace/crates/platform/trogon-gateway/src/main.rs b/rsworkspace/crates/platform/trogon-gateway/src/main.rs index 12800fcad0..9da12ada83 100644 --- a/rsworkspace/crates/platform/trogon-gateway/src/main.rs +++ b/rsworkspace/crates/platform/trogon-gateway/src/main.rs @@ -219,12 +219,14 @@ async fn serve(resolved: config::ResolvedConfig) -> anyhow::Result<()> { Ok(()) } +#[cfg_attr(coverage, allow(dead_code))] #[derive(Debug, thiserror::Error)] enum NotionVerificationTokenCommandError { #[error("notion integration '{0}' is not configured")] IntegrationNotConfigured(source_integration_id::SourceIntegrationId), } +#[cfg_attr(coverage, allow(dead_code))] async fn notion_verification_token( resolved: &config::ResolvedConfig, integration: &source_integration_id::SourceIntegrationId, diff --git a/rsworkspace/crates/platform/trogon-gateway/src/source/slack/socket_mode.rs b/rsworkspace/crates/platform/trogon-gateway/src/source/slack/socket_mode.rs index dfc1079b78..1dd8a152c7 100644 --- a/rsworkspace/crates/platform/trogon-gateway/src/source/slack/socket_mode.rs +++ b/rsworkspace/crates/platform/trogon-gateway/src/source/slack/socket_mode.rs @@ -5,8 +5,10 @@ use futures_util::{Sink, SinkExt, Stream, StreamExt}; use serde::Deserialize; use tokio_tungstenite::tungstenite::{Error as WebSocketError, Message}; use tracing::{info, warn}; +#[cfg_attr(coverage, allow(unused_imports))] use trogon_nats::jetstream::{ClaimCheckPublisher, JetStreamPublisher, ObjectStorePut}; +#[cfg_attr(coverage, allow(unused_imports))] use super::config::{SlackConfig, SlackSocketModeConfig}; use super::constants::RECONNECT_MAX_DELAY; #[cfg(not(coverage))] From c7f65f28401b7079e97bb18845e2f8d2cdbd7817 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 1 Sep 2026 15:01:03 -0400 Subject: [PATCH 2/2] fix(gateway): stop TLS from depending on which crates got linked Every outbound TLS handshake panicked in production because the dependency graph reaches rustls through both provider features, and rustls will not guess between them. The panic killed the Discord source while liveness and readiness kept answering 200, so the gateway looked healthy while silently ingesting nothing. Signed-off-by: Yordis Prieto --- rsworkspace/Cargo.lock | 2 ++ rsworkspace/Cargo.toml | 1 + .../crates/platform/trogon-gateway/Cargo.toml | 3 +- .../platform/trogon-gateway/src/main.rs | 2 ++ .../trogon-gateway/tests/crypto_provider.rs | 14 +++++++++ .../crates/platform/trogon-std/Cargo.toml | 2 ++ .../crates/platform/trogon-std/src/lib.rs | 4 +++ .../crates/platform/trogon-std/src/tls.rs | 31 +++++++++++++++++++ .../platform/trogon-std/src/tls/tests.rs | 16 ++++++++++ 9 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 rsworkspace/crates/platform/trogon-gateway/tests/crypto_provider.rs create mode 100644 rsworkspace/crates/platform/trogon-std/src/tls.rs create mode 100644 rsworkspace/crates/platform/trogon-std/src/tls/tests.rs diff --git a/rsworkspace/Cargo.lock b/rsworkspace/Cargo.lock index 1df6615828..7c2e9ac9d4 100644 --- a/rsworkspace/Cargo.lock +++ b/rsworkspace/Cargo.lock @@ -7136,6 +7136,7 @@ dependencies = [ "hex", "hmac 0.13.0", "reqwest 0.12.28", + "rustls", "serde", "serde_json", "sha2 0.11.0", @@ -7302,6 +7303,7 @@ dependencies = [ "bytesize", "clap", "opentelemetry", + "rustls", "serde", "serde_json", "thiserror 2.0.19", diff --git a/rsworkspace/Cargo.toml b/rsworkspace/Cargo.toml index 060a02e97c..3a5b1c08fe 100644 --- a/rsworkspace/Cargo.toml +++ b/rsworkspace/Cargo.toml @@ -168,6 +168,7 @@ hmac = "=0.13.0" jsonwebtoken = { version = "=11.0.0", features = ["rust_crypto"] } nats-jwt-rs = "=0.1.1" nkeys = { version = "=0.4.5", features = ["xkeys"] } +rustls = { version = "=0.23.37", default-features = false, features = ["aws-lc-rs", "std"] } rustls-pemfile = "=2.2.0" rustls-pki-types = { version = "=1.15.1", default-features = false, features = ["std"] } rustls-webpki = { version = "=0.103.13", default-features = false, features = ["std", "alloc", "ring"] } diff --git a/rsworkspace/crates/platform/trogon-gateway/Cargo.toml b/rsworkspace/crates/platform/trogon-gateway/Cargo.toml index 683606c1bc..a72dc64be1 100644 --- a/rsworkspace/crates/platform/trogon-gateway/Cargo.toml +++ b/rsworkspace/crates/platform/trogon-gateway/Cargo.toml @@ -39,10 +39,11 @@ twilight-model = { workspace = true } trogon-nats = { workspace = true } trogon-semconv = { workspace = true } trogon-service-config = { workspace = true } -trogon-std = { workspace = true, features = ["clap", "signal", "telemetry-http"] } +trogon-std = { workspace = true, features = ["clap", "signal", "telemetry-http", "tls"] } url = "2.5" [dev-dependencies] +rustls = { workspace = true } tempfile = { workspace = true } time = { workspace = true } tower = "0.5" diff --git a/rsworkspace/crates/platform/trogon-gateway/src/main.rs b/rsworkspace/crates/platform/trogon-gateway/src/main.rs index 9da12ada83..2906e097d9 100644 --- a/rsworkspace/crates/platform/trogon-gateway/src/main.rs +++ b/rsworkspace/crates/platform/trogon-gateway/src/main.rs @@ -50,6 +50,8 @@ type SourceResult = (&'static str, anyhow::Result<()>); #[cfg(not(coverage))] #[tokio::main] async fn main() -> anyhow::Result<()> { + trogon_std::tls::install_default_crypto_provider()?; + let cli = CliArgs::::new().parse_args(); let resolved = config::load_with_overrides(cli.runtime.config.as_deref(), &cli.runtime.nats)?; diff --git a/rsworkspace/crates/platform/trogon-gateway/tests/crypto_provider.rs b/rsworkspace/crates/platform/trogon-gateway/tests/crypto_provider.rs new file mode 100644 index 0000000000..f1c45dfd69 --- /dev/null +++ b/rsworkspace/crates/platform/trogon-gateway/tests/crypto_provider.rs @@ -0,0 +1,14 @@ +//! Guards the dependency graph that made every TLS handshake panic at runtime. +//! +//! `trogon-gateway` reaches rustls through both provider features: `aws-lc-rs` +//! via the OTLP exporter and `ring` via async-nats and twilight. rustls refuses +//! to pick one, so anything building a TLS client panicked instead of +//! connecting. This fails if the explicit provider install stops happening or a +//! dependency change reintroduces the ambiguity. + +#[test] +fn tls_clients_build_once_the_provider_is_installed() { + assert!(trogon_std::tls::install_default_crypto_provider().is_ok()); + + let _ = rustls::ClientConfig::builder(); +} diff --git a/rsworkspace/crates/platform/trogon-std/Cargo.toml b/rsworkspace/crates/platform/trogon-std/Cargo.toml index 4d551ddf25..ad0136716c 100644 --- a/rsworkspace/crates/platform/trogon-std/Cargo.toml +++ b/rsworkspace/crates/platform/trogon-std/Cargo.toml @@ -12,6 +12,7 @@ test-support = ["dep:tracing", "dep:tracing-subscriber"] clap = ["dep:clap"] uuid = ["dep:uuid"] signal = ["dep:tokio", "dep:tracing"] +tls = ["dep:rustls"] telemetry-http = [ "dep:axum", "dep:opentelemetry", @@ -27,6 +28,7 @@ axum = { workspace = true, optional = true } bytesize = "2.3.1" clap = { workspace = true, optional = true } opentelemetry = { workspace = true, optional = true } +rustls = { workspace = true, optional = true } trogon-semconv = { workspace = true, optional = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/rsworkspace/crates/platform/trogon-std/src/lib.rs b/rsworkspace/crates/platform/trogon-std/src/lib.rs index 8847ebdab1..8c4e52b211 100644 --- a/rsworkspace/crates/platform/trogon-std/src/lib.rs +++ b/rsworkspace/crates/platform/trogon-std/src/lib.rs @@ -51,6 +51,8 @@ pub mod signal; #[cfg(feature = "telemetry-http")] pub mod telemetry; pub mod time; +#[cfg(feature = "tls")] +pub mod tls; #[cfg(feature = "uuid")] pub mod uuid; @@ -71,5 +73,7 @@ pub use json::{JsonSerialize, StdJsonSerialize}; pub use log_capture::{CapturedEvent, CapturedEvents}; pub use secret_string::{EmptySecretError, SecretString}; pub use time::{EpochClock, GetElapsed, GetNow, SystemClock}; +#[cfg(feature = "tls")] +pub use tls::{CryptoProviderAlreadyInstalledError, install_default_crypto_provider}; #[cfg(feature = "uuid")] pub use uuid::{NowV7, UuidV7Generator}; diff --git a/rsworkspace/crates/platform/trogon-std/src/tls.rs b/rsworkspace/crates/platform/trogon-std/src/tls.rs new file mode 100644 index 0000000000..e44c58304c --- /dev/null +++ b/rsworkspace/crates/platform/trogon-std/src/tls.rs @@ -0,0 +1,31 @@ +//! Process-wide TLS provider selection for rustls. +//! +//! rustls only derives its process-level [`rustls::crypto::CryptoProvider`] from +//! crate features when exactly one provider feature is enabled. A dependency +//! graph that reaches both `aws-lc-rs` and `ring` leaves the choice ambiguous, +//! and rustls panics on the first handshake rather than guessing. Installing the +//! provider explicitly removes the dependency on feature unification, so adding +//! a dependency cannot change which cryptography a binary uses. +//! +//! Call [`install_default_crypto_provider`] before the first TLS handshake, +//! ahead of any client construction or task spawn. + +/// A rustls [`rustls::crypto::CryptoProvider`] was already installed for this +/// process, so the caller has no guarantee which cryptography it now uses. +#[derive(Debug, thiserror::Error)] +#[error("a rustls CryptoProvider was already installed for this process")] +pub struct CryptoProviderAlreadyInstalledError; + +/// Installs aws-lc-rs as the process-wide rustls +/// [`rustls::crypto::CryptoProvider`]. +/// +/// Fails rather than accepting whichever provider got there first, so a second +/// installer cannot silently downgrade the process to different cryptography. +pub fn install_default_crypto_provider() -> Result<(), CryptoProviderAlreadyInstalledError> { + rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .map_err(|_| CryptoProviderAlreadyInstalledError) +} + +#[cfg(test)] +mod tests; diff --git a/rsworkspace/crates/platform/trogon-std/src/tls/tests.rs b/rsworkspace/crates/platform/trogon-std/src/tls/tests.rs new file mode 100644 index 0000000000..1d90152c62 --- /dev/null +++ b/rsworkspace/crates/platform/trogon-std/src/tls/tests.rs @@ -0,0 +1,16 @@ +use super::install_default_crypto_provider; +use rustls::crypto::CryptoProvider; + +#[test] +fn installs_a_process_wide_provider() { + let _ = install_default_crypto_provider(); + + assert!(CryptoProvider::get_default().is_some()); +} + +#[test] +fn refuses_to_replace_an_installed_provider() { + let _ = install_default_crypto_provider(); + + assert!(install_default_crypto_provider().is_err()); +}