Skip to content

fix(ansible/vnc): offer VncAuth so clients can connect, give VNC its own TLS key, stop provisioning on unselected hosts (#13060) - #13061

Merged
mrveiss merged 3 commits into
Dev_new_guifrom
issue-13060
Jul 30, 2026
Merged

mrveiss merged 3 commits into
Dev_new_guifrom
issue-13060

Conversation

@mrveiss

@mrveiss mrveiss commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Closes #13060

Thinking Path

Started from a live deploy symptom: the server offered VeNCrypt only (type 19) while the novnc apt package ships 1.0.0, which supports std_vnc_auth / tight_auth / xvp_auth — VeNCrypt only arrived in noVNC 1.4.0.

The obvious read is "upgrade noVNC". That would not have fixed it. Tracing the config chain showed the role sets SecurityTypes=VeNCrypt, which names the VeNCrypt wrapper without any sub-type (TLSVnc / X509Vnc / Plain), leaving the sub-type list empty — so even a VeNCrypt-capable client has nothing to negotiate. The role also provisions a VncAuth password (vncpasswd -f), so it was configuring a security type that never reads the credentials it creates.

Confirming it as divergence rather than intent: every other VNC launcher in the repo already uses VncAuth — api/vnc_manager.py:143, browser-vnc.service:18, setup_browser_vnc.sh:57, fix-vnc-desktop.sh:58, and docs/guides/vision-vnc-ui-testing.md:2316. The Ansible role was the lone outlier.

That also explains any intermittency: starting VNC through the backend API passes -SecurityTypes VncAuth,TLSVnc on the CLI and works, while the systemd unit passes none — so ~/.vnc/config wins at boot and the server comes up unreachable.

Two further defects surfaced while fixing this and are folded in, per the fix-what-you-find rule.

What Changed

1. Security type — VeNCrypt → VncAuth,TLSVnc,X509Vnc

roles/vnc/defaults/main.yml. noVNC connects via plain VncAuth; native viewers can negotiate the TLS-wrapped sub-types. VncAuth on the raw RFB hop is safe here because websockify already terminates TLS for the browser and reaches VNC over loopback, and register-credentials.yml registers the websockify port, not the raw RFB port.

2. Dedicated VNC TLS keypair

vnc-websockify.service.j2 runs as {{ vnc_user }} but pointed --cert/--key at /etc/autobot/certs/server-key.pem — documented root:root 0600 in setup-internal-ca.yml:435 and chmodded 0600 with no chown by this role. An unprivileged user cannot read it, so the --ssl-only unit could never start.

Fixed with a dedicated pair (vnc-cert.pem / vnc-key.pem) owned by vnc_user, rather than loosening the shared node key that nginx, Redis and PostgreSQL also consume. The same pair backs the new X509Vnc type. Node-level cert generation is left in place — a vnc-only node still needs one.

3. role_vnc_active — explicit selection only

Reported from the field: the VNC stack landed on a host with no vnc in node_roles. Both duplicated copies (playbooks/vars/role_active_facts.yml, inventory/group_vars/all.yml) had groups['main'] and groups['backend'] fallbacks, pulling in tigervnc, x11vnc, novnc, websockify and xfce4. VNC was the outlier — role_xrdp_active, defined immediately below, has no such fallback. Both now gate on 'vnc' in node_roles or vnc_nodes membership.

Important

Migration. A backend/main host that relied on implicit activation must now list vnc in its node_roles (or join vnc_nodes) to keep its desktop provisioned. This matters where api/vnc_manager.py:85 pgreps for Xtigervnc on the canonical display.

Verification

Role-active-facts tests — both loading paths, all 9 synthetic hosts, failed=0:

# group_vars path
ansible-playbook -i tests/inventory/test_role_facts.yml tests/playbooks/test_role_active_facts.yml
test-ai-stack-and-llm            : ok=4    failed=0
test-backend-short-name          : ok=3    failed=0
test-browser-legacy-name         : ok=3    failed=0
test-dedicated-slm-manager       : ok=10   failed=0
test-empty-roles-via-group       : ok=4    failed=0
test-frontend-prefixed           : ok=3    failed=0
test-no-roles-defined            : ok=10   failed=0
test-npu-and-tts                 : ok=4    failed=0
test-single-host-slm-and-backend : ok=10   failed=0

# vars_files path (dynamic inventory, no group_vars) — same 9 hosts, failed=0
ansible-playbook -i tests/inventory_dynamic/test_role_facts.yml \
                 tests/playbooks/test_role_active_facts_no_group_vars.yml

Negative control — proves the flipped expectation is a live assertion, not a silently-skipped one:

$ ansible-playbook ... -e 'role_vnc_active=true'
fatal: [test-empty-roles-via-group]: FAILED! =>
  "test-empty-roles-via-group: role_vnc_active=true, expected=False (node_roles=[])"
fatal: [test-backend-short-name]: FAILED! =>
  "test-backend-short-name: role_vnc_active=true, expected=False (node_roles=['backend'])"

Rendered templates:

# vnc-config
SecurityTypes=VncAuth,TLSVnc,X509Vnc
X509Cert=/etc/autobot/certs/vnc-cert.pem
X509Key=/etc/autobot/certs/vnc-key.pem

# vnc-websockify.service   (User=autobot — matches the key owner)
ExecStart=/usr/bin/websockify --web=/usr/share/novnc \
    --cert=/etc/autobot/certs/vnc-cert.pem \
    --key=/etc/autobot/certs/vnc-key.pem \
    --ssl-only \
    0.0.0.0:6080 localhost:5901

Syntax / YAML: ansible-playbook --syntax-check deploy.yml passes; all 6 changed YAML files parse.

openssl argv smoke test (exact string the command module shlex-splits):

subject=CN = node.autobot.internal
notAfter=Jul 27 17:03:50 2036 GMT
RSA key ok

Notes

vncpasswd -f writes a VncAuth password, and the RFB VncAuth scheme truncates to 8 characters — so the ~16-char generated secret in /etc/autobot/vnc-secrets.env yields ~47 bits of effective strength regardless of its length. That is inherent to VncAuth, not introduced here, and is a further reason the TLS sub-types stay in the offer list. Noted on #13060 rather than expanded into this PR.

Model Used

Claude Opus 5 (1M context)

…own TLS key, stop provisioning on unselected hosts (#13060)

Three defects in the Ansible vnc role, all reproduced on a live deploy.

1. SecurityTypes=VeNCrypt made the server unreachable by every client.
   The role provisions VncAuth credentials (vncpasswd -f) and then advertised
   only security type 19. Bare "VeNCrypt" names the wrapper with no sub-type,
   so the sub-type list is empty and even a VeNCrypt-capable client has nothing
   to negotiate; noVNC < 1.4.0 (apt novnc ships 1.0.0) cannot do VeNCrypt at
   all. Every other launcher in the repo already uses VncAuth --
   api/vnc_manager.py, browser-vnc.service, setup_browser_vnc.sh,
   fix-vnc-desktop.sh, vision-vnc-ui-testing.md -- so the role was the lone
   outlier. Default is now VncAuth,TLSVnc,X509Vnc.

   This also explains intermittency: the backend API path passes
   -SecurityTypes on the CLI and worked, while the systemd unit passes none,
   so ~/.vnc/config won at boot and the server came up unreachable.

2. websockify could not read its own TLS key. The unit runs as vnc_user but
   pointed --cert/--key at the shared node keypair, which is root:root 0600
   (setup-internal-ca.yml) and is also consumed by nginx, Redis and
   PostgreSQL. The --ssl-only unit could never start. VNC now gets a dedicated
   keypair owned by vnc_user instead of widening access to the shared key;
   the same pair backs the X509Vnc security type.

3. role_vnc_active pulled tigervnc, x11vnc, novnc, websockify and xfce4 onto
   every host in groups['main'] or groups['backend'], including hosts with no
   'vnc' in node_roles. Both duplicated copies now gate on explicit selection
   only, matching role_xrdp_active.

   Migration: a backend/main host that relied on implicit activation must list
   'vnc' in node_roles (or join vnc_nodes) to keep its desktop provisioned.

Verification: both role-active-facts test playbooks pass across all 9 synthetic
hosts (group_vars and vars_files paths); the flipped expectation is a live
assertion, proven by a negative control forcing role_vnc_active=true;
templates render SecurityTypes=VncAuth,TLSVnc,X509Vnc and a vnc_user-readable
key path; deploy.yml passes --syntax-check; the openssl argv produces a valid
keypair with the expected subject.
@github-actions

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No hardcoded values detected that have SSOT config equivalents!

@mrveiss
mrveiss merged commit df0c203 into Dev_new_gui Jul 30, 2026
38 checks passed
@mrveiss
mrveiss deleted the issue-13060 branch July 30, 2026 19:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant