[pve-devel] [PATCH pve-xtermjs v3] termproxy: allow to use unix sockets for auth requests
Dietmar Maurer
dietmar at proxmox.com
Wed Jul 24 13:33:29 CEST 2024
Remove ureq, because it does not support unix sockets.
Signed-off-by: Dietmar Maurer <dietmar at proxmox.com>
---
Changes sinve v2:
split out the command line help text change into patch:
[PATCH pve-xtermjs] termproxy: fix the command line help text
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 | 24 ++++++++++++++---
termproxy/src/main.rs | 63 ++++++++++++++++++++++++++++++++++---------
3 files changed, 72 insertions(+), 17 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 adfd830..02049f5 100644
--- a/termproxy/src/cli.rs
+++ b/termproxy/src/cli.rs
@@ -12,6 +12,7 @@ Arguments:
Options:
--authport <authport> Port to relay auth-request, default 85
+ --authsocket <authsocket> Unix socket to relay auth-request (conflicts with --authport)
--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_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<u16> = args.opt_value_from_str("--authport")?;
+ let authsocket: Option<String> = 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<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 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<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 +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
More information about the pve-devel
mailing list