feat: isolate simultaneous RDP fixture profiles

This commit is contained in:
2026-09-14 07:16:39 +00:00
parent 9a2b0fd3c1
commit 620f882fe2
6 changed files with 97 additions and 14 deletions
+31
View File
@@ -125,6 +125,7 @@ All fixture-specific values have embedded, working defaults: the
`rvboxtest`, the browser listener (`127.0.0.1:5002`), and the private tunnel
port (`54001`). They can be overridden without editing tracked files through
`RVBOX_TEST_VBOX_HOST`, `RDP_ACCESS_VRDE_HOST`, `RDP_ACCESS_VRDE_PORT`,
`RDP_ACCESS_PROFILE`,
`RDP_ACCESS_WEB_USER`, `RDP_ACCESS_RDP_USER`, `RDP_ACCESS_BIND`,
`RDP_ACCESS_HTTP_PORT`, `RDP_ACCESS_TUNNEL_PORT`, and
`RDP_ACCESS_PUBLIC_HOST`, `RDP_ACCESS_IPV6_BIND`, and
@@ -134,6 +135,36 @@ turn the diagnostic server into a remote target.
Set `RDP_ACCESS_GUACD_LOG_LEVEL=debug` temporarily when collecting detailed
guacd/RDP negotiation diagnostics; the default is `info`.
The VM selector and the gateway profile are separate. Set the matching
`RVBOX_TEST_VBOX_*` identity variables (and `RDP_ACCESS_VRDE_PORT`) for the
VM you want; the complete identity set is listed in
[`docs/testing-vm.md`](../../docs/testing-vm.md). For two already-running VMs,
give the second gateway a distinct profile and listener ports, for example:
```sh
RDP_ACCESS_PROFILE=vm-b \
RVBOX_TEST_VBOX_HOST=helium-remote-b \
RDP_ACCESS_VRDE_PORT=3391 \
RDP_ACCESS_HTTP_PORT=5003 \
RDP_ACCESS_TUNNEL_PORT=54002 \
test/rdp-access/rdp-access up --bind 0.0.0.0 --public-host vm-b.example.net
```
Replace `helium-remote-b`, `3391`, and the public hostname with the second
fixture's recorded values, and export its matching VM/snapshot UUID variables
before running `up` or `status`. A non-`default` profile stores its verifier,
certificate, tunnel state, and logs under `.runtime/<profile>/` and uses the
Compose project `rvbox-rdp-access-<profile>`. Keep the HTTP port, public
hostname, and profile unique so IPv4/IPv6 listeners and browser sessions do
not collide. Inspect, repair, stop, or clean that instance by repeating the
same `RDP_ACCESS_PROFILE` and endpoint variables on `status`, `repair`,
`down`, or `clean`.
Profile isolation does not bypass the native controller's exclusive lease:
`test-host prepare`/`reset` must still be coordinated when VMs share one
fixture host and staging root. For VMs that are already running, the profile
and matching identity/VRDE variables are sufficient to select the endpoint.
When `--bind 0.0.0.0` is used, `up` also starts a tracked `socat` listener on
`[::]:$RDP_ACCESS_HTTP_PORT` and forwards it to the IPv4 gateway listener. This
matters when the public hostname has an AAAA record: some browsers choose IPv6
+41 -13
View File
@@ -5,11 +5,11 @@ set -eu
helper_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo_root=$(CDPATH= cd -- "$helper_dir/../.." && pwd)
runtime_dir=$helper_dir/.runtime
runtime_root=$helper_dir/.runtime
compose_file=$helper_dir/compose.yaml
mapping_template=$helper_dir/user-mapping.xml.in
project=rvbox-rdp-access
: "${RDP_ACCESS_PROFILE:=default}"
: "${RDP_ACCESS_BIND:=127.0.0.1}"
: "${RDP_ACCESS_HTTP_PORT:=5002}"
: "${RDP_ACCESS_TUNNEL_PORT:=54001}"
@@ -38,6 +38,8 @@ Actions:
the exact unused Guacamole/nginx images
up options:
--profile NAME isolated VM/gateway profile (default default; use one
distinct profile per simultaneous VM gateway)
--bind ADDRESS listener address (default 127.0.0.1; use 0.0.0.0 only
for a temporary, deliberately public endpoint)
--http-port PORT HTTPS listener port (default 5002)
@@ -47,7 +49,7 @@ up options:
--reset-auth discard the saved password hash and prompt again
--web-password-stdin read the password once from stdin instead of prompting
Environment equivalents: RDP_ACCESS_BIND, RDP_ACCESS_HTTP_PORT,
Environment equivalents: RDP_ACCESS_PROFILE, RDP_ACCESS_BIND, RDP_ACCESS_HTTP_PORT,
RDP_ACCESS_TUNNEL_PORT, RDP_ACCESS_PUBLIC_HOST, RDP_ACCESS_WEB_USER,
RDP_ACCESS_RDP_USER, RVBOX_TEST_VBOX_HOST, RDP_ACCESS_VRDE_HOST, and
RDP_ACCESS_VRDE_PORT. In public mode, RDP_ACCESS_IPV6_BIND and
@@ -63,6 +65,16 @@ safe_name() {
case $2 in ''|*[!A-Za-z0-9.-]*) fail "$1 contains unsupported characters" ;; esac
}
safe_profile() {
case $2 in
[a-z0-9]* ) ;;
* ) fail "$1 must start with a lowercase alphanumeric" ;;
esac
case $2 in
*[!a-z0-9-]*) fail "$1 must contain only lowercase letters, digits, and hyphens" ;;
esac
}
safe_port() {
case $2 in ''|*[!0-9]*) fail "$1 must be a port number" ;; esac
[ "$2" -ge 1024 ] && [ "$2" -le 65535 ] || fail "$1 must be between 1024 and 65535"
@@ -102,15 +114,6 @@ wait_for_gateway() {
return 1
}
socket_path=$runtime_dir/ssh-control.socket
session_file=$runtime_dir/session.env
cert_name_file=$runtime_dir/cert-name
tunnel_stop_file=$runtime_dir/tunnel.stop
tunnel_pid_file=$runtime_dir/tunnel-watchdog.pid
tunnel_log_file=$runtime_dir/tunnel.log
ipv6_pid_file=$runtime_dir/ipv6-forward.pid
ipv6_log_file=$runtime_dir/ipv6-forward.log
stop_tunnel() {
mkdir -p "$runtime_dir"
: >"$tunnel_stop_file"
@@ -429,6 +432,7 @@ password_stdin=false
remove_images=false
while [ "$#" -gt 0 ]; do
case $1 in
--profile) [ "$#" -ge 2 ] || fail "--profile needs a value"; RDP_ACCESS_PROFILE=$2; shift 2 ;;
--bind) [ "$#" -ge 2 ] || fail "--bind needs a value"; RDP_ACCESS_BIND=$2; shift 2 ;;
--http-port) [ "$#" -ge 2 ] || fail "--http-port needs a value"; RDP_ACCESS_HTTP_PORT=$2; shift 2 ;;
--tunnel-port) [ "$#" -ge 2 ] || fail "--tunnel-port needs a value"; RDP_ACCESS_TUNNEL_PORT=$2; shift 2 ;;
@@ -442,6 +446,26 @@ while [ "$#" -gt 0 ]; do
esac
done
safe_profile RDP_ACCESS_PROFILE "$RDP_ACCESS_PROFILE"
profile=$RDP_ACCESS_PROFILE
if [ "$profile" = default ]; then
runtime_dir=$runtime_root
project=rvbox-rdp-access
else
runtime_dir=$runtime_root/$profile
project=rvbox-rdp-access-$profile
fi
[ ! -L "$runtime_root" ] || fail "runtime root must not be a symlink"
[ ! -L "$runtime_dir" ] || fail "runtime directory must not be a symlink"
socket_path=$runtime_dir/ssh-control.socket
session_file=$runtime_dir/session.env
cert_name_file=$runtime_dir/cert-name
tunnel_stop_file=$runtime_dir/tunnel.stop
tunnel_pid_file=$runtime_dir/tunnel-watchdog.pid
tunnel_log_file=$runtime_dir/tunnel.log
ipv6_pid_file=$runtime_dir/ipv6-forward.pid
ipv6_log_file=$runtime_dir/ipv6-forward.log
safe_bind "$RDP_ACCESS_BIND"
safe_port RDP_ACCESS_HTTP_PORT "$RDP_ACCESS_HTTP_PORT"
safe_port RDP_ACCESS_TUNNEL_PORT "$RDP_ACCESS_TUNNEL_PORT"
@@ -520,6 +544,7 @@ case $action in
;;
status)
[ "$#" -eq 0 ] || { usage >&2; fail "status accepts no options"; }
printf 'profile=%s project=%s runtime=%s\n' "$profile" "$project" "$runtime_dir"
"$repo_root/scripts/windows/test-host" status || true
if [ -f "$session_file" ]; then sed -n '1p' "$session_file"; fi
compose ps
@@ -568,7 +593,10 @@ case $action in
stop_ipv6_forward
stop_tunnel
compose down --remove-orphans || true
case $runtime_dir in "$helper_dir"/.runtime) rm -rf "$runtime_dir" ;; *) fail "unsafe runtime path" ;; esac
case "$runtime_dir" in
"$runtime_root"|"$runtime_root/$profile") rm -rf "$runtime_dir" ;;
*) fail "unsafe runtime path" ;;
esac
if [ "$remove_images" = true ]; then
docker image rm guacamole/guacamole:1.6.0 guacamole/guacd:1.6.0 nginx:1.27-alpine >/dev/null 2>&1 || true
fi