Sfoglia il codice sorgente

chore: Fix cargo audit warnings up to but excluding behaviour changes (#456)

* Fix libgit2-sys vuln by upgrading vergen

* Remove atty dependency (unmaintained)

* Update clap to version 4

* Remove rustls-pemfile dependency (unmaintained)

* Fix rustls issues

Restore rustls-native-certs and upgrade version
Switch to tokio_rustlrs pki_types

* Run cargo fmt

* Test single threading tests

* Fix use of rustls-native-certs

* Update packages to resolve vulnerabilities
Stephen Tierney 4 giorni fa
parent
commit
139bda695e
11 ha cambiato i file con 492 aggiunte e 361 eliminazioni
  1. 324 255
      Cargo.lock
  2. 4 9
      Cargo.toml
  3. 35 14
      build.rs
  4. 23 16
      src/cli.rs
  5. 7 2
      src/client.rs
  6. 35 27
      src/helper.rs
  7. 6 2
      src/main.rs
  8. 3 2
      src/server.rs
  9. 44 18
      src/transport/rustls.rs
  10. 2 6
      src/transport/websocket.rs
  11. 9 10
      tests/integration_test.rs

File diff suppressed because it is too large
+ 324 - 255
Cargo.lock


+ 4 - 9
Cargo.toml

@@ -30,7 +30,6 @@ client = []
 native-tls = ["tokio-native-tls"]
 rustls = [
     "tokio-rustls",
-    "rustls-pemfile",
     "rustls-native-certs",
     "p12",
 ]
@@ -87,7 +86,7 @@ codegen-units = 1
 [dependencies]
 tokio = { version = "1", features = ["full"] }
 bytes = { version = "1", features = ["serde"] }
-clap = { version = "3.0", features = ["derive"] }
+clap = { version = "4.0", features = ["derive"] }
 toml = "0.5"
 serde = { version = "1.0", features = ["derive"] }
 anyhow = "1.0"
@@ -110,7 +109,6 @@ notify = { version = "5.0.0-pre.13", optional = true }
 console-subscriber = { version = "0.1", optional = true, features = [
     "parking_lot",
 ] }
-atty = "0.2"
 async-http-proxy = { version = "1.2", features = [
     "runtime-tokio",
     "basic-auth",
@@ -123,18 +121,15 @@ futures-core = { version = "0.3.28", optional = true }
 futures-sink = { version = "0.3.28", optional = true }
 tokio-native-tls = { version = "0.3", optional = true }
 tokio-rustls = { version = "0.25", optional = true }
-rustls-native-certs = { version = "0.7", optional = true }
-rustls-pemfile = { version = "2.0", optional = true }
+rustls-native-certs = { version = "0.8.3", optional = true }
 p12 = { version = "0.6.3", optional = true }
 
 [target.'cfg(target_env = "musl")'.dependencies]
 openssl = { version = "0.10", features = ["vendored"], optional = true }
 
 [build-dependencies]
-vergen = { version = "7.4.2", default-features = false, features = [
+vergen-gitcl = { version = "9.1.0", default-features = false, features = [
     "build",
-    "git",
-    "cargo",
+    "cargo"
 ] }
 anyhow = "1.0"
-

+ 35 - 14
build.rs

@@ -1,19 +1,40 @@
 use anyhow::Result;
-use vergen::{vergen, Config, SemverKind};
+use vergen_gitcl::{BuildBuilder, CargoBuilder, Emitter, GitclBuilder};
 
 fn main() -> Result<()> {
-    let mut config = Config::default();
-    // Change the SEMVER output to the lightweight variant
-    *config.git_mut().semver_kind_mut() = SemverKind::Lightweight;
-    // Add a `-dirty` flag to the SEMVER output
-    *config.git_mut().semver_dirty_mut() = Some("-dirty");
-    // Generate the instructions
-    if let Err(e) = vergen(config) {
-        eprintln!("error occurred while generating instructions: {:?}", e);
-        let mut config = Config::default();
-        *config.git_mut().enabled_mut() = false;
-        vergen(config)
-    } else {
-        Ok(())
+    // Manually define compile time env vars that were auto
+    // generated by earlier versions of vergen.
+    println!(
+        "cargo:rustc-env=VERGEN_BUILD_SEMVER={}",
+        env!("CARGO_PKG_VERSION")
+    );
+    println!(
+        "cargo:rustc-env=VERGEN_CARGO_PROFILE={}",
+        std::env::var("PROFILE").unwrap()
+    );
+
+    let build = BuildBuilder::all_build()?;
+    let cargo = CargoBuilder::all_cargo()?;
+
+    let mut emitter = Emitter::default();
+    emitter.add_instructions(&build)?;
+    emitter.add_instructions(&cargo)?;
+
+    match GitclBuilder::default()
+        .describe(true, true, None)
+        .sha(false)
+        .commit_timestamp(true)
+        .branch(true)
+        .build()
+    {
+        Ok(git) => {
+            emitter.add_instructions(&git)?;
+        }
+        Err(e) => {
+            println!("cargo:warning=vergen-gitcl failed, building without git info: {e}");
+        }
     }
+
+    emitter.emit()?;
+    Ok(())
 }

+ 23 - 16
src/cli.rs

@@ -1,7 +1,7 @@
-use clap::{AppSettings, ArgGroup, Parser};
+use clap::{ArgGroup, Parser, ValueEnum};
 use lazy_static::lazy_static;
 
-#[derive(clap::ArgEnum, Clone, Debug, Copy)]
+#[derive(ValueEnum, Clone, Debug, Copy)]
 pub enum KeypairType {
     X25519,
     X448,
@@ -9,7 +9,7 @@ pub enum KeypairType {
 
 lazy_static! {
     static ref VERSION: &'static str =
-        option_env!("VERGEN_GIT_SEMVER_LIGHTWEIGHT").unwrap_or(env!("VERGEN_BUILD_SEMVER"));
+        option_env!("VERGEN_GIT_DESCRIBE").unwrap_or(env!("VERGEN_BUILD_SEMVER"));
     static ref LONG_VERSION: String = format!(
         "
 Build Timestamp:     {}
@@ -33,36 +33,43 @@ cargo Features:      {}
 }
 
 #[derive(Parser, Debug, Default, Clone)]
-#[clap(
+#[command(
     about,
-    version(*VERSION),
-    long_version(LONG_VERSION.as_str()),
-    setting(AppSettings::DeriveDisplayOrder)
+    version = *VERSION,
+    long_version = LONG_VERSION.as_str(),
+    // AppSettings::DeriveDisplayOrder has no direct v4 enum replacement.
+    // clap v4 generally respects declaration order; keep or add explicit display_order if needed.
 )]
-#[clap(group(
-            ArgGroup::new("cmds")
-                .required(true)
-                .args(&["CONFIG", "genkey"]),
-        ))]
+#[command(group(
+    ArgGroup::new("cmds")
+        .required(true)
+        .args(&["CONFIG", "genkey"]),
+))]
 pub struct Cli {
     /// The path to the configuration file
     ///
     /// Running as a client or a server is automatically determined
     /// according to the configuration file.
-    #[clap(parse(from_os_str), name = "CONFIG")]
+    #[arg(value_name = "CONFIG")]
     pub config_path: Option<std::path::PathBuf>,
 
     /// Run as a server
-    #[clap(long, short, group = "mode")]
+    #[arg(long, short, group = "mode")]
     pub server: bool,
 
     /// Run as a client
-    #[clap(long, short, group = "mode")]
+    #[arg(long, short, group = "mode")]
     pub client: bool,
 
     /// Generate a keypair for the use of the noise protocol
     ///
     /// The DH function to use is x25519
-    #[clap(long, arg_enum, value_name = "CURVE")]
+    #[arg(
+        long,
+        value_enum,
+        value_name = "CURVE",
+        num_args = 0..=1,
+        default_missing_value = "x25519"
+    )]
     pub genkey: Option<Option<KeypairType>>,
 }

