Skip to content

[Feature] Support for dropping linux capabilities. #254

@cheako

Description

@cheako

CAP_NET_BIND_SERVICE being the most useful, but this is only needed until bind. I don't know much about this, but the docs for capset(2) says "thread" so I think that means this needs to be done prior to starting tokio(and it's threads).

I looked at this a bit and it looks like reading the config needs to be moved out of async and some other uncomfortable changes. I feel there should be a new type to hold the sockets as they get passed down the call chain and then destructured, also the sockets need to be std::net::{TcpListener, UdpSocket} and latter wrapped in the tokio async types.

These changes are not massive, but it demonstrates why multi-platform codebases are bad. Is it worth having this complexity on platforms like Windows where it will see no benefit? There is an argument to be made for having separate projects.

I was playing with this a bit and then stopped when I started changing rpxy-bin/src/main.rs, here is that diff. It was edited so much it won't apply, my toml setup alphabetizes things and changes whitespace also rust use statements are alphabetized. I also wasn't looking at all for use cases other than my own.

diff --git a/rpxy-lib/Cargo.toml b/rpxy-lib/Cargo.toml
index 8fac76c..5c95418 100644
--- a/rpxy-lib/Cargo.toml
+++ b/rpxy-lib/Cargo.toml
@@ -1,18 +1,20 @@
+[target.'cfg(target_os = "linux")'.dependencies]
+caps = '0.5.5'
diff --git a/rpxy-lib/src/proxy/proxy_main.rs b/rpxy-lib/src/proxy/proxy_main.rs
index 5244ecf..69102c3 100644
--- a/rpxy-lib/src/proxy/proxy_main.rs
+++ b/rpxy-lib/src/proxy/proxy_main.rs
@@ -24,6 +24,14 @@ use std::{net::SocketAddr, sync::Arc, time::Duration};
 use tokio::time::timeout;
 use tokio_util::sync::CancellationToken;
 
+fn drop_capabilities() -> Result<(), caps::errors::CapsError> {
+  #[cfg(target_os = "linux")]
+  {
+    caps::clear(None, caps::CapSet::Permitted)?;
+  }
+  Ok(())
+}
+
 /// Wrapper function to handle request for HTTP/1.1 and HTTP/2
 /// HTTP/3 is handled in proxy_h3.rs which directly calls the message handler
 async fn serve_request<T>(
@@ -120,6 +128,7 @@ where
       let tcp_socket = bind_tcp_socket(&self.listening_on)?;
       let tcp_listener = tcp_socket.listen(self.globals.proxy_config.tcp_listen_backlog)?;
       info!("Start TCP proxy serving with HTTP request for configured host names");
+      let _ = drop_capabilities();
       while let Ok((stream, client_addr)) = tcp_listener.accept().await {
         self.serve_connection(TokioIo::new(stream), client_addr, None);
       }
@@ -131,6 +140,10 @@ where
 
   /// Start with TLS (HTTPS)
   pub(super) async fn start_with_tls(&self, cancel_token: CancellationToken) -> RpxyResult<()> {
+    let udp_socket = super::socket::bind_udp_socket(&self.listening_on)?;
+    let tcp_socket = bind_tcp_socket(&self.listening_on)?;
+    let _ = drop_capabilities();
+
     #[cfg(not(any(feature = "http3-quinn", feature = "http3-s2n")))]
     {
       self.tls_listener_service().await?;
@@ -145,7 +159,7 @@ where
           let cancel_token = cancel_token.clone();
           async move {
             select! {
-              _ = self_clone.tls_listener_service().fuse() => {
+              _ = self_clone.tls_listener_service(tcp_socket).fuse() => {
                 error!("TCP proxy service for TLS exited");
                 cancel_token.cancel();
               },
@@ -159,7 +173,7 @@ where
           let self_clone = self.clone();
           async move {
             select! {
-              _ = self_clone.h3_listener_service().fuse() => {
+              _ = self_clone.h3_listener_service(udp_socket).fuse() => {
                 error!("UDP proxy service for QUIC exited");
                 cancel_token.cancel();
               },
@@ -173,7 +187,7 @@ where
 
         Ok(())
       } else {
-        self.tls_listener_service().await?;
+        self.tls_listener_service(tcp_socket).await?;
         error!("TCP proxy service for TLS exited");
         Ok(())
       }
@@ -181,11 +195,10 @@ where
   }
 
   // TCP Listener Service, i.e., http/2 and http/1.1
-  async fn tls_listener_service(&self) -> RpxyResult<()> {
+  async fn tls_listener_service(&self, tcp_socket: tokio::net::TcpSocket) -> RpxyResult<()> {
     let Some(mut server_crypto_rx) = self.globals.cert_reloader_rx.clone() else {
       return Err(RpxyError::NoCertificateReloader);
     };
-    let tcp_socket = bind_tcp_socket(&self.listening_on)?;
     let tcp_listener = tcp_socket.listen(self.globals.proxy_config.tcp_listen_backlog)?;
     info!("Start TCP proxy serving with HTTPS request for configured host names");
 
diff --git a/rpxy-lib/src/proxy/proxy_quic_quinn.rs b/rpxy-lib/src/proxy/proxy_quic_quinn.rs
index c316ed9..05fd4e8 100644
--- a/rpxy-lib/src/proxy/proxy_quic_quinn.rs
+++ b/rpxy-lib/src/proxy/proxy_quic_quinn.rs
@@ -13,7 +13,7 @@ impl<T> Proxy<T>
 where
   T: Send + Sync + Connect + Clone + 'static,
 {
-  pub(super) async fn h3_listener_service(&self) -> RpxyResult<()> {
+  pub(super) async fn h3_listener_service(&self, udp_socket: std::net::UdpSocket) -> RpxyResult<()> {
     let Some(mut server_crypto_rx) = self.globals.cert_reloader_rx.clone() else {
       return Err(RpxyError::NoCertificateReloader);
     };
@@ -45,7 +45,6 @@ where
     server_config_h3.max_incoming(self.globals.proxy_config.h3_max_concurrent_connections as usize);
 
     // To reuse address
-    let udp_socket = bind_udp_socket(&self.listening_on)?;
     let runtime =
       quinn::default_runtime().ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "No async runtime found"))?;
     let endpoint = Endpoint::new(quinn::EndpointConfig::default(), Some(server_config_h3), udp_socket, runtime)?;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions