[pve-devel] [PATCH pve-xtermjs] termproxy: allow to use unix sockets for auth requests

Dietmar Maurer dietmar at proxmox.com
Tue Jul 23 14:51:22 CEST 2024


Remove ureq, because it does not support unix sockets.

Signed-off-by: Dietmar Maurer <dietmar at proxmox.com>
---
 termproxy/Cargo.toml  |  2 +-
 termproxy/src/cli.rs  | 29 +++++++++++++++++----
 termproxy/src/main.rs | 59 +++++++++++++++++++++++++++++++++----------
 3 files changed, 71 insertions(+), 19 deletions(-)

diff --git a/termproxy/Cargo.toml b/termproxy/Cargo.toml
index a49d6b0..f6d8814 100644
--- a/termproxy/Cargo.toml
+++ b/termproxy/Cargo.toml
@@ -22,4 +22,4 @@ nix = "0.26.1"
 pico-args = "0.4"
 proxmox-io = "1"
 proxmox-lang = "1.1"
-ureq = { version = "2.4", default-features = false, features = [ "gzip" ] }
+form_urlencoded = "1.1"
diff --git a/termproxy/src/cli.rs b/termproxy/src/cli.rs
index cc44655..409cbe1 100644
--- a/termproxy/src/cli.rs
+++ b/termproxy/src/cli.rs
@@ -4,14 +4,15 @@ use std::os::fd::RawFd;
 use anyhow::{bail, Result};
 
 const CMD_HELP: &str = "\
-Usage: proxmox-termproxy [OPTIONS] --path <path> <listen-port> -- <terminal-cmd>...
+Usage: proxmox-termproxy <listen-port> [OPTIONS] --path <path> -- <terminal-cmd>...
 
 Arguments:
   <listen-port>           Port or file descriptor to listen for TCP connections
   <terminal-cmd>...       The command to run connected via a proxied PTY
 
 Options:
-      --authport <authport>       Port to relay auth-request, default 85
+      --authport <authport>       Port or unix socket to relay auth-request,
+                                  default is port 85
       --port-as-fd                Use <listen-port> as file descriptor.
       --path <path>               ACL object path to test <perm> on.
       --perm <perm>               Permission to test.
@@ -40,14 +41,20 @@ impl PortOrFd {
     }
 }
 