+ 7 - 2
src/client.rs

@@ -227,7 +227,8 @@ async fn run_data_channel<T: Transport>(args: Arc<RunDataChannelArgs<T>>) -> Res
             if args.service.service_type != ServiceType::Udp {
                 bail!("Expect UDP traffic. Please check the configuration.")
             }
-            run_data_channel_for_udp::<T>(conn, &args.service.local_addr, args.service.prefer_ipv6).await?;
+            run_data_channel_for_udp::<T>(conn, &args.service.local_addr, args.service.prefer_ipv6)
+                .await?;
         }
     }
     Ok(())
@@ -255,7 +256,11 @@ async fn run_data_channel_for_tcp<T: Transport>(
 type UdpPortMap = Arc<RwLock<HashMap<SocketAddr, mpsc::Sender<Bytes>>>>;
 
 #[instrument(skip(conn))]
-async fn run_data_channel_for_udp<T: Transport>(conn: T::Stream, local_addr: &str, prefer_ipv6: bool) -> Result<()> {
+async fn run_data_channel_for_udp<T: Transport>(
+    conn: T::Stream,
+    local_addr: &str,
+    prefer_ipv6: bool,
+) -> Result<()> {
     debug!("New data channel starts forwarding");
 
     let port_map: UdpPortMap = Arc::new(RwLock::new(HashMap::new()));

+ 35 - 27
src/helper.rs

@@ -65,7 +65,6 @@ pub fn host_port_pair(s: &str) -> Result<(&str, u16)> {
 
 /// Create a UDP socket and connect to `addr`
 pub async fn udp_connect<A: ToSocketAddrs>(addr: A, prefer_ipv6: bool) -> Result<UdpSocket> {
-
     let (socket_addr, bind_addr);
 
     match prefer_ipv6 {
@@ -76,7 +75,7 @@ pub async fn udp_connect<A: ToSocketAddrs>(addr: A, prefer_ipv6: bool) -> Result
                 SocketAddr::V4(_) => "0.0.0.0:0",
                 SocketAddr::V6(_) => ":::0",
             };
-        },
+        }
         true => {
             let all_host_addresses: Vec<SocketAddr> = lookup_host(addr).await?.collect();
 
@@ -85,7 +84,7 @@ pub async fn udp_connect<A: ToSocketAddrs>(addr: A, prefer_ipv6: bool) -> Result
                 Some(socket_addr_ipv6) => {
                     socket_addr = *socket_addr_ipv6;
                     bind_addr = ":::0";
-                },
+                }
                 None => {
                     let socket_addr_ipv4 = all_host_addresses.iter().find(|x| x.is_ipv4());
                     match socket_addr_ipv4 {
@@ -194,7 +193,10 @@ where
     Ok(())
 }
 
-pub fn generate_proxy_protocol_header(s: &TcpStream, proxy_protocol: &str) -> Result<Vec<u8>, anyhow::Error> {
+pub fn generate_proxy_protocol_header(
+    s: &TcpStream,
+    proxy_protocol: &str,
+) -> Result<Vec<u8>, anyhow::Error> {
     let local_addr = s.local_addr()?;
     let remote_addr = s.peer_addr()?;
 
@@ -202,22 +204,31 @@ pub fn generate_proxy_protocol_header(s: &TcpStream, proxy_protocol: &str) -> Re
         "v1" => {
             let proto = if local_addr.is_ipv4() { "TCP4" } else { "TCP6" };
             let header = format!(
-                "PROXY {} {} {} {} {}\r\n", 
-                proto, 
-                remote_addr.ip(), 
-                local_addr.ip(), 
-                remote_addr.port(), 
+                "PROXY {} {} {} {} {}\r\n",
+                proto,
+                remote_addr.ip(),
+                local_addr.ip(),
+                remote_addr.port(),
                 local_addr.port()
             );
 
             Ok(header.into_bytes())
         }
         "v2" => {
-
-            let v2sig: &[u8] = &[0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A];
+            let v2sig: &[u8] = &[
+                0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A,
+            ];
             let ver_cmd = &[0x21]; // 0x21 version 2 and PROXY command
-            let proto = if local_addr.is_ipv4() { &[0x11] } else { &[0x21] }; // 0x11 for TCP IPv4 and 0x21 for TCP IPv6, TODO: support UNIX
-            let addrs_length: &[u8] = if local_addr.is_ipv4() { &[0, 12] } else { &[0, 36] }; // 12 for IPv4 and 36 for IPv6, TOOD: support UNIX
+            let proto = if local_addr.is_ipv4() {
+                &[0x11]
+            } else {
+                &[0x21]
+            }; // 0x11 for TCP IPv4 and 0x21 for TCP IPv6, TODO: support UNIX
+            let addrs_length: &[u8] = if local_addr.is_ipv4() {
+                &[0, 12]
+            } else {
+                &[0, 36]
+            }; // 12 for IPv4 and 36 for IPv6, TOOD: support UNIX
             let src_addr = match remote_addr {
                 SocketAddr::V4(v4) => v4.ip().octets().to_vec(),
                 SocketAddr::V6(v6) => v6.ip().octets().to_vec(),
@@ -226,28 +237,25 @@ pub fn generate_proxy_protocol_header(s: &TcpStream, proxy_protocol: &str) -> Re
                 SocketAddr::V4(v4) => v4.ip().octets().to_vec(),
                 SocketAddr::V6(v6) => v6.ip().octets().to_vec(),
             };
-    
-            let header:Vec<u8> = [
-                v2sig, 
-                ver_cmd, 
-                proto, 
+
+            let header: Vec<u8> = [
+                v2sig,
+                ver_cmd,
+                proto,
                 addrs_length,
                 &src_addr,
                 &dst_addr,
                 &remote_addr.port().to_be_bytes(),
-                &local_addr.port().to_be_bytes()
-                ].concat();
-    
+                &local_addr.port().to_be_bytes(),
+            ]
+            .concat();
+
             trace!("Proxy protocol v2 header: {:02x?}", header);
-    
-            Ok(header)
 
-        },
-        _ => {
-            Err(anyhow!("Unknown proxy protocol {}", proxy_protocol))
+            Ok(header)
         }
+        _ => Err(anyhow!("Unknown proxy protocol {}", proxy_protocol)),
     }
-
 }
 
 #[cfg(test)]

+ 6 - 2
src/main.rs

@@ -2,6 +2,10 @@ use anyhow::Result;
 use clap::Parser;
 use rathole::{run, Cli};
 use tokio::{signal, sync::broadcast};
+
+#[cfg(not(feature = "console"))]
+use std::io::{self, IsTerminal};
+
 #[cfg(not(feature = "console"))]
 use tracing_subscriber::EnvFilter;
 
@@ -31,14 +35,14 @@ async fn main() -> Result<()> {
     }
     #[cfg(not(feature = "console"))]
     {
-        let is_atty = atty::is(atty::Stream::Stdout);
+        let ansi = io::stdout().is_terminal();
 
         let level = "info"; // if RUST_LOG not present, use `info` level
         tracing_subscriber::fmt()
             .with_env_filter(
                 EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::from(level)),
             )
-            .with_ansi(is_atty)
+            .with_ansi(ansi)
             .init();
     }
 

+ 3 - 2
src/server.rs

@@ -649,12 +649,13 @@ async fn run_tcp_connection_pool<T: Transport>(
                     let proxy_proto = proxy_protocol.clone();
                     tokio::spawn(async move {
                         if !proxy_proto.is_empty() {
-                            let proxy_proto_header = generate_proxy_protocol_header(&visitor, &proxy_proto);
+                            let proxy_proto_header =
+                                generate_proxy_protocol_header(&visitor, &proxy_proto);
                             match proxy_proto_header {
                                 Ok(header) => {
                                     let _ = ch.write_all(&header).await;
                                     let _ = ch.flush().await;
-                                },
+                                }
                                 Err(e) => {
                                     error!("Failed to generate proxy protocol header: {}", e);
                                 }

+ 44 - 18
src/transport/rustls.rs

@@ -4,16 +4,18 @@ use crate::transport::{AddrMaybeCached, SocketOpts, TcpTransport, Transport};
 use std::fmt::Debug;
 use std::fs;
 use std::net::SocketAddr;
+use std::path::Path;
 use std::sync::Arc;
 use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
+use tokio_rustls::rustls::pki_types::pem::PemObject;
 use tokio_rustls::rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer, ServerName};
+use tokio_rustls::rustls::{ClientConfig, RootCertStore, ServerConfig};
+pub(crate) use tokio_rustls::TlsStream;
+use tokio_rustls::{TlsAcceptor, TlsConnector};
 
 use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use p12::PFX;
-use tokio_rustls::rustls::{ClientConfig, RootCertStore, ServerConfig};
-pub(crate) use tokio_rustls::TlsStream;
-use tokio_rustls::{TlsAcceptor, TlsConnector};
 
 pub struct TlsTransport {
     tcp: TcpTransport,
@@ -55,24 +57,48 @@ fn load_server_config(config: &TlsConfig) -> Result<Option<ServerConfig>> {
 }
 
 fn load_client_config(config: &TlsConfig) -> Result<Option<ClientConfig>> {
-    let cert = if let Some(path) = config.trusted_root.as_ref() {
-        rustls_pemfile::certs(&mut std::io::BufReader::new(fs::File::open(path).unwrap()))
-            .map(|cert| cert.unwrap())
-            .next()
-            .with_context(|| "Failed to read certificate")?
+    let mut root_certs = RootCertStore::empty();
+
+    if let Some(path) = config.trusted_root.as_deref() {
+        // Parse CERTIFICATE blocks from PEM using rustls-pki-types
+        let iter = CertificateDer::pem_file_iter(path).with_context(|| {
+            format!(
+                "Failed to open/read certificate file {}",
+                Path::new(path).display()
+            )
+        })?;
+
+        let mut added_any = false;
+        for cert in iter {
+            let cert = cert?; // pem::Error -> anyhow
+            root_certs.add(cert.into_owned())?; // add expects owned DER
+            added_any = true;
+        }
+
+        if !added_any {
+            anyhow::bail!(
+                "No CERTIFICATE entries found in PEM file {}",
+                Path::new(path).display()
+            );
+        }
     } else {
-        // read from native
-        match rustls_native_certs::load_native_certs() {
-            Ok(certs) => certs.into_iter().next().unwrap(),
-            Err(e) => {
-                eprintln!("Failed to load native certs: {}", e);
-                return Ok(None);
-            }
+        // New rustls-native-certs API: CertificateResult { certs, errors }
+        let native = rustls_native_certs::load_native_certs();
+
+        for err in &native.errors {
+            eprintln!("Failed to load some native certs: {err}");
         }
-    };
 
-    let mut root_certs = RootCertStore::empty();
-    root_certs.add(cert).unwrap();
+        if native.certs.is_empty() {
+            // allow missing client root_certs (old behaviour)
+            return Ok(None);
+        }
+
+        for cert in native.certs {
+            // Some certs may fail parsing into the store
+            root_certs.add(cert).context("Failed to add native cert")?;
+        }
+    }
 
     Ok(Some(
         ClientConfig::builder()

+ 2 - 6
src/transport/websocket.rs

@@ -94,9 +94,7 @@ impl Stream for StreamWrapper {
         match Pin::new(&mut self.get_mut().inner).poll_next(cx) {
             Poll::Pending => Poll::Pending,
             Poll::Ready(None) => Poll::Ready(None),
-            Poll::Ready(Some(Err(err))) => {
-                Poll::Ready(Some(Err(Error::other(err))))
-            }
+            Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(Error::other(err)))),
             Poll::Ready(Some(Ok(res))) => {
                 if let Message::Binary(b) = res {
                     Poll::Ready(Some(Ok(Bytes::from(b))))
@@ -147,9 +145,7 @@ impl AsyncWrite for WebsocketTunnel {
         buf: &[u8],
     ) -> Poll<Result<usize, std::io::Error>> {
         let sw = self.get_mut().inner.get_mut();
-        ready!(Pin::new(&mut sw.inner)
-            .poll_ready(cx)
-            .map_err(Error::other))?;
+        ready!(Pin::new(&mut sw.inner).poll_ready(cx).map_err(Error::other))?;
 
         match Pin::new(&mut sw.inner).start_send(Message::Binary(buf.to_vec())) {
             Ok(()) => Poll::Ready(Ok(buf.len())),

+ 9 - 10
tests/integration_test.rs

@@ -27,7 +27,6 @@ const PP2_SIG: [u8; 12] = [
     0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A,
 ];
 
-
 #[derive(Clone, Copy, Debug)]
 enum Type {
     Tcp,
@@ -357,7 +356,9 @@ async fn test_proxy_protocol(config_path: &'static str) -> Result<()> {
     Ok(())
 }
 
-async fn read_proxy_protocol_header(rd: &mut BufReader<tokio::net::tcp::OwnedReadHalf>) -> Result<Vec<u8>> {
+async fn read_proxy_protocol_header(
+    rd: &mut BufReader<tokio::net::tcp::OwnedReadHalf>,
+) -> Result<Vec<u8>> {
     // Read 12 bytes to distinguish v2 signature vs v1 ("PROXY ...")
     let mut first12 = [0u8; 12];
     time::timeout(Duration::from_secs(5), rd.read_exact(&mut first12)).await??;
@@ -403,8 +404,12 @@ fn assert_proxy_v2_matches(header: &[u8], local: SocketAddr, peer: SocketAddr) {
             // INET + STREAM, minimum 12 bytes address block
             assert!(len >= 12);
 
-            let src = IpAddr::V4(Ipv4Addr::new(header[16], header[17], header[18], header[19]));
-            let dst = IpAddr::V4(Ipv4Addr::new(header[20], header[21], header[22], header[23]));
+            let src = IpAddr::V4(Ipv4Addr::new(
+                header[16], header[17], header[18], header[19],
+            ));
+            let dst = IpAddr::V4(Ipv4Addr::new(
+                header[20], header[21], header[22], header[23],
+            ));
             let src_port = u16::from_be_bytes([header[24], header[25]]);
             let dst_port = u16::from_be_bytes([header[26], header[27]]);
 
@@ -436,7 +441,6 @@ fn assert_proxy_v2_matches(header: &[u8], local: SocketAddr, peer: SocketAddr) {
     }
 }
 
-
 async fn tcp_echo_hitter_expect_proxy_protocol(addr: &'static str) -> Result<()> {
     let conn = TcpStream::connect(addr).await?;
     let local = conn.local_addr()?;
@@ -478,8 +482,3 @@ async fn tcp_echo_hitter_expect_proxy_protocol(addr: &'static str) -> Result<()>
 
     Ok(())
 }
-
-
-
-
-

Some files were not shown because too many files changed in this diff