session: keep Wake-on-LAN to connection entries and bound it

guacd sends the magic packet to whatever broadcast address and UDP port
it is given and then waits the given time before connecting. On an
ad-hoc request from a non-admin those came from the caller, so a
poweruser could have guacd send UDP to an arbitrary address and port,
or hold a guacd process for as long as they liked. Wake-on-LAN is now
ignored on those requests (it is configured on connection entries), the
wait is capped at 10 minutes, and the SSH font size is clamped to 6-72.
This commit is contained in:
Dave Kempe
2026-10-03 10:13:36 +10:00
parent 9b3b11fd09
commit 45df2bac70
+49 -10
View File
@@ -551,6 +551,33 @@ pub(crate) fn allowed_address(
})
}
/// Longest Wake-on-LAN wait rustguac will ask guacd for, in seconds.
const MAX_WOL_WAIT_SECS: u32 = 600;
/// Bounds for the SSH terminal font size, in points.
const MIN_SSH_FONT: u32 = 6;
const MAX_SSH_FONT: u32 = 72;
/// Wake-on-LAN settings for guacd.
///
/// guacd sends the magic packet to whatever broadcast address and UDP port
/// it is given, then waits the given time before connecting, so for an
/// ad-hoc request from a non-admin these come from the caller. They are
/// dropped there: Wake-on-LAN is configured on connection entries. The
/// wait is capped either way so one session cannot hold a guacd process for
/// days.
fn wol_params(req: &CreateSessionRequest, fence_all_targets: bool) -> guacd::WolParams {
if fence_all_targets {
return guacd::WolParams::default();
}
guacd::WolParams {
send_packet: req.wol_send_packet.unwrap_or(false),
mac_addr: req.wol_mac_addr.clone(),
broadcast_addr: req.wol_broadcast_addr.clone(),
udp_port: req.wol_udp_port,
wait_time: req.wol_wait_time.map(|t| t.min(MAX_WOL_WAIT_SECS)),
}
}
/// An address formatted for a `host:port` string (IPv6 in brackets), as
/// the SSH tunnel and probe code builds its dial address that way.
pub(crate) fn dial_host(ip: std::net::IpAddr) -> String {
@@ -768,6 +795,8 @@ impl SessionManager {
// and the generic tunnel setup after the match uses them for the other
// session types.
let fence_all_targets = req.fence_all_targets;
// Wake-on-LAN params, applied to SSH/RDP/VNC alike (passed through to guacd).
let wol = wol_params(&req, fence_all_targets);
let jump_hops: Vec<tunnel::JumpHost> = if let Some(hops) = req.jump_hosts {
hops
} else if let Some(ref jh) = req.jump_host {
@@ -804,15 +833,6 @@ impl SessionManager {
// proxy hops); merged into the session's tunnel list after the match.
let mut proxmox_tunnels: Vec<tunnel::SshTunnel> = Vec::new();
// Wake-on-LAN params, applied to SSH/RDP/VNC alike (passed through to guacd).
let wol = guacd::WolParams {
send_packet: req.wol_send_packet.unwrap_or(false),
mac_addr: req.wol_mac_addr.clone(),
broadcast_addr: req.wol_broadcast_addr.clone(),
udp_port: req.wol_udp_port,
wait_time: req.wol_wait_time,
};
let (
mut conn_params,
hostname,
@@ -944,7 +964,9 @@ impl SessionManager {
.as_ref()
.map(|(_, _, c)| *c)
.unwrap_or(false),
font_size: req.ssh_font_size,
font_size: req
.ssh_font_size
.map(|s| s.clamp(MIN_SSH_FONT, MAX_SSH_FONT)),
wol: wol.clone(),
});
(
@@ -3077,6 +3099,23 @@ mod tests {
}
}
#[test]
fn wol_is_dropped_for_untrusted_ad_hoc_and_wait_is_capped() {
let req = fenced_request(serde_json::json!({
"session_type": "ssh",
"wol_send_packet": true,
"wol_mac_addr": "00:11:22:33:44:55",
"wol_broadcast_addr": "192.0.2.255",
"wol_udp_port": 4822,
"wol_wait_time": 999999,
}));
let fenced = wol_params(&req, true);
assert!(!fenced.send_packet && fenced.broadcast_addr.is_none());
let trusted = wol_params(&req, false);
assert!(trusted.send_packet);
assert_eq!(trusted.wait_time, Some(MAX_WOL_WAIT_SECS));
}
#[test]
fn test_allowed_address_returns_literal() {
let ip = allowed_address("10.1.2.3", 22, &["10.0.0.0/8".into()]).unwrap();