+#[derive(Debug)]
+pub enum DaemonAddress {
+    Port(u16),
+    UnixSocket(String),
+}
+
 #[derive(Debug)]
 pub struct Options {
     /// The actual command to run proxied in a pseudo terminal.
     pub terminal_command: Vec<OsString>,
     /// The port or FD that termproxy will listen on for an incoming conection
     pub listen_port: PortOrFd,
-    /// The port of the local privileged daemon that authentication is relayed to. Defaults to `85`
-    pub api_daemon_port: u16,
+    /// The port (or unix socket path) of the local privileged daemon that authentication is relayed to. Defaults to port `85`
+    pub api_daemon_port: DaemonAddress,
     /// The ACL object path the 'acl_permission' is checked on
     pub acl_path: String,
     /// The ACL permission that the ticket, read from the stream, is required to have on 'acl_path'
@@ -81,7 +88,19 @@ impl Options {
         let options = Self {
             terminal_command: terminal_command.unwrap(), // checked above
             listen_port: PortOrFd::from_cli(args.free_from_str()?, args.contains("--port-as-fd"))?,
-            api_daemon_port: args.opt_value_from_str("--authport")?.unwrap_or(85),
+            api_daemon_port: {
+                let authport: Option<String> = args.opt_value_from_str("--authport")?;
+                match authport {
+                    Some(authport) => {
+                        if let Ok(port) = authport.parse::<u16>() {
+                            DaemonAddress::Port(port)
+                        } else {
+                            DaemonAddress::UnixSocket(authport)
+                        }
+                    }
+                    None => DaemonAddress::Port(85),
+                }
+            },
             acl_path: args.value_from_str("--path")?,
             acl_permission: args.opt_value_from_str("--perm")?,
         };
diff --git a/termproxy/src/main.rs b/termproxy/src/main.rs
index c674993..ca4f01b 100644
--- a/termproxy/src/main.rs
+++ b/termproxy/src/main.rs
@@ -1,11 +1,13 @@
 use std::cmp::min;
 use std::collections::HashMap;
 use std::ffi::OsString;
-use std::io::{ErrorKind, Write};
+use std::io::{ErrorKind, Read, Write};
 use std::os::unix::io::{AsRawFd, FromRawFd};
+use std::os::unix::net::UnixStream;
 use std::os::unix::process::CommandExt;
 use std::process::Command;
 use std::time::{Duration, Instant};
+//use std::io::prelude::*;
 
 use anyhow::{bail, format_err, Result};
 use mio::net::{TcpListener, TcpStream};
@@ -145,6 +147,39 @@ fn read_ticket_line(
     }
 }
 
+fn simple_auth_request<S: Read + Write>(mut stream: S, params: &[(&str, &str)]) -> Result<()> {
+    let mut form = form_urlencoded::Serializer::new(String::new());
+
+    for (name, value) in params {
+        form.append_pair(name, value);
+    }
+    let body = form.finish();
+    let raw_body = body.as_bytes();
+
+    let mut raw_request = String::new();
+    raw_request.push_str("POST /api2/json/access/ticket HTTP/1.1\r\n");
+    raw_request.push_str("Connection: close\r\n");
+    raw_request.push_str("User-Agent: termproxy/1.0\r\n");
+    raw_request.push_str("Content-Type: application/x-www-form-urlencoded;charset=UTF-8\r\n");
+    raw_request.push_str(&format!("Content-Length: {}\r\n", raw_body.len()));
+    raw_request.push_str("\r\n");
+
+    stream.write_all(raw_request.as_bytes())?;
+    stream.write_all(raw_body)?;
+    stream.flush()?;
+
+    let mut res: Vec<u8> = Vec::new();
+    stream.read_to_end(&mut res)?;
+
+    let res = String::from_utf8(res)?;
+
+    if res.starts_with("HTTP/1.1 200 OK\r\n") {
+        Ok(())
+    } else {
+        bail!("authentication request failed");
+    }
+}
+
 fn authenticate(username: &[u8], ticket: &[u8], options: &Options, listen_port: u16) -> Result<()> {
     let mut post_fields: Vec<(&str, &str)> = Vec::with_capacity(5);
     post_fields.push(("username", std::str::from_utf8(username)?));
@@ -161,19 +196,17 @@ fn authenticate(username: &[u8], ticket: &[u8], options: &Options, listen_port:
         port_str = listen_port.to_string();
         post_fields.push(("port", &port_str));
     }
-
-    let url = format!(
-        "http://localhost:{}/api2/json/access/ticket",
-        options.api_daemon_port
-    );
-
-    match ureq::post(&url).send_form(&post_fields[..]) {
-        Ok(res) if res.status() == 200 => Ok(()),
-        Ok(res) | Err(ureq::Error::Status(_, res)) => {
-            let code = res.status();
-            bail!("invalid authentication - {code} {}", res.status_text())
+    match options.api_daemon_port {
+        cli::DaemonAddress::Port(port) => {
+            let stream =
+                std::net::TcpStream::connect(std::net::SocketAddr::from(([127, 0, 0, 1], port)))?;
+            stream.set_nodelay(true)?;
+            simple_auth_request(stream, &post_fields)
+        }
+        cli::DaemonAddress::UnixSocket(ref path) => {
+            let stream = UnixStream::connect(path)?;
+            simple_auth_request(stream, &post_fields)
         }
-        Err(err) => bail!("authentication request failed - {err}"),
     }
 }
 
-- 
2.39.2




More information about the pve-devel mailing list