test: add temporary Guacamole fixture access helper
This commit is contained in:
@@ -20,6 +20,8 @@ Read the documents in this order:
|
|||||||
8. [Provisioned Windows test VM](testing-vm.md) — exact fixture identity,
|
8. [Provisioned Windows test VM](testing-vm.md) — exact fixture identity,
|
||||||
host/guest access, endpoints, snapshots, credentials contract, reset
|
host/guest access, endpoints, snapshots, credentials contract, reset
|
||||||
procedure, and known limitations.
|
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):
|
The wire authority is in [`../protos/rvbox/v1`](../protos/rvbox/v1):
|
||||||
`common.proto` contains shared data types, `agent.proto` contains the
|
`common.proto` contains shared data types, `agent.proto` contains the
|
||||||
|
|||||||
@@ -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
|
tests. The consent-prompt branch itself remains an interactive UAC test; an
|
||||||
invisible Guest Control session must never answer it.
|
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
|
For this provisioned lane, the approved host-only credential-file location is
|
||||||
`/home/cabbage/.local/share/rvbox-secrets/rvbox-win10-test.password`. It must
|
`/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
|
remain mode `0600`, is never read into a repository process, and is supplied to
|
||||||
|
|||||||
@@ -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,
|
headless-server crashes. Use Guest Control for deterministic setup, execution,
|
||||||
and collection. Do not expose the VM's RDP endpoints beyond the test LAN.
|
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
|
## Snapshots and reset contract
|
||||||
|
|
||||||
Three clean snapshots exist and must be retained. `baseline-clean-administrator`
|
Three clean snapshots exist and must be retained. `baseline-clean-administrator`
|
||||||
|
|||||||
@@ -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
|
at `127.0.0.1:3389` for diagnostics, while native Windows RDP is disabled in
|
||||||
the baseline.
|
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
|
The canonical headless VirtualBox/Guest Control adapter is
|
||||||
`scripts/windows/test-host`. It is a POSIX controller script because the
|
`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
|
fixture's VirtualBox host is Arch Linux and has no PowerShell runtime. The
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
.runtime/
|
||||||
@@ -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/`.
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+280
@@ -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
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<user-mapping>
|
||||||
|
<authorize username="@WEB_USER@" password="@WEB_PASSWORD_MD5@" encoding="md5">
|
||||||
|
<connection name="RVBox Windows test VM">
|
||||||
|
<protocol>rdp</protocol>
|
||||||
|
<param name="hostname">@DOCKER_GATEWAY@</param>
|
||||||
|
<param name="port">@TUNNEL_PORT@</param>
|
||||||
|
<param name="security">rdp</param>
|
||||||
|
<param name="username">@RDP_USER@</param>
|
||||||
|
<param name="password">${GUAC_PASSWORD}</param>
|
||||||
|
<param name="ignore-cert">true</param>
|
||||||
|
<param name="disable-audio">true</param>
|
||||||
|
<param name="enable-audio-input">false</param>
|
||||||
|
<param name="enable-drive">false</param>
|
||||||
|
<param name="enable-printing">false</param>
|
||||||
|
<param name="enable-wallpaper">false</param>
|
||||||
|
<param name="enable-theming">false</param>
|
||||||
|
<param name="enable-font-smoothing">false</param>
|
||||||
|
<param name="disable-gfx">true</param>
|
||||||
|
</connection>
|
||||||
|
</authorize>
|
||||||
|
</user-mapping>
|
||||||
Reference in New Issue
Block a user