diff --git a/docs/README.md b/docs/README.md index b7ea2d4..5f64144 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,6 +20,8 @@ Read the documents in this order: 8. [Provisioned Windows test VM](testing-vm.md) — exact fixture identity, host/guest access, endpoints, snapshots, credentials contract, reset procedure, and known limitations. +9. [Interactive VM access](../test/rdp-access/README.md) — temporary, + self-signed HTTPS browser gateway for the rare manual UAC recovery step. The wire authority is in [`../protos/rvbox/v1`](../protos/rvbox/v1): `common.proto` contains shared data types, `agent.proto` contains the diff --git a/docs/implementation-plan.v1.md b/docs/implementation-plan.v1.md index 1233006..f4b7b47 100644 --- a/docs/implementation-plan.v1.md +++ b/docs/implementation-plan.v1.md @@ -847,6 +847,14 @@ The normal active `rvboxtest` session remains the target for execution-role tests. The consent-prompt branch itself remains an interactive UAC test; an invisible Guest Control session must never answer it. +When that bounded manual step is necessary, use the tracked Docker-only +[`test/rdp-access`](../test/rdp-access/README.md) helper. It starts a +self-signed HTTPS Guacamole gateway only after `test-host prepare` holds the +fixture lease; VRDE remains loopback-only on Helium and its SSH tunnel is bound +only to the helper's private Docker gateway. Stop the helper before the normal +`test-host reset`. It is a recovery interface, not a product component or a +replacement for Guest Control/native test automation. + For this provisioned lane, the approved host-only credential-file location is `/home/cabbage/.local/share/rvbox-secrets/rvbox-win10-test.password`. It must remain mode `0600`, is never read into a repository process, and is supplied to diff --git a/docs/testing-vm.md b/docs/testing-vm.md index 8e34349..813cbcc 100644 --- a/docs/testing-vm.md +++ b/docs/testing-vm.md @@ -85,6 +85,12 @@ VirtualBox 7.2.16 does not handle reliably, and earlier probes included headless-server crashes. Use Guest Control for deterministic setup, execution, and collection. Do not expose the VM's RDP endpoints beyond the test LAN. +For the rare interactive UAC/manual-recovery step, use the Docker-only helper +in [`test/rdp-access`](../test/rdp-access/README.md). It creates a temporary +self-signed HTTPS Guacamole gateway while retaining VRDE on Helium loopback and +the SSH tunnel on a private Docker gateway. Follow its full lease/prepare/up/ +down/reset lifecycle; it is not an alternative to the native test controller. + ## Snapshots and reset contract Three clean snapshots exist and must be retained. `baseline-clean-administrator` diff --git a/docs/testing.md b/docs/testing.md index 76c8571..ce742ff 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -138,6 +138,11 @@ than treating it as a stable endpoint. VRDE is enabled only on Helium loopback at `127.0.0.1:3389` for diagnostics, while native Windows RDP is disabled in the baseline. +Interactive browser access is a deliberately temporary recovery path only. See +[`test/rdp-access`](../test/rdp-access/README.md) for the Docker-only, +self-signed HTTPS Guacamole lifecycle; it must be started only after the native +fixture controller has prepared and leased the VM, and stopped before reset. + The canonical headless VirtualBox/Guest Control adapter is `scripts/windows/test-host`. It is a POSIX controller script because the fixture's VirtualBox host is Arch Linux and has no PowerShell runtime. The diff --git a/test/rdp-access/.gitignore b/test/rdp-access/.gitignore new file mode 100644 index 0000000..cc17739 --- /dev/null +++ b/test/rdp-access/.gitignore @@ -0,0 +1 @@ +.runtime/ diff --git a/test/rdp-access/README.md b/test/rdp-access/README.md new file mode 100644 index 0000000..2ec0b8e --- /dev/null +++ b/test/rdp-access/README.md @@ -0,0 +1,118 @@ +# Interactive Windows VM access + +This directory is an explicitly temporary, manual-recovery path to the Helium +Windows fixture desktop. It is for the small class of actions that require an +interactive UAC consent dialog. Normal setup, testing, collection, and reset +remain `scripts/windows/test-host` plus VirtualBox Guest Control. + +The helper builds this private path: + +```text +browser -- HTTPS/self-signed --> nginx + Guacamole containers + | + private Docker gateway + | +controller SSH tunnel --> Helium 127.0.0.1:3389 --> VirtualBox VRDE --> VM console +``` + +Only the HTTPS listener can be made public, and that requires an explicit +`--bind 0.0.0.0`. The VirtualBox VRDE endpoint stays on Helium loopback and the +SSH tunnel binds only to the Docker network gateway; neither is publicly +exposed. Guacamole requires the fixture login before it forwards the entered +password to the VM. Clipboard, drives, printing, audio, microphone input, GFX, +and display-resize extensions are disabled because the fixture's VirtualBox +RDP4 server does not handle them reliably. + +## Lifecycle + +Run commands from the repository root. First prepare the disposable VM using a +dedicated run ID. This restores the snapshot, starts the VM headlessly, and +holds the exclusive fixture lease while the manual action is in progress: + +```sh +scripts/windows/test-host prepare --run-id interactive-rdp +``` + +For browser access from another machine, deliberately expose the temporary +HTTPS listener and name the host or IP users will enter in the browser: + +```sh +test/rdp-access/rdp-access up \ + --bind 0.0.0.0 \ + --public-host x1.example.net +``` + +The command prompts without echo for the fixture password. It stores only its +MD5 verifier in `test/rdp-access/.runtime/config/user-mapping.xml`, mode 600; +the plaintext password is not placed in a command line, environment variable, +log, or repository file. Browse to the printed `https://.../guacamole/` URL, +accept the short-lived self-signed certificate warning, and sign in as +`rvboxtest` with the fixture password. + +For localhost-only use, omit `--bind` and `--public-host`. The default listener +is `127.0.0.1:5002`; use a local SSH forward or a browser on the controller. +Choose alternate ports with `--http-port` and `--tunnel-port` if either is in +use. The VM must already be running. `up` checks the documented VM/snapshot +identity but intentionally does not restore, start, stop, or reset the VM. + +All fixture-specific values have embedded, working defaults: the +`helium-remote` SSH alias, Helium's loopback VRDE endpoint (`127.0.0.1:3389`), +`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_WEB_USER`, `RDP_ACCESS_RDP_USER`, `RDP_ACCESS_BIND`, +`RDP_ACCESS_HTTP_PORT`, `RDP_ACCESS_TUNNEL_PORT`, and +`RDP_ACCESS_PUBLIC_HOST`. The helper deliberately limits the VRDE host to +Helium loopback (`127.0.0.1` or `localhost`) so an override cannot accidentally +turn the diagnostic server into a remote target. + +After the interactive action, close the browser connection and remove the +temporary access path before releasing the fixture lease: + +```sh +test/rdp-access/rdp-access down +scripts/windows/test-host reset --run-id interactive-rdp +``` + +`down` stops containers and the SSH master/tunnel but retains the one-day +certificate and password verifier for a quick restart. To remove all generated +state, including the certificate and verifier: + +```sh +test/rdp-access/rdp-access clean +``` + +To also reclaim the exact Guacamole and nginx images when they have no +container dependency, use: + +```sh +test/rdp-access/rdp-access clean --images +``` + +`clean --images` deliberately leaves `alpine:3.20` alone because it may be +shared by unrelated containers. Docker will refuse removal if any other +container still depends on an image. + +## Operational checks and recovery + +```sh +test/rdp-access/rdp-access status +test/rdp-access/rdp-access logs --tail=100 +test/rdp-access/rdp-access url +``` + +If the browser reaches Guacamole but stays on “Waiting for response”, verify +that the VM is running and the private tunnel is active with `status`. This +helper already uses `security=rdp` and disables Guacamole's GFX extension, +which are required by the fixture's legacy VRDE server. Do not switch the +helper to native Windows RDP: `TermService` is intentionally disabled in the +baseline. If VRDE remains unusable, stop this helper and use Guest Control for +the deterministic portion of the work; record the blocked interactive step in +the native test report. + +The helper requires Docker/Docker Compose, SSH access through the existing +`helium-remote` alias, and the fixture password file documented in +[`docs/testing-vm.md`](../../docs/testing-vm.md). It does not install host +packages, write credentials into Git, or alter VM settings. The tracked files +are Docker-only configuration and the controller script; all generated content +is ignored beneath `.runtime/`. diff --git a/test/rdp-access/compose.yaml b/test/rdp-access/compose.yaml new file mode 100644 index 0000000..c7f19a1 --- /dev/null +++ b/test/rdp-access/compose.yaml @@ -0,0 +1,46 @@ +services: + guacd: + image: guacamole/guacd:1.6.0 + + guacamole: + image: guacamole/guacamole:1.6.0 + depends_on: + - guacd + environment: + GUACD_HOSTNAME: guacd + GUACD_PORT: "4822" + volumes: + - ${RDP_ACCESS_RUNTIME_DIR}/config:/etc/guacamole:ro + + certgen: + image: alpine:3.20 + environment: + RDP_ACCESS_CERT_NAME: ${RDP_ACCESS_CERT_NAME} + RDP_ACCESS_CERT_SAN: ${RDP_ACCESS_CERT_SAN} + RDP_ACCESS_HOST_UID: ${RDP_ACCESS_HOST_UID} + RDP_ACCESS_HOST_GID: ${RDP_ACCESS_HOST_GID} + volumes: + - ${RDP_ACCESS_RUNTIME_DIR}/tls:/tls + entrypoint: /bin/sh + command: + - -ec + - >- + apk add --no-cache openssl >/dev/null && + (test -s /tls/cert.pem && test -s /tls/key.pem && + openssl x509 -checkend 3600 -noout -in /tls/cert.pem) || + (umask 077 && + openssl req -x509 -newkey rsa:3072 -sha256 -nodes -days 1 + -keyout /tls/key.pem -out /tls/cert.pem + -subj /CN=$${RDP_ACCESS_CERT_NAME} + -addext subjectAltName=$${RDP_ACCESS_CERT_SAN} && + chown $${RDP_ACCESS_HOST_UID}:$${RDP_ACCESS_HOST_GID} /tls /tls/cert.pem /tls/key.pem) + + gateway: + image: nginx:1.27-alpine + depends_on: + - guacamole + ports: + - ${RDP_ACCESS_BIND}:${RDP_ACCESS_HTTP_PORT}:8443 + volumes: + - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + - ${RDP_ACCESS_RUNTIME_DIR}/tls:/etc/nginx/tls:ro diff --git a/test/rdp-access/nginx.conf b/test/rdp-access/nginx.conf new file mode 100644 index 0000000..16f0fc2 --- /dev/null +++ b/test/rdp-access/nginx.conf @@ -0,0 +1,19 @@ +server { + listen 8443 ssl; + server_name _; + + ssl_certificate /etc/nginx/tls/cert.pem; + ssl_certificate_key /etc/nginx/tls/key.pem; + ssl_protocols TLSv1.2 TLSv1.3; + + location / { + proxy_pass http://guacamole:8080; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_buffering off; + } +} diff --git a/test/rdp-access/rdp-access b/test/rdp-access/rdp-access new file mode 100755 index 0000000..f4f409c --- /dev/null +++ b/test/rdp-access/rdp-access @@ -0,0 +1,280 @@ +#!/bin/sh +# Temporary browser access to the Helium fixture's loopback-only VirtualBox +# VRDE endpoint. This is a manual-recovery helper, never a normal test channel. +set -eu + +helper_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +repo_root=$(CDPATH= cd -- "$helper_dir/../.." && pwd) +runtime_dir=$helper_dir/.runtime +compose_file=$helper_dir/compose.yaml +mapping_template=$helper_dir/user-mapping.xml.in +project=rvbox-rdp-access + +: "${RDP_ACCESS_BIND:=127.0.0.1}" +: "${RDP_ACCESS_HTTP_PORT:=5002}" +: "${RDP_ACCESS_TUNNEL_PORT:=54001}" +: "${RDP_ACCESS_PUBLIC_HOST:=localhost}" +: "${RDP_ACCESS_WEB_USER:=rvboxtest}" +: "${RDP_ACCESS_RDP_USER:=rvboxtest}" +: "${RVBOX_TEST_VBOX_HOST:=helium-remote}" +: "${RDP_ACCESS_VRDE_HOST:=127.0.0.1}" +: "${RDP_ACCESS_VRDE_PORT:=3389}" + +usage() { + cat <<'EOF' +usage: test/rdp-access/rdp-access ACTION [OPTIONS] + +Actions: + up start a private VRDE SSH tunnel and self-signed HTTPS Guacamole + status show gateway, tunnel, and fixture status without changing anything + url print the current browser URL + logs follow or print Compose logs (pass Docker Compose log options) + down stop containers and the private SSH tunnel; retain generated state + clean run down and delete generated state; pass --images to also remove + the exact unused Guacamole/nginx images + +up options: + --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) + --tunnel-port PORT private VRDE tunnel port (default 54001) + --public-host NAME browser-visible hostname or IP for the URL and cert SAN + --web-user USER Guacamole and Windows account (default rvboxtest) + --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, +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. +EOF +} + +fail() { printf '%s\n' "rdp-access: $*" >&2; exit 2; } + +safe_name() { + case $2 in ''|*[!A-Za-z0-9.-]*) fail "$1 contains unsupported characters" ;; 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" +} + +safe_bind() { + case $1 in 127.0.0.1|0.0.0.0) ;; *) fail "--bind must be 127.0.0.1 or 0.0.0.0" ;; esac +} + +compose() { + RDP_ACCESS_RUNTIME_DIR=$runtime_dir \ + RDP_ACCESS_BIND=$RDP_ACCESS_BIND \ + RDP_ACCESS_HTTP_PORT=$RDP_ACCESS_HTTP_PORT \ + RDP_ACCESS_CERT_NAME=$RDP_ACCESS_PUBLIC_HOST \ + RDP_ACCESS_CERT_SAN=$cert_san \ + RDP_ACCESS_HOST_UID=$(id -u) \ + RDP_ACCESS_HOST_GID=$(id -g) \ + docker compose --project-name "$project" -f "$compose_file" "$@" +} + +socket_path=$runtime_dir/ssh-control.socket +session_file=$runtime_dir/session.env +cert_name_file=$runtime_dir/cert-name + +stop_tunnel() { + if [ -S "$socket_path" ]; then + ssh -S "$socket_path" -O exit "$RVBOX_TEST_VBOX_HOST" >/dev/null 2>&1 || true + fi + rm -f "$socket_path" +} + +gateway_for_network() { + docker network inspect --format '{{(index .IPAM.Config 0).Gateway}}' "${project}_default" +} + +assert_fixture_running() { + fixture_status=$("$repo_root/scripts/windows/test-host" status) || fail "fixture identity check failed" + printf '%s\n' "$fixture_status" + case $fixture_status in *'state=running') ;; *) fail "VM is not running; prepare it first with scripts/windows/test-host prepare --run-id interactive-rdp" ;; esac +} + +password_hash_from_terminal() { + password= + restore_tty=false + if [ "$password_stdin" = true ]; then + IFS= read -r password || fail "could not read password from stdin" + else + [ -t 0 ] || fail "stdin is not a terminal; use --web-password-stdin" + printf 'Fixture password for %s: ' "$RDP_ACCESS_WEB_USER" >&2 + stty -echo + restore_tty=true + trap 'test "$restore_tty" = true && stty echo || true' EXIT HUP INT TERM + IFS= read -r password || fail "could not read password" + stty echo + restore_tty=false + trap - EXIT HUP INT TERM + printf '\n' >&2 + fi + [ -n "$password" ] || fail "password must not be empty" + hash=$(printf '%s' "$password" | docker run --rm -i --entrypoint md5sum alpine:3.20 | awk '{print $1}') + unset password + case $hash in ''|*[!0-9a-f]*) fail "could not generate password hash" ;; esac + [ "${#hash}" -eq 32 ] || fail "could not generate password hash" + printf '%s\n' "$hash" +} + +render_mapping() { + umask 077 + mkdir -p "$runtime_dir/config" "$runtime_dir/tls" + chmod 700 "$runtime_dir" "$runtime_dir/config" "$runtime_dir/tls" + if [ "$reset_auth" = true ]; then rm -f "$runtime_dir/config/user-mapping.xml"; fi + if [ -s "$runtime_dir/config/user-mapping.xml" ]; then + password_hash=$(sed -n 's/.*password="\([0-9a-f][0-9a-f]*\)".*/\1/p' "$runtime_dir/config/user-mapping.xml" | head -n 1) + case $password_hash in ''|*[!0-9a-f]*) password_hash=$(password_hash_from_terminal) ;; esac + [ "${#password_hash}" -eq 32 ] || password_hash=$(password_hash_from_terminal) + else + password_hash=$(password_hash_from_terminal) + fi + sed \ + -e "s/@WEB_USER@/$RDP_ACCESS_WEB_USER/g" \ + -e "s/@WEB_PASSWORD_MD5@/$password_hash/g" \ + -e "s/@DOCKER_GATEWAY@/$docker_gateway/g" \ + -e "s/@TUNNEL_PORT@/$RDP_ACCESS_TUNNEL_PORT/g" \ + -e "s/@RDP_USER@/$RDP_ACCESS_RDP_USER/g" \ + "$mapping_template" >"$runtime_dir/config/user-mapping.xml" + chmod 600 "$runtime_dir/config/user-mapping.xml" + if [ -f "$cert_name_file" ] && [ "$(cat "$cert_name_file")" != "$RDP_ACCESS_PUBLIC_HOST" ]; then + rm -f "$runtime_dir/tls/cert.pem" "$runtime_dir/tls/key.pem" + fi + printf '%s\n' "$RDP_ACCESS_PUBLIC_HOST" >"$cert_name_file" + chmod 600 "$cert_name_file" + printf 'url=https://%s:%s/guacamole/\n' "$RDP_ACCESS_PUBLIC_HOST" "$RDP_ACCESS_HTTP_PORT" >"$session_file" + printf 'docker_gateway=%s\n' "$docker_gateway" >>"$session_file" + printf 'tunnel_port=%s\n' "$RDP_ACCESS_TUNNEL_PORT" >>"$session_file" + chmod 600 "$session_file" +} + +start_tunnel() { + stop_tunnel + ssh -M -S "$socket_path" -fN \ + -o BatchMode=yes \ + -o ExitOnForwardFailure=yes \ + -o ServerAliveInterval=30 \ + -o ServerAliveCountMax=3 \ + -L "$docker_gateway:$RDP_ACCESS_TUNNEL_PORT:$RDP_ACCESS_VRDE_HOST:$RDP_ACCESS_VRDE_PORT" \ + "$RVBOX_TEST_VBOX_HOST" + ssh -S "$socket_path" -O check "$RVBOX_TEST_VBOX_HOST" >/dev/null 2>&1 || fail "private VRDE tunnel did not start" +} + +action=${1-} +[ -n "$action" ] || { usage >&2; exit 2; } +shift || true +case $action in --help|-h) usage; exit 0 ;; esac + +reset_auth=false +password_stdin=false +remove_images=false +while [ "$#" -gt 0 ]; do + case $1 in + --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 ;; + --public-host) [ "$#" -ge 2 ] || fail "--public-host needs a value"; RDP_ACCESS_PUBLIC_HOST=$2; shift 2 ;; + --web-user) [ "$#" -ge 2 ] || fail "--web-user needs a value"; RDP_ACCESS_WEB_USER=$2; RDP_ACCESS_RDP_USER=$2; shift 2 ;; + --reset-auth) reset_auth=true; shift ;; + --web-password-stdin) password_stdin=true; shift ;; + --images) remove_images=true; shift ;; + --help|-h) usage; exit 0 ;; + *) break ;; + esac +done + +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" +[ "$RDP_ACCESS_HTTP_PORT" != "$RDP_ACCESS_TUNNEL_PORT" ] || fail "HTTPS and tunnel ports must differ" +safe_name RDP_ACCESS_PUBLIC_HOST "$RDP_ACCESS_PUBLIC_HOST" +safe_name RDP_ACCESS_WEB_USER "$RDP_ACCESS_WEB_USER" +safe_name RDP_ACCESS_RDP_USER "$RDP_ACCESS_RDP_USER" +safe_name RVBOX_TEST_VBOX_HOST "$RVBOX_TEST_VBOX_HOST" +case $RDP_ACCESS_VRDE_HOST in 127.0.0.1|localhost) ;; *) fail "RDP_ACCESS_VRDE_HOST must be 127.0.0.1 or localhost" ;; esac +safe_port RDP_ACCESS_VRDE_PORT "$RDP_ACCESS_VRDE_PORT" + +case $RDP_ACCESS_PUBLIC_HOST in + *[!0-9.]* ) cert_san="DNS:$RDP_ACCESS_PUBLIC_HOST" ;; + * ) cert_san="IP:$RDP_ACCESS_PUBLIC_HOST" ;; +esac + +[ "$remove_images" = false ] || [ "$action" = clean ] || fail "--images is valid only with clean" +[ "$reset_auth" = false ] || [ "$action" = up ] || fail "--reset-auth is valid only with up" +[ "$password_stdin" = false ] || [ "$action" = up ] || fail "--web-password-stdin is valid only with up" + +case $action in + up) + [ "$#" -eq 0 ] || { usage >&2; fail "unknown up option $1"; } + if [ "$RDP_ACCESS_BIND" = 0.0.0.0 ] && [ "$RDP_ACCESS_PUBLIC_HOST" = localhost ]; then + fail "a public bind requires --public-host with the browser-visible hostname or IP" + fi + docker version >/dev/null + docker compose version >/dev/null + assert_fixture_running + if compose ps -q | grep -q .; then + fail "gateway already exists; use status or down first" + fi + mkdir -p "$runtime_dir" + compose up -d guacd + docker_gateway=$(gateway_for_network) || { compose down --remove-orphans; fail "could not determine private Docker gateway"; } + render_mapping + if ! start_tunnel; then + compose down --remove-orphans + fail "could not create private SSH tunnel" + fi + if ! compose run --rm certgen; then + stop_tunnel + compose down --remove-orphans + fail "could not generate self-signed certificate" + fi + if ! compose up -d guacamole gateway; then + stop_tunnel + compose down --remove-orphans + fail "could not start Guacamole gateway" + fi + printf 'Guacamole is ready at https://%s:%s/guacamole/\n' "$RDP_ACCESS_PUBLIC_HOST" "$RDP_ACCESS_HTTP_PORT" + printf 'Accept the self-signed certificate warning, then sign in as %s with the fixture password.\n' "$RDP_ACCESS_WEB_USER" + ;; + status) + [ "$#" -eq 0 ] || { usage >&2; fail "status accepts no options"; } + "$repo_root/scripts/windows/test-host" status || true + if [ -f "$session_file" ]; then sed -n '1p' "$session_file"; fi + compose ps + if [ -S "$socket_path" ] && ssh -S "$socket_path" -O check "$RVBOX_TEST_VBOX_HOST" >/dev/null 2>&1; then + printf 'private_tunnel=active\n' + else + printf 'private_tunnel=inactive\n' + fi + ;; + url) + [ "$#" -eq 0 ] || { usage >&2; fail "url accepts no options"; } + [ -f "$session_file" ] || fail "no saved gateway session; run up first" + sed -n '1s/^url=//p' "$session_file" + ;; + logs) + compose logs "$@" + ;; + down) + [ "$#" -eq 0 ] || { usage >&2; fail "down accepts no options"; } + stop_tunnel + compose down --remove-orphans || true + printf 'Temporary gateway and private tunnel stopped; generated certificate and password hash retained in %s.\n' "$runtime_dir" + ;; + clean) + [ "$#" -eq 0 ] || { usage >&2; fail "clean accepts only --images"; } + stop_tunnel + compose down --remove-orphans || true + case $runtime_dir in "$helper_dir"/.runtime) 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 + printf 'Temporary gateway state removed.\n' + ;; + *) usage >&2; fail "unknown action $action" ;; +esac diff --git a/test/rdp-access/user-mapping.xml.in b/test/rdp-access/user-mapping.xml.in new file mode 100644 index 0000000..065ee97 --- /dev/null +++ b/test/rdp-access/user-mapping.xml.in @@ -0,0 +1,21 @@ + + + + rdp + @DOCKER_GATEWAY@ + @TUNNEL_PORT@ + rdp + @RDP_USER@ + ${GUAC_PASSWORD} + true + true + false + false + false + false + false + false + true + + +