From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 234471FF2C7 for ; Wed, 24 Jul 2024 09:51:00 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 4ABD0EEBB; Wed, 24 Jul 2024 09:51:34 +0200 (CEST) From: Dietmar Maurer To: pve-devel@lists.proxmox.com Date: Wed, 24 Jul 2024 09:51:29 +0200 Message-Id: <20240724075129.171221-1-dietmar@proxmox.com> X-Mailer: git-send-email 2.39.2 MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.550 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [cli.rs, main.rs] Subject: [pve-devel] [PATCH pve-xtermjs v2] termproxy: allow to use unix sockets for auth requests X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox VE development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" Remove ureq, because it does not support unix sockets. Signed-off-by: Dietmar Maurer --- Changes since v1: - use extra --authsocket cli option - use single format!() instead of multiple push_str() - cleanup variable names termproxy/Cargo.toml | 2 +- termproxy/src/cli.rs | 26 +++++++++++++++--- termproxy/src/main.rs | 63 ++++++++++++++++++++++++++++++++++--------- 3 files changed, 73 insertions(+), 18 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..02049f5 100644 --- a/termproxy/src/cli.rs +++ b/termproxy/src/cli.rs @@ -4,7 +4,7 @@ use std::os::fd::RawFd; use anyhow::{bail, Result}; const CMD_HELP: &str = "\ -Usage: proxmox-termproxy [OPTIONS] --path -- ... +Usage: proxmox-termproxy [OPTIONS] --path -- ... Arguments: Port or file descriptor to listen for TCP connections @@ -12,6 +12,7 @@ Arguments: Options: --authport Port to relay auth-request, default 85 + --authsocket Unix socket to relay auth-request (conflicts with --authport) --port-as-fd Use as file descriptor. --path ACL object path to test on. --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, /// 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_address: 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,18 @@ 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_address: { + let authport: Option = args.opt_value_from_str("--authport")?; + let authsocket: Option = args.opt_value_from_str("--authsocket")?; + match (authport, authsocket) { + (Some(authport), None) => DaemonAddress::Port(authport), + (None, Some(authsocket)) => DaemonAddress::UnixSocket(authsocket), + (None, None) => DaemonAddress::Port(85), + (Some(_), Some(_)) => { + bail!("conflicting options: --authport and --authsocket are mutually exclusive.") + } + } + }, 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..54e6bf9 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,43 @@ fn read_ticket_line( } } +fn simple_auth_request(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 raw_request = format!( + concat!( + "POST /api2/json/access/ticket HTTP/1.1\r\n", + "Connection: close\r\n", + "User-Agent: termproxy/1.0\r\n", + "Content-Type: application/x-www-form-urlencoded;charset=UTF-8\r\n", + "Content-Length: {}\r\n", + "\r\n" + ), + raw_body.len() + ); + + stream.write_all(raw_request.as_bytes())?; + stream.write_all(raw_body)?; + stream.flush()?; + + let mut res: Vec = 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 +200,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_address { + 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 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